阅读 592

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()
复制代码

扩展函数的可见性

        在扩展函数中,可以直接访问被扩展类的方法和属性。但扩展函数不允许你打破对象的封装性,扩展函数不能访问privateprotected的成员。具体什么意思呢,先定义一个java类:

public class daqiJava {
    private String str = "";
    public String name = "";

    public void daqi(){
    }

    private void daqi(String name){
    }
}
复制代码

对其该类进行扩展:

        public的方法可以正常访问,但凡用privateprotected修饰的属性或方法,无法在扩展函数中被调用。

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关键字。
  • 扩展可以毫无副作用给原有库的类增加属性和方法。

参考资料:

android Kotlin系列:

Kotlin知识归纳(一) —— 基础语法

Kotlin知识归纳(二) —— 让函数更好调用

Kotlin知识归纳(三) —— 顶层成员与扩展

Kotlin知识归纳(四) —— 接口和类

Kotlin知识归纳(五) —— Lambda

Kotlin知识归纳(六) —— 类型系统

Kotlin知识归纳(七) —— 集合

Kotlin知识归纳(八) —— 序列

Kotlin知识归纳(九) —— 约定

Kotlin知识归纳(十) —— 委托

Kotlin知识归纳(十一) —— 高阶函数

Kotlin知识归纳(十二) —— 泛型

Kotlin知识归纳(十三) —— 注解

Kotlin知识归纳(十四) —— 反射

关注下面的标签,发现更多相似文章
评论