|
|
|
|
移动端
创建专栏

Kotlin重载个方法,还有两幅面孔,省代码的同时也带来一个深坑 | Kotlin 原理

本文来源:http://www.2233122.com/pc_pcgames_com_cn/

太阳城娱乐网最快登入,近日,有用户向苹果高管CraigFederighi发邮件询问花屏问题如何解决,Federighi回复称,在最新的macOSSierra10.12.2测试版中,花屏问题已经得到了修复。互联网时代,天目领航以物联网思维,以打造上市公司集群为目标的商业模式研发与输出单位,按照打造上市公司的股份有限公司来管理与运行股权增值的管理模式,以物联网平台公司和物联网实体公司的组合来激活传统企业的股权融资能力,帮助传统实体企业打造一条既快捷又科学,特别是最安全地进入资本市场的快速通道。  不管怎样,阿里和运营商的合作已经提上日程,运营商通过这一合作实现解决应用的最后一公里问题,还是非常值得期待的。而目前,相类似的技术已经广泛应用于3DNAND颗粒的制造当中。

(完)(来源:)  台湾精品馆馆长杨光明介绍,台湾精品馆推出的产品是台湾产业的成长标杆。部属单位工业和信息化部工业和信息化部电信研究院中国电子信息产业发展研究院计算机网络处理中心国家无线电监测中心部电子科技情报研究所电子标准化研究所电子产品试验研究所人民邮电报社中国电子报社通信产业报社人民邮电出版社电子工业出版社部电子人才交流中心部电子教育中心部电子国际合作服务中心部软件与集成电路促进中心网络不良信息举报运营商中国电信中国移动中国联通通信厂商中兴通讯亿美软通-移动商务深圳天源迪科爱立信诺基亚西门子通信上海贝尔阿尔卡特华为技术CIOE光通信展摩托罗拉亨通集团大唐电信联想美的空调IBM中国瑞斯康达HP中兴通讯微软(中国)有限公司烽火通信俊知技术中国普天烽火网络UT斯达康安捷伦R&SJDSU长飞突破电气国人通信三星日讯科技虹信通信艾默生阿尔西中利科技集团宇龙博通公司中天科技松下空调帝斯曼迪索新邮通多普达高通诺基亚英特尔科华公司重邮信科展讯通信天宇朗通四方通信科达科技RADVISION通鼎集团大圣光纤华夏未来NEC北电网络思科系统OKWAP罗德与施瓦茨金鹏鼎桥富士通中国日立中国日立信息系统日立数据系统NEC信息系统CA(中国)有限公司飞思卡尔宏正美国模拟器件公司中创信测瑞萨九五领讯通信公司MEI辽宁授权培训中心天玑科技摩卡软件威速科技通信有关单位中国产业网中国产业报协会中国光学光电子行业协会中国邮电咨询设计院中国电子商务协会中国电子企业协会中国电子视像行业协会中国电子商会中国无线电协会中国计算机用户协会中国计算机行业协会中国卫星通信广播电视用户协会中国软件行业协会中国信息产业商会中国光学光电子行业协会中国互联网协会中国通信标准化协会中国移动通信联合会中国通信企业协会中国通信工业协会中国通信学会中国通信标准化协会发改委国资委移动labs科技部地方监管北京天津河北内蒙古山西辽宁吉林黑龙江上海江苏浙江安徽福建江西山东广东海南广西湖北湖南河南重庆四川云南贵州西藏陕西甘肃新疆青海宁夏据介绍,在设计上,ZUI2.5支持全屏锁屏、个性化锁屏,并包含20+种个性化字体,并且正式上线ZUI主题中心,里面有海量主题供用户选择。

公示的随机抽取相关信息应当真实有效。全文描述:作为魅蓝定位高端系列的全新机型,魅蓝X一改以往入门学生机的形象。贾跃亭表示,乐视生态已经进入到收入快速增长阶段,今年全生态销售收入将突破500亿元。  与电竞区相配合的还有电竞主播室,隔音效果良好,主播可以随着比赛的进行时而摇旗呐喊,时而妙语连珠。

今年五月的 Google I/O 上,Google 正式向全球宣布 Kotlin-First 这一重要概念,Kotlin 将成为 Android 开发者的首选语言。

作者:张旸|2019-11-08 09:24

即将开播:5月20日,基于kubernetes打造企业级私有云实践

 一. 序

