Kotlin知识归纳(一) —— 基础语法
Kotlin知识归纳(二) —— 让函数更好调用
Kotlin知识归纳(三) —— 顶层成员与扩展
在Java项目中,多多少少都存在以Utils结尾的Java类。其内部并无任何状态和实例函数,只有一堆与该名称相关的静态属性或静态方法。该类只是作为一种容器存储着静态属性和静态方法。
Kotlin认为,根本不需要创建这些无意义的类。可以直接将函数放在代码文件的顶层,不用附属于任何一个类。
在com.daqi包中的daqi.kt文件中定义顶层函数joinToString()
package com.daqi @JvmOverloads fun <T> joinToString(collection: Collection<T>, separator:String = ",", prefix:String = "", postfix:String = ""):String{ val result = StringBuilder(prefix) for ((index,element) in collection.withIndex()){ if (index > 0) result.append(separator) result.append(element) } result.append(postfix) return result.toString() } 复制代码
在Kotlin中,顶层函数属于包内成员,包内可以直接使用,包外只需要import该顶层函数,即可使用。
Kotlin和Java具有很强互操作性,如果让Java调用顶层函数该怎么调用呢?先看一下顶层函数编译成Java是什么样的:
public final class DaqiKt { @NotNull public static final String joinToString(Collection collection, String separator, String prefix,String postfix) { } } 复制代码
编译器将顶层函数所在的文件名daqi.kt作为类名DaqiKt,生成对应的类文件。该kt文件下的所有顶层函数都编译为这个类的静态函数。
so,如果在Java中调用Kotlin的顶层函数时,需要对其的文件名转换为对应的类名,再进行调用。
DaqiKt.joinToString(new ArrayList<>(),"",",",""); 复制代码
如果想规定kt文件转换为Java类时的类名,可以使用@file:JvmName()注解进行修改。将其放在文件的开头,位于包名之前:
@file:JvmName("StringUtils") package com.daqi fun <T> joinToString(...){ ... } 复制代码
就可以使用特定的类名在Java中调用对应的顶层函数。
StringUtils.joinToString(new ArrayList<>(),"",",",""); 复制代码
既然有顶层方法,应该也有顶层属性。和顶层函数一样,属性也可以放在文件的顶层,不附属与任何一个类。这种属性叫顶层属性。
@file:JvmName("StringUtils") package com.daqi val daqiField :String = "daqi" 复制代码
顶层属性和其他任意属性一样,都提供对应的访问器(val 变量提供getter,var 变量提供getter 和 setter)。也就是说,当Java访问该顶层属性时,通过访问器进行访问的。
StringUtils.getDaqiField(); 复制代码
通过反编译查看其转换为Java的样子:
@NotNull private static final String daqiField = "daqi"; @NotNull public static final String getDaqiField() { return daqiField; } 复制代码
顶层属性被定义为 私有的 静态对象,并配套了一个静态访问器方法。
如果需要定义 public 的静态变量,可以用 const关键字 修饰该变量。(仅适用于基础数据类型和String类型的属性)
在反编译的文件中可以看到,静态属性变成public,且没有了具体的静态访问器。
//Kotlin const val daqiField :String = "daqi" //Java public static final String daqiField = "daqi"; 复制代码
Kotlin可以在无需继承的情况下扩展一个类的功能,然后像内部函数一样直接通过对象进行调用。扩展函数这个新特可以很平滑与现有Java代码进行集成。
声明一个扩展函数,需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。而调用该扩展函数的对象,叫作 接收者对象 。接收者对象用this表示,this可有可无。
fun String.lastChar():Char{ return this.get(this.length - 1) } //调用扩展函数 "daqi".lastChar() 复制代码
在扩展函数中,可以直接访问被扩展类的方法和属性。但扩展函数不允许你打破对象的封装性,扩展函数不能访问 private 的成员。具体什么意思呢,先定义一个java类:
public class daqiJava { private String str = ""; public String name = ""; public void daqi(){ } private void daqi(String name){ } } 复制代码
对其该类进行扩展:
public的方法可以正常访问,但凡用 private 修饰的属性或方法,无法在扩展函数中被调用。
回到Kotlin和Java交互性的问题,Java如何调用扩展函数的呢?这时候又要一波反编译:
public static final void extensionMethod(daqiJava $receiver,String string) { } 复制代码
扩展函数daqiJava#extensionMethod()被转换为一个相同名称的 静态函数 。函数 第一个参数变成接受者类型 ,后面才是原函数的参数列表。
在JVM语言的多态中,被重写方法的调用依据其调用对象的实际类型进行调用。但扩展函数是静态分发的,即意味着扩展函数是由其所在表达式中的调用者的类型来决定的。
我们都知道Button是View的子类,同时为View和Button定义名为daqi的扩展函数。
val view:View = Button() view.daqi() 复制代码
此时调用的是View的扩展函数,即使它实质是一个Button对象。因为扩展函数所在的表达式中,view是View类型,而不是Button类型。
扩展函数与顶层函数类似,在Java层进行调用时,依据其所在的文件名作为类名,其作为静态函数,存储在该类中。(也支持@file:JvmName("")进行)
在扩展函数中,除了可以调用接受者类型的成员函数和成员属性外,还可以调用该类的扩展函数。
如果一个类的成员函数与扩展函数拥有相同的方法签名,成员函数会被优先使用。
扩展函数其实是静态函数,扩展函数不能被子类重写。但子类仍可以调用父类的扩展函数。
扩展属性不能有初始化器,它们的行为只能由 显式提供 的 getters/setters 定义。因为没有地方对它进行存储,不可能给现有的Java对象实例添加额外的属性。只是用 属性的语法 对接受者类型进行扩展。
声明一个扩展常量:
val String.lastChar:Char get() = get(length - 1) 复制代码
声明一个扩展变量:
var StringBuffer.lastChar:Char get() = get(length - 1) set(value:Char){ this.setCharAt(length - 1,value) } 复制代码