Kotlin 面向对象(下)

878 阅读11分钟

1 扩展

Kotlin 的扩展是一个很独特的功能,Java本身并不支持扩展,Kotlin 为了让扩展能在 JVM 平台上运行,必须做一些独特的处理。Kotlin 支持扩展方法和扩展属性。

1.1 扩展方法

扩展方法的语法很简单,其实就是定义一个函数,只是函数名不要写成简单的函数,而是 要在函数名前增加被扩展的类(或接口)名 + 点号(.) 。 例如如下程序 。

open class Raw {
    fun test() {
        println("test方法")
    }
}

// 定义一个函数 ,函数名为 “被扩展类.方法”
fun Raw.info() {
    println("===扩展的 info 方法===")
}


class RawSub : Raw() {
    fun sub() {
        println("--sub()方法--")
    }
}


fun main(args: Array<String>) {
    var t = Raw()
    t.test()
    // 调用 Raw 对象扩展的方法
    t.info()
    // 创建 Raw 类的子类的实例
    var rs = RawSub()
    rs.test()
    rs.sub()
    // Raw 的子类的对象也可调用扩展的方法
    rs.info()
}

输出:

test方法
===扩展的 info 方法===
test方法
--sub()方法--
===扩展的 info 方法===

上面程序定义了 一个 Raw 类,该 Raw 类使用了 open 修饰符,这是为了示范给 Raw 类派 生子类。 如果只是对 Raw 类进行扩展,并不需要使用 open 修饰该类 。

接下来,为 Raw 类派生子类 RawSub,并增加了一个新方法:sub()。

接下来,为 Raw 类扩展一个 info() 方法,就像为 Raw 类增加了 info()方法一样,所有的 Raw 对象都可调用 info() 方法。不仅如此,Raw 类的子类的实例也可调用 info() 方法。

从上面代码可以看出,不管是 Raw 类的实例,还是 Raw 子类的实例,都可调用 Raw 类扩展的方法,就像真的为 Raw 类新增了一个方法一样。

扩展方法中的 this 与成员方法中的 this 一样,也代表调用该方法的对象。当然,扩展也可以为 Kotlin 系统提供的类增加方法。

例如,下面程序为系统提供的 List 集合扩展了一个 shuffle() 方法,该方法用于对集合元素进行随机排列。

// 该方法的实现思路是:先生成 List 集合所有索引的随机排列
// 然后根据随机排列的索引去 List 集合中取元素
fun List<Int>.shuffle(): List<Int> {
    val size = this.size
    // 下面的 indexArr 用于保存 List 集合的索引的随机排列
    var indexArr = Array<Int>(size) { 0 }
    var result: MutableList<Int> = mutableListOf()
    // 创建随机对象
    val rand = java.util.Random()
    var i = 0
    outer@ while (i < size) {
        // 生成随机数
        var r = rand.nextInt(size)
        for (j in 0 until i) {
            // 如果 r 和前面己生成的任意数字相等,则该随机数不可用,需要重新生成
            if (r == indexArr[j]) {
                continue@outer
            }
        }
        // 如果上面的循环结束了都没有执行 continue,则说明该 r 是一个不重复的随机数
        // 将随机数 r 存入 indexArr 数组中
        indexArr[i] = r
        // 根据随机的索引读取 List 集合元素,并将元素添加到 result 集合中
        result.add(this[r])
        i++
    }
    return result.toList()
}

fun main(args: Array<String>) {
    var nums = listOf(20, 30, 100, 34, 67)
    // 调用程序为 List 扩展的 shuffle ()方法
    println(nums.shuffle())
    println(nums.shuffle())
}

输出:

[20, 67, 30, 34, 100]
[67, 30, 100, 20, 34]

上面是为 List 扩展了 一个 shuftle() 方法。此外,上面程序扩展 List 时指定了它的泛型形参为 Int。 也就是说,该扩展仅对 List 有效,因此 List 就不能调用 shuftle() 方法。

实际上 Kotlin 完全支持直接进行泛型类扩展,只不过此时需要在函数上使用泛型,因此要使用泛型函数的语法 。 例如如下代码。

fun <T> List<T>.shuffle(): List<T>{
	val size = this.size
	var indexArr = Array<Int>(size, {0})
	var result: MutableList<T> = mutableListOf()
	val rand = java.util.Random()
	var i = 0
	outer@ while (i < size ) {
		var r = rand.nextInt(size)
		for (j in 0 until i) {
			if (r == indexArr[j]) {
				continue@outer
			}
		}
		indexArr[i] = r
		result.add(this[r])
		i++
	}
	return result.toList()
}
    // 程序就为 List<T>扩展了一个方法: shuffle(),
    // 这样 List<Int>、 List<String>等集合都可调用该方法
    var strList = listOf("Kotlin", "Java", "Go", "Erlang")
    println(strList.shuffle())
    println(strList.shuffle())

