Kotlin 教程 在线

2296Kotlin 扩展

伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。

对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:

  • (1)类内扩展的伴随对象函数和类外扩展的伴随对象可以同名,它们是两个独立的函数,互不影响;
  • (2)当类内扩展的伴随对象函数和类外扩展的伴随对象同名时,类内的其它函数优先引用类内扩展的伴随对象函数,即对于类内其它成员函数来说,类内扩展屏蔽类外扩展;
  • (3)类内扩展的伴随对象函数只能被类内的函数引用,不能被类外的函数和伴随对象内的函数引用;
  • (4)类外扩展的伴随对象函数可以被伴随对象内的函数引用,;

例如以下代码:

class MyClass {
    companion object {
        val myClassField1: Int = 1
        var myClassField2 = "this is myClassField2"
        fun companionFun1() {
            println("this is 1st companion function.")
            foo()
        }
        fun companionFun2() {
            println("this is 2st companion function.")
            companionFun1()
        }
    }
    fun MyClass.Companion.foo() {
        println("伴随对象的扩展函数(内部)")
    }
    fun test2() {
        MyClass.foo()
    }
    init {
        test2()
    }
}
val MyClass.Companion.no: Int
    get() = 10
fun MyClass.Companion.foo() {
    println("foo 伴随对象外部扩展函数")
}
fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    println("field1:${MyClass.myClassField1}")
    println("field2:${MyClass.myClassField2}")
    MyClass.foo()
    MyClass.companionFun2()
}

运行结果:

no:10
field1:1
field2:this is myClassField2
foo 伴随对象外部扩展函数
this is 2st companion function.
this is 1st companion function.
foo 伴随对象外部扩展函数

2295Kotlin 继承

几点补充:

1、子类继承父类时,不能有跟父类同名的变量,除非父类中该变量为 private,或者父类中该变量为 open 并且子类用 override 关键字重写:

open class Person(var name: String, var age: Int) {    
    open var sex: String = "unknow"    
    init {        
        println("基类初始化")    
    }
}
// 子类的主构造方法的 name 前边也加了 var,这是不允许的,会报'name' hides member of supertype and needs 'override' modifier
class Student(var name: String, age: Int, var no: String, var score: Int) : Person(name, age) {
    override var sex: String = "male"
}

如上代码片段中,子类 Student 主构造方法的第一个字段 name 前边加 var 关键字会报错。

2、子类不一定要调用父类和接口中共同拥有的同名的方法

引用文章中的话:“C 继承自 a() 或 b(), C 不仅可以从 A 或则 B 中继承函数,而且 C 可以继承 A()、B() 中共有的函数。此时该函数在中只有一个实现,为了消除歧义,该函数必须调用A()和B()中该函数的实现,并提供自己的实现。”

我试过了,不是必须调用 A() 和 B() 中该函数的实现,代码如下:

open class A {
    open fun f() {
        println("A")
    }
    fun a() {
        println("a")
    }
}
interface B {
    fun f() {
        println("B")
    }
    fun b() {
        println("b")
    }
}
class C : A(), B {
    override fun f() {
        // super<A>.f()
        // super<B>.f()
        println("C")
    }
}

如上代码片段,注释掉 super<A>.f()super<B>.f() 也不报错。

3、关于子类不能用 val 重写父类中的 var,我的猜测是:子类重写父类属性,也就相当于必须重写该属性的 getter 和 setter 方法,而子类中的 val 不能有 setter 方法,所以无法“覆盖”父类中 var 的 setter 方法,相当于缩小了父类中相应属性的使用范围,是不允许的,就像我们不能把父类中一个 public 方法重写成 private 方法一样。

4、如果一个变量想要在定义的时候被初始化,则该变量必须拥有 backing field 字段,该变量的默认 getter 和 setter 方法中是有定义 field 字段的,但是如果我们重写了这个变量的 getter 方法和 setter 方法,并且在 getter 方法和 setter 方法中都没有出现过 filed 这个关键字,则编译器会报错,提示 Initializer is not allowed here because this property has no backing field,除非显式写出 filed 关键字(哪怕它什么都不干,只要放在那里就可以了,我理解是出现一次就相当于“声明”过了,就可以用了,而在定义变量的时候初始化是要求 field 被“声明”过才可以):

var aaa: Int = 0
get() {
    field // 这里必须出现一下field关键字,否则 var aaa: Int = 0 会报错,除非你去掉 = 0这部分,不要给它赋初始化值
    return 0
}
set(value) {}

2294Kotlin 类和对象

关于 field 我也分享一下理解。

// 还是 JAVA 代码
int no = 100;
private int _no = 100;
public int getNo() {
    return _no;// 
}

public void setNo(int value) {
    if (value < 10) {
        _no = value;// Kotlin中出现“no =”这样的字样,直接被编译器理解成“这里要调用setter方法”
    } else {
        _no = -1;// 在setter方法中调用setter方法,这是不正确的
    }
}