今年五月的 Google I/O 上,Google 正式向全球宣布 Kotlin-First 这一重要概念,Kotlin 将成为 Android 开发者的首选语言。

新语言有新特性,开发者还保持 Java 的编程习惯去写 Kotlin,也不是不行,但是总感觉差点意思。

最近公众号「谷歌开发者」连载了一个《实用 Kotlin 构建 Android 应用 | Kotlin 迁移指南》的系列文章,就举例了一些 Kotlin 编码的小技巧。

既然是一种指南性质的文章,自然在「多而广」的基础上,有意去省略一些细节,同时举例的场景,可能还有一些不恰当的地方。

这里我就来补齐这些细节,今天聊聊利用 Kotlin 的方法默认参数的特性,完成类似 Java 的方法重载的效果。完全解析这个特性的使用方式和原理,以及在使用过程中的一个深坑。

二. Kotlin 的简易方法重载

2.1 Kotlin 如何简化方法重载?

在 Java 中,我们可以在同一个类中,定义多个同名的方法,只需要保证每个方法具有不同的参数类型或参数个数,这就是 Java 的方法重载。

  1. class Hello { 
  2.     public static void hello() { 
  3.         System.out.println("Hello, world!"); 
  4.     } 
  5.  
  6.     public static void hello(String name) { 
  7.         System.out.println("Hello, "name +"!"); 
  8.     } 
  9.  
  10.     public static void hello(String nameint age) { 
  11.         if (age > 0) { 
  12.             System.out.println("Hello, "name + "(" +age +")!"); 
  13.         } else { 
  14.             System.out.println("Hello, "name +"!"); 
  15.         } 
  16.     } 

在这个例子中,我们定义了三个同名的 hello() 方法,具有不同的逻辑细节。

在 Kotlin 中,因为它支持在同一个方法里,通过 「?」标出可空参数,以及通过「=」给出参数的默认值。那这三个方法就可以在 Kotlin 中,被柔和成一个方法。

  1. object HelloDemo{ 
  2.     fun hello(name: String = "world", age: Int = 0) { 
  3.         if (age > 0) { 
  4.             System.out.println("Hello, ${name}(${age})!"); 
  5.         } else { 
  6.             System.out.println("Hello, ${name}!"); 
  7.         } 
  8.     } 

在 Kotlin 类中调用,和前面 Java 实现的效果是一致的。

  1. HelloDemo.hello() 
  2. HelloDemo.hello("承香墨影"
  3. HelloDemo.hello("承香墨影", 16) 

但是这个通过 Kotlin 方法参数默认值的特性申明的方法,在 Java 类中使用时,就有些区别了。因为 HelloDemo 类被声明为 object,所以在 Java 中需要使用 INSTANCE 来调用它的方法。

  1. HelloDemo.INSTANCE.hello("承香墨影",16); 

Kotlin 中调用 hello() 方法很方便,可以选择性的忽略参数,但是在 Java 中使用,必须全量的显式的去做参数赋值。

这就是使用了参数默认值的方法申明时,分别在 Kotlin 和 Java 中的使用方式,接下来我们看看原理。

2.2 Kotlin 方法参数指定默认值的原理

Kotlin 编写的代码,之所以可以在 Java 系的虚拟机中运行,主要是因为它在编译的过程中,会被编译成虚拟机可识别的 Java 字节码。所以我们通过两次转换的方式(Show Kotlin Bytecode + Decompile),就可以得到 Kotlin 生成的对应 Java 代码了。

  1. public final void hello(@NotNull String nameint age) { 
  2.   Intrinsics.checkParameterIsNotNull(name"name"); 
  3.   if (age > 0) { 
  4.      System.out.println("Hello, " + name + '(' + age + ")!"); 
  5.   } else { 
  6.      System.out.println("Hello, " + name + '!'); 
  7.   } 
  8.  
  9. / $FF: synthetic method 
  10. public static void hello$default(HelloDemo var0, String var1, int var2, int var3, Object var4) { 
  11.   if ((var3 & 1) != 0) { 
  12.      var1 = "world"
  13.   } 
  14.  
  15.   if ((var3 & 2) != 0) { 
  16.      var2 = 0; 
  17.   } 
  18.   var0.hello(var1, var2); 

在这里会生成一个 hello() 方法,同时还会有一个合成方法(synthetic method)hello$default,用来处理默认参数的问题。在 Kotlin 中调用 hello()方法,会在编译期间,有选择性的自动替换成 hello() 的合成方法去调用。

  1. / Kotlin 调用 
  2. HelloDemo.hello() 
  3. HelloDemo.hello("承香墨影"
  4. HelloDemo.hello("承香墨影", 16) 
  5.  
  6. / 编译后的 Java 代码 
  7. HelloDemo.hello$default(HelloDemo.INSTANCE, (String)null, 0, 3, (Object)null); 
  8. HelloDemo.hello$default(HelloDemo.INSTANCE, "承香墨影", 0, 2, (Object)null); 
  9. HelloDemo.INSTANCE.hello("承香墨影", 16); 

注意看示例的末尾,当使用 hello(name,age) 这个方法重载时,其实与 Java 中的调用,是一致的,这没什么好说的。

这就是 Kotlin 方法重载时,使用指定默认参数的方式,省去多个方法重载代码的原理。

理解原理后,发现它确实减少了我们编写的代码量,但是有没有场景,是我们就需要显式的存在这几个方法的重载的?自然是有的,例如自定义 View 时。

三. 自定义 View 遇上 Kotlin

3.1 构造方法也是方法

再回到前面提到的谷歌开发者的太阳城娱乐网最快登入《实用 Kotlin 构建 Android 应用 | Kotlin 迁移指南》系列文章中,举的例子其实很不恰当。

它这里的例子中,使用了 View 这个词,并且重载的几个方法,都是 View 的构造方法,我们在自定义 View 时,经常会和这三个方法打交道。

但是谷歌工程师在这里举的例子,很容易让人误会,实际上你如果在自定义 View 时,这么写一定是会报错的。

例如我们自定义一个 DemoView,它继承自 EditView。

  1. class DemoView( 
  2.         context: Context,  
  3.         attrs: AttributeSet? = null,  
  4.         defStyleAttr: Int = 0 
  5. ) : EditText(context, attrs, defStyleAttr) { 

这个自定义的 DemoView,当使用在 XML 布局中时,虽然编译不会出错,但是运行时,你会得到一个 NoSuchMethodException。

  1. Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet] 

什么问题呢?

在 LayoutInflater 创建控件时,找不到 DemoView(Context, AttributeSet) 这个重载方法,所以就报错了。

这其实很好理解,在前面说到 Kotlin 在使用带默认值的方法的原理,其实 Kotlin 最终会在编译后,额外生成一个合成方法,来处理方法的参数默认值的情况,它和 Java 的方法重载还不一样,用它生成的方法,确实不会存在多个方法的重载。

所以要明白,Kotlin 的方法指定默认参数与 Java 的方法重载,并不等价。只能说它们在某些场景下,特性是类似的。

3.2 使用 @JvmOverloads

那么回到这里的问题,在自定义 View 或者其他需要保留 Java 方法重载的场景下,怎么让 Kotlin 在编译时,真实的去生成对应的重载方法?

这里就需要用到 @JvmOverloads 了。

当 Kotlin 使用了默认值的方法,被增加了 @JvmOverloads 注解后,它的含义就是在编译时,保持并暴露出该方法的多个重载方法。

其实当我们自定义 View 时,AS 已经给了我们充分的提示,它会自动帮我们生成带 @JvmOverloads 构造方法。

AS 帮我们补全的代码如下:

  1. class DemoView @JvmOverloads constructor( 
  2.         context: Context,  
  3.         attrs: AttributeSet? = null,  
  4.         defStyleAttr: Int = 0 
  5. ) : AppCompatEditText(context, attrs, defStyleAttr) { 

再用「Kotlin Bytecode + Decompile」查看一下编译后的代码,来验证@JvmOverloads 的效果。

  1. @JvmOverloads 
  2. public DemoView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 
  3.   Intrinsics.checkParameterIsNotNull(context, "context"); 
  4.   super(context, attrs, defStyleAttr); 
  5.  
  6. / $FF: synthetic method 
  7. public DemoView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) { 
  8.   if ((var4 & 2) != 0) { 
  9.      var2 = (AttributeSet)null
  10.   } 
  11.  
  12.   if ((var4 & 4) != 0) { 
  13.      var3 = 0; 
  14.   } 
  15.  
  16.   this(var1, var2, var3); 
  17.  
  18. @JvmOverloads 
  19. public DemoView(@NotNull Context context, @Nullable AttributeSet attrs) { 
  20.   this(context, attrs, 0, 4, (DefaultConstructorMarker)null); 
  21.  
  22. @JvmOverloads 
  23. public DemoView(@NotNull Context context) { 
  24.   this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null); 

可以看到,@JvmOverloads 生效后,会按照我们的预期生成对应的重载方法,同时保留合成方法,完成在 Kotlin 中使用时,使用默认参数的需求。

是不是以为到这里就完了?并不是,如果你在自定义 View 时,完全按照 AS 给你的提示生成代码,虽然程序不会崩溃了,但你会得到一些未知的错误。

3.3 View 中别直接用 AS 生成代码

在自定义 View 时,依赖 AS 的提示生成代码,会遇到一些未知的错误。例如在本文的例子中,我们想要实现一个 EditView 的子类,用 AS 提示生成了代码。

会出现什么问题呢?

在 EditView 的场景下,你会发现焦点没有了,点击之后软键盘也不会自动弹出。

那为什么会出现这种问题?

原因就在 AS 在自动生成的代码时,对参数默认值的处理。

当在自定义 View 时,通过 AS 生成重载方法时,它对参数默认值的处理规则是这样的。

  1. 遇到对象,默认值为 null。
  2. 遇到基础数据类型,默认值为基本数据类型的默认值。例如 Int 就是 0,Boolean 就是 false。

而在这里的场景下, defStyleAttr 这个参数的类型为 Int,所以默认值会被赋值为 0,但是它并不是我们需要的。

在 Android 中,当 View 通过 XML 文件来布局使用时,会调用两个参数的构造方法 (Context context, AttributeSet attrs),而它内部会调用三个参数的构造方法,并传递一个默认的 defStyleAttr,注意它并不是 0。

既然找到了问题,就很好解决了。我们看看自定义 View 的父类中,两个参数的构造方法如何实现的,将 defStyleArrt 当默认值传递进去就好了。

那我们先看看 AppCompatEditText 中的实现。

  1. public AppCompatEditText(Context context,  
  2.                          AttributeSet attrs) { 
  3.     this(context, attrs, R.attr.editTextStyle); 

再修改 DemoView 中对 defStyleAttr 默认值的指定即可。

  1. class DemoView @JvmOverloads constructor( 
  2.         context: Context, 
  3.         attrs: AttributeSet? = null,  
  4.         defStyleAttr: Int = R.attr.editTextStyle 
  5. ) : AppCompatEditText(context, attrs, defStyleAttr) { 

到这里,自定义 View 中,使用默认参数的构造方法重载问题,也解决了。

在自定义 View 的场景下,当然也可以通过重写多个 constructor 方法来实现类似的效果,但是既然已经明白了它的原理,那就放心大胆的使用吧。

四. 小结时刻

到这里就弄清楚 Kotlin 中,使用默认参数来减少方法重载代码的使用技巧和原理,以及注意事项了。

弄清楚原理以及需要注意的点,可以帮助我们更好的使用 Kotlin 的特性。我们最后再总结一下本文的知识点:

Kotlin 可以通过对一个方法的参数,通过指定默认值的方式,来完成类似 Java 中「方法重载」的效果。

若想保留 Java 的重载方法,可以使用 @JvmOverloads 注解标记,它会自动生成该方法的全部重载方法。

在自定义 View 时,需要注意指定参数 defStyleAttr 的默认值,而不应该是 0。

【本文为51CTO专栏作者“张旸”的原创稿件,转载请通过微信公众号联系作者获取授权】

戳这里,看该作者更多好文

【编辑推荐】

  1. Java开发者薪资?程序员只能干到30岁?国外真的没有996?
  2. 你太菜了,竟然不知道Code Review?
  3. Go语言出现后,Java还是最佳选择吗?
  4. 曾想干掉Java的微软正式加入OpenJDK,意欲何为?
  5. 太阳城娱乐网最快登入Java服务,内存OOM问题如何快速定位?
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢
申博管理网网址 申博娱乐 申博管理网网址 申博太阳城游戏帐号 www.33sbc.com 太阳城注册开户登入
申博开户平台登入 菲律宾申博游戏登入 申博现金网 申博娱乐城直营网 申博在线娱乐登入网址 申博注册账户登入
旧版太阳城申博开户 太阳城官方直营网登入 菲律宾申博游戏登入 申博138登入 太阳城游戏官网 www.msc11.com