1.2 扩展的实现机制

Kotlin 的扩展并没有真正地修改所扩展的类,被扩展的类还是原来的类,没有任何改变。 Kotlin 扩展的本质就是定义了一个函数,当程序用对象调用扩展方法时, Kotlin 在编 译时会执行静态解析:就是根据调用对象、方法名找到扩展函数,转换为函数调用。

例如,上面程序中的如下代码:

strList. shuffle ()

Kotlin 在编译时这行代码按如下步骤执行。

  1. 检查 strList 的类型,发现其类型是 List。
  2. 检查 List<String> 类本身是否定义了 shuffle() 方法,如果该类本身包含该方法,则 Kotlin 无须进行处理,直接编译即可。
  3. 如果 List<String> 类本身不包含 shuffle() 方法,则 Kotlin 会查找程序是否为 List<String> 扩展了 shuffle() 方法,也就是查找系统中是否包含了名为 List<String>.shuffile()的函数(或泛型函数)定义,如果找到该函数,则 Kotlin 编译器会执行静态解析,它会将上面代码替换成执行 List<String>.shuffile() 函数。
  4. 如果 List<String> 不包含 shuffle() 方法,也找不到名为 List<String>.shuffile()的函数(或泛型函数)定义,编译器将报错。

由此可见, Kotlin 的扩展并没有真正改变被扩展的类,Kotlin 只是耍了一个小花招, 当 Kotlin 程序调用扩展方法时, Kotlin 编译器会将这行代码静态解析为调用函数,这样 JVM 就可接受了。

这意味着调用扩展方法是由其所在表达式的编译时类型决定的,而不是由它所在表达式的运行时类型决定的。

总结起来一句话:成员方法执行动态解析(由运行时类型决定);扩展方法 执行静态解析(由编译时类型决定)。

1.3 为可空类型扩展方法

Kotlin 还允许为可空类型(带“?”后缀的类型)扩展方法。由于可空类型允许接受 null 值,这样使得 null 值也可调用该扩展方法。从另一方面来看,由于会导致 null 值调用该扩展方法,因此程序需要在扩展方法中处理 null 值的情形。例如如下程序。

// 为可空类型扩展 equals 方法
fun Any?.equals(other: Any?): Boolean {
    if (this == null) {
        return if (other == null) true else false
    }
    return other.equals(other)
}


fun main(args: Array<String>) {
    var a = null
    println(a.equals(null)) // 输出 true
    println(a.equals("Kotlin")) // 输出 false
}

上面代码示范了为可空类型扩展 equals() 方法,这样 null 值也可调用 equals() 方法与 其他对象进行比较。

1.4 扩展属性

Kotlin 也允许扩展属性,但由于 Kotlin 的扩展并不能真正修改目标类,因此 Kotlin 扩展的 属性其实是通过添加 getter、 setter 方法实现的,没有幕后宇段。简单来说,扩展的属性只能是计算属性。

由于 Kotlin 的扩展属性只能是计算属性,因此对扩展属性有如下两个限制。

  • 扩展属性不能有初始值(没有存储属性值的幕后宇段) 。
  • 不能用 field 关键字显式访问幕后宇段。
  • 扩展只读属性必须提供 getter方法;扩展读写属性必须提供 getter、 setter 方法。

如下程序示范了为 Kotlin类添加扩展属性。

/**
 * Description:<br>
 * ��վ: <a href="http://www.crazyit.org">���Java����</a><br>
 * Copyright (C), 2001-2018, Yeeku.H.Lee<br>
 * This program is protected by copyright laws.<br>
 * Program Name:<br>
 * Date:<br>
 * @author Yeeku.H.Lee kongyeeku@163.com<br>
 * @version 1.0<br>
 */
class User(var first: String, var last: String) {
}

// 为 User 扩展读写属性
var User.fullName: String
    get() = "${first}.${last}"
    set(value) {
        println("ִ执行扩展属性 fullName 的 setter 方法")
        // value 字符串中不包含.或包含几个.都不行
        if ("." !in value || value.indexOf(".") != value.lastIndexOf(".")) {
            println("您输入的 fullName 不合法")
        } else {
            var tokens = value.split(".")
            first = tokens[0]
            last = tokens[1]
        }
    }

fun main(args: Array<String>) {
    var user = User("罗伯特", "巴乔")
    println(user.fullName)
    user.fullName = "加布里埃尔.巴蒂斯图塔"
    println(user.first)
    println(user.last)
}

输出:

罗伯特.巴乔
ִ执行扩展属性 fullName 的 setter 方法
加布里埃尔
巴蒂斯图塔

