在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){
}
}
对其该类进行扩展:
imagepublic的方法可以正常访问,但凡用private修饰的属性或方法,无法在扩展函数中被调用。
Java调用扩展函数
回到Kotlin和Java交互性的问题,Java如何调用扩展函数的呢?这时候又要一波反编译:
public static final void extensionMethod(daqiJava $receiver,String string) {
}
扩展函数daqiJava#extensionMethod()被转换为一个相同名称的静态函数。函数第一个参数变成接受者类型,后面才是原函数的参数列表。也就是说Java调用扩展函数时,需要先传入对应的接收者对象,再传入该扩展函数的参数。
扩展是静态解析的
在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)
}
总结:
- 顶层函数和扩展函数都可以去掉以”Utils“结尾的静态方法容器类。
- 顶层函数和顶层属性提供全局的方法和属性,不需要任何对象实例进行调用。
- 扩展函数需要接受者对象实例来进行调用。
- 顶层属性是等价于私有的静态对象。
- 若想获取基本类型和String类型的公有静态对象,在顶层属性定义时,添加const关键字。
- 扩展可以毫无副作用给原有库的类增加属性和方法。
参考文献:
- 《Kotlin实战》
- Kotlin官网
网友评论