Kotlin笔记 02 - 面向对象
类
1 | class Person(val name: String, var age: Int) |
Kotlin 定义的类,在默认情况下是 public 的
自定义属性 getter
1 | class Person(val name: String, var age: Int) { |
所谓 getter,就是获取属性值的方法
如果 get() 方法内部的逻辑比较复杂,我们仍然可以像正常函数那样,带上花括号:
1 | class Person(val name: String, var age: Int) { |
在这种情况下,编译器的自动类型推导就会失效了,所以我们要为 isAdult 属性增加明确的类型:Boolean。
判断一个人是否为成年人,我们只需要判断 age 这个属性即可,为什么还要引入一个新的属性 isAdult 呢?
- 实际上,这里涉及到 Java 到 Kotlin 的一种思想转变。让我们来详细分解上面的问题:首先,从语法的角度上来说,是否为成年人,本来就是属于人身上的一种属性。我们在代码当中将其定义为属性,更符合直觉。而如果我们要给 Person 增加一个行为,比如 walk,那么这种情况下定义一个新的方法就是非常合适的。
- 其次,从实现层面来看,我们确实定义了一个新的属性 isAdult,但是 Kotlin 编译器能够分析出,我们这个属性实际是根据 age 来做逻辑判断的。在这种情况下,Kotlin 编译器可以在 JVM 层面,将其优化为一个方法。
- 通过以上两点,我们就成功在语法层面有了一个 isAdult 属性;但是在实现层面,isAdult 仍然还是个方法。这也就意味着,isAdult 本身不会占用内存,它的性能和我们用 Java 写的方法是一样的。而这在 Java 当中是无法实现的。
自定义属性 setter
所谓 setter,就是可以对属性赋值的方法
1 | class Person(val name: String) { |
有的时候,我们不希望属性的 set 方法在外部访问,那么我们可以给 set 方法加上可见性修饰符
1 | class Person(val name: String) { |
抽象类与继承
1 | abstract class Person(val name: String) { |
1 | // Java 的继承 |
没有用 open 修饰的话,它是无法被继承的。
1 | class Person() { |
Kotlin 的类,默认是不允许继承的,除非这个类明确被 open 关键字修饰了。另外,对于被 open 修饰的普通类,它内部的方法和属性,默认也是不允许重写的,除非它们也被 open 修饰了:
1 | open class Person() { |
Java 的继承是默认开放的,Kotlin 的继承是默认封闭的。Kotlin 的这个设计非常好,这样就不会出现 Java 中“继承被滥用”的情况。
嵌套
1 | class A { |
这种写法就对应了 Java 当中的静态内部类
1 | // 等价的Java代码如下: |
Kotlin 当中的普通嵌套类,它的本质是静态的。相应地,如果想在 Kotlin 当中定义一个普通的内部类,我们需要在嵌套类的前面加上 inner 关键字。
1 | class A { |
Kotlin 的这种设计非常巧妙。如果你熟悉 Java 开发,你会知道,Java 当中的嵌套类,如果没有 static 关键字的话,它就是一个内部类,这样的内部类是会持有外部类的引用的。可是,这样的设计在 Java 当中会非常容易出现内存泄漏!而大部分 Java 开发者之所以会犯这样的错误,往往只是因为忘记加“static”关键字了。这是一个 Java 开发者默认情况下就容易犯的错。
Kotlin 则反其道而行之,在默认情况下,嵌套类变成了静态内部类,而这种情况下的嵌套类是不会持有外部类引用的。只有当我们真正需要访问外部类成员的时候,我们才会加上 inner 关键字。这样一来,默认情况下,开发者是不会犯错的,只有手动加上 inner 关键字之后,才可能会出现内存泄漏,而当我们加上 inner 之后,其实往往也就能够意识到内存泄漏的风险了。
也就是说,Kotlin 这样的设计,就将默认犯错的风险完全抹掉了!
接口和实现
Kotlin 的接口,跟 Java 最大的差异就在于,接口的方法可以有默认实现,同时,它也可以有属性。
1 | interface Behavior { |
虽然在 Java 1.8 版本当中,接口也引入了类似的特性,但由于 Kotlin 是完全兼容 Java 1.6 版本的。因此为了实现这个特性,Kotlin 编译器在背后做了一些转换。这也就意味着,它是有一定局限性的(TODO 后面会讲)。
Kotlin 中的特殊类
数据类
用于存放数据的类
1 | // 数据类当中,最少要有一个属性 |
编译器会为数据类自动生成一些有用的方法。它们分别是:
- equals();
- hashCode();
- toString();
- componentN() 函数;
- copy()。
1 | val tom = Person("Tom", 18) |
“val (name, age) = tom”这行代码,其实是使用了数据类的解构声明。这种方式,可以让我们快速通过数据类来创建一连串的变量
密封类
密封类,是更强大的枚举类,需要使用 sealed 关键字
Android 开发当中,我们会经常使用密封类对数据进行封装。比如我们可以来看一个代码例子:
1 | sealed class Result<out R> { |
使用:
1 | fun display(data: Result) = when(data) { |
小结
Kotlin 语法在一些细节的良苦用心
- Kotlin 的类,默认是 public 的。
- Kotlin 的类继承语法、接口实现语法,是完全一样的。
- Kotlin 当中的类默认是对继承封闭的,类当中的成员和方法,默认也是无法被重写的。这样的设计就很好地避免了继承被滥用。
- Kotlin 接口可以有成员属性,还可以有默认实现。
- Kotlin 的嵌套类默认是静态的,这种设计可以防止我们无意中出现内存泄漏问题。
- Kotlin 独特的数据类,在语法简洁的同时,还给我们提供了丰富的功能。
- 密封类,作为枚举和对象的结合体,帮助我们很好地设计数据模型,支持 when 表达式完备性。