上面程序中粗体字代码为 User 类扩展了一个 fullName 读写属性,扩展该属性时重写了 getter、 setter 方法。

此外,由于扩展属性的本质就是 getter、 setter 方法,因此也可用泛型函数的形式来定义扩 展属性。例如, Kotlin 为 List 扩展的 lastlndex 属性的代码如下:

val <T> List<T>.lastIndex: Int get() = size - 1

上面的扩展属性在 var 和 List 之间定义了泛型形参,这是典型的泛型函数的语法。

1.5 以成员方式定义扩展

Kotlin 还支持以类成员的方式定义扩展,就像为类定义方法、属性那样定义扩展。

对于以类成员方式定义的扩展,一方面它属于被扩展的类,因此在扩展方法(属性)中可 直接调用被扩展类的成员(省略 this 前缀);另一方面它又位于定义它所在类的类体中 , 因此在扩展方法(属性)中又可直接调用它所在类的成员(省略 this 前缀)。

class A {
    fun bar() = println("A 的 bar 方法")
}

class B {
    fun baz() = println("B 的 baz 方法")
    // 以成员方式为A扩展 foo() 方法
    fun A.foo() {
        // 在该方法内既可调用类 A 的成员,也可调用类 B 的成员
        bar() // A 对象为隐式调用者
        baz() // B 对象为隐式调用者
    }

    fun test(target: A) {
        // 调用 A 对象的成员方法
        target.bar()
        // 调用 A 对象的扩展方法
        target.foo()
    }
}

fun main(args: Array<String>) {
    var b = B()
    b.test(A())
}

输出:

A 的 bar 方法
A 的 bar 方法
B 的 baz 方法

上面程序在类 B 中为类 A 扩展了一个 foo() 方法。由于该 foo() 方法一方面属于类 A,另一方面又定义在类 B 中,因此在 foo() 方法内既可直接调用类 A 的成员,也可直接调用类 B 的成员,且都不需要使用 this 前缀。

这样又会产生 一个新的问题。如果被扩展类和扩展定义所在的类包含了同名的方法,此时就会导致:程序在扩展方法中调用两个类都包含的方法时,系统总是优先调用被扩展类的方法 。 为了让系统调用扩展定义所在类的方法,必须使用带标签的 this 进行限定。

class Tiger {
    fun foo() {
        println("Tiger 类的 foo () 方法")
    }
}

class Bear {
    fun foo() {
        println("Bear 类的 foo () 方法")
    }

    // 以成员方式为 Tiger 类扩展 test () 方法
    fun Tiger.test() {
        foo()
        // 使用带标签的 this 指定调用 Bear 的 foo ()方法
        this@Bear.foo()
    }

    fun info(tiger: Tiger) {
        tiger.test()
    }
}

fun main(args: Array<String>) {
    val b = Bear()
    b.info(Tiger())
}

输出:

Tiger 类的 foo () 方法
Bear 类的 foo () 方法

上面程序在 Bear类 中为 Tiger 类扩展了一个 test() 方法,且 Bear 类和 Tiger 类都包含 foo() 方法,因此如果程序在 test() 扩展方法中调用 foo() 方法,系统总是优先调用 Tiger 类的 foo() 方法。

为了在 test()扩展方法中调用 Bear定义的 foo()方法,就需要使用 this@Bear前缀。

Kotlin 的 this 比 Java 的 this 更强大, Kotlin 的 this 支持用“@类名”形式,这种形式限制了该 this 代表哪个类的对象 。

1.6 带接收者的匿名函数

1.7 何时使用扩展

2 final和open修饰符

2.1 可执行“宏替换”的常量

2.2 final属性

2.3 final方法

2.4 final类

2.5 不可变类

3 抽象类

3.1 抽象成员和抽象类

3.2 抽象类的作用

3.3 密封类

4 接口

4.1 接口的定义

4.2 接口的继承

4.3 使用接口

4.4 接口和抽象类

5 嵌套类和内部类

5.1 内部类

5.2 嵌套类

5.3 在外部类以外使用内部类

5.4 在外部类以外使用嵌套类

5.5 局部嵌套类

5.6 匿名内部类

6 对象表达式和对象声明

6.1 对象表达式

6.2 对象声明和单例模式

6.3 伴生对象和静态成员

6.4 伴生对象的扩展

7 枚举类

7.1 枚举类入门

7.2 枚举类的属性、方法和构造器

7.3 实现接口的枚举类

7.4 包含抽象方法的抽象枚举类

8 类委托和属性委托

8.1 类委托

8.2 属性委托

8.3 延迟属性

8.4 属性监听

8.5 使用Map存储属性值

8.6 局部属性委托

8.7 委托工厂