上面这样就没有问题, 而 field 就相当于编译器给你提供两一个隐式私有变量。

2293Kotlin 类和对象

补充几点:

1、field 关键字

这个问题对 Java 开发者来说十分难以理解,网上有很多人讨论这个问题,但大多数都是互相抄,说不出个所以然来,要说还是老外对这个问题的理解比较透彻,可以参考这个帖子:https://stackoverflow.com/questions/43220140/whats-kotlin-backing-field-for/43220314

其中最关键的一句:Remember in kotlin whenever you write foo.bar = value it will be translated into a setter call instead of a PUTFIELD.

也就是说,在 Kotlin 中,任何时候当你写出“一个变量后边加等于号”这种形式的时候,比如我们定义 var no: Int 变量,当你写出 no = ... 这种形式的时候,这个等于号都会被编译器翻译成调用 setter 方法;而同样,在任何位置引用变量时,只要出现 no 变量的地方都会被编译器翻译成 getter 方法。那么问题就来了,当你在 setter 方法内部写出 no = ... 时,相当于在 setter 方法中调用 setter 方法,形成递归,进而形成死循环,例如文中的例子:

var no: Int = 100
    get() = field                // 后端变量
    set(value) {
        if (value < 10) {       // 如果传入的值小于 10 返回该值
            field = value
        } else {
            field = -1         // 如果传入的值大于等于 10 返回 -1
        }
    }

这段代码按以上这种写法是正确的,因为使用了 field 关键字,但是如果不用 field 关键字会怎么样呢?例如:

var no: Int = 100
    get() = no
    set(value) {
        if (value < 10) {       // 如果传入的值小于 10 返回该值
            no = value
        } else {
            no = -1         // 如果传入的值大于等于 10 返回 -1
        }
    }

注意这里我们使用的 Java 的思维写了 gettersetter 方法,那么这时,如果将这段代码翻译成 Java 代码会是怎么样呢?如下:

int no = 100;
public int getNo() {
    return getNo();// Kotlin中的get() = no语句中出来了变量no,直接被编译器理解成“调用getter方法”
}

public void setNo(int value) {
    if (value < 10) {
        setNo(value);// Kotlin中出现“no =”这样的字样,直接被编译器理解成“这里要调用setter方法”
    } else {
        setNo(-1);// 在setter方法中调用setter方法,这是不正确的
    }
}

翻译成 Java 代码之后就很直观了,在 getter 方法和 setter 方法中都形成了递归调用,显然是不正确的,最终程序会出现内存溢出而异常终止。

2、嵌套类和内部类在使用时的区别

(1)创建对象的区别

var demo = Outter.Nested()// 嵌套类,Outter后边没有括号
var demo = Outter().Inner();// 内部类,Outter后边有括号

也就是说,要想构造内部类的对象,必须先构造外部类的对象,而嵌套类则不需要;

(2)引用外部类的成员变量的方式不同

先来看嵌套类:

class Outer {                  // 外部类
    private val bar: Int = 1
    class Nested {             // 嵌套类
        var ot: Outer = Outer()
        println(ot.bar) // 嵌套类可以引用外部类私有变量,但要先创建外部类的实例,不能直接引用
        fun foo() = 2
    }
}

再来看一下内部类(引用文章中代码):

class Outer {
    private val bar: Int = 1
    var v = "成员属性"
    /**嵌套内部类**/
    inner class Inner {
        fun foo() = bar  // 访问外部类成员
        fun innerTest() {
            var o = this@Outer //获取外部类的成员变量
            println("内部类可以引用外部类的成员,例如:" + o.v)
        }
    }
}

可以看来内部类可以直接通过 this@ 外部类名 的形式引用外部类的成员变量,不需要创建外部类对象;

3、匿名内部类的实现

引用文章中的代码

fun main(args: Array<String>) {
    var test = Test()

    /**
     * 采用对象表达式来创建接口对象,即匿名内部类的实例。
     */
    test.setInterFace(object : TestInterFace {
        override fun test() {
            println("对象表达式创建匿名内部类的实例")
        }
    })
}

特别注意这里的 object : TestInterFace,这个 object 是 Kotlin 的关键字,要实现匿名内部类,就必须使用 object 关键字,不能随意替换其它单词,切记切记。

2292Kotlin 循环控制

正常循环:

for (i in 1..4) print(i) // 打印结果为: "1234"

如果你需要按反序遍历整数可以使用标准库中的 downTo() 函数:

for (i in 4 downTo 1) print(i) // 打印结果为: "4321"
也支持指定步长:
for (i in 1..4 step 2) print(i) // 打印结果为: "13"

for (i in 4 downTo 1 step 2) print(i) // 打印结果为: "42"

如果循环中不要最后一个范围区间的值可以使用 until 函数:

for (i in 1 until 10) { // i in [1, 10), 不包含 10
     println(i)
}