本课程为极客时间上朱涛老师的[《Kotlin编程第一课》](time.geekbang.o# 面向对象
1.如何写出Kotlin风格的代码
由于Kotlin和Java都是基于JVM,而且二者的原理极其接近,这就导致了很多人会用Java的思维来写Kotlin的代码,那么Java和Kotlin之间的代码具备什么样的区别呢?
- 如何写出Kotlin的类
下面就是一个Kotlin的类
class Person(val name: String, var age: Int)
一行代码搞定,言简意赅;如果把上面的代码翻译成对应的Java代码
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 属性 name 没有 setter
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
对比一目了然;而且Kotlin中使用了val关键字修饰参数name,也就是说;这个类中name只有getter,没有setter;而age使用的是var,也就是说age同时拥有getter和setter
2.自定义属性getter
例如:这里提出一个需求,当前Person的年龄是否大于18;
从一个Java Coder的角度,肯定能写出下面的代码:
class Person(val name: String, var age: Int) {
fun isAdult(): Boolean {
return age >= 18
}
}
当然,还会很聪明觉得可以写得更简洁
class Person(val name: String, var age: Int) {
fun isAdult() = age >= 18
}
但其实,这里还有个更便捷的写法,就是用getter
class Person(val name: String, var age: Int) {
val isAdult
get() = age >= 18
// ↑
// 这就是isAdult属性的getter方法
}
当然,若是判断逻辑非常复杂,也可以用{} 修饰
class Person(val name: String, var age: Int) {
val isAdult: Boolean
get() {
// do something else
return age >= 18
}
}
但是这样的,类型推断就会失效,必须在参数值后加返回的参数类型(否者就是返回Void,这是后话不提)
-
答疑时刻:为何这里需要引入一个isAdult的属性了,属性在Java中会常占一些内存,会不会本末倒置了?
1.首先,从语法的角度上来说,是否为成年人,本来就是属于人身上的一种属性。我们在代码当中将其定义为属性,更符合直觉。而如果我们要给 Person 增加一个行为,比如 walk,那么这种情况下定义一个新的方法就是非常合适的。
2.其次,从实现层面来看,我们确实定义了一个新的属性 isAdult,但是 Kotlin 编译器能够分析出,我们这个属性实际是根据 age 来做逻辑判断的。在这种情况下,Kotlin 编译器可以在 JVM 层面,将其优化为一个方法。而且转义成Java,这种属性也是定义成方法的
综上所述就是:我们用语法上的一个属性,实现了一个方法;这样可读性好,而且资源消耗低
-
错误的写法(下面的写法会导致什么问题?)
class Person(val name: String, var age: Int) {
val isAdult = age >= 18
}
分析:它的问题就在于一次初始化后,age的参数被使用一次后就无法修改,这就违背了代码初衷(曾几何时写过这样的bug代码)
3.自定义属性setter
问题:如果在设置age的时候,我想要添加一些log日志记录,这又该怎么做?
回答:Java中肯定就直接在setAge的代码块中直接添加;但是Kotlin有更简洁的方法
class Person(val name: String) {
var age: Int = 0
// 这就是age属性的setter
// ↓
set(value: Int) {
log(value)
field = value
}
// 省略
}
上面就是setter,用来对属性赋值;其中关键字就是field = value甚至,如果要限制外部访问,还可以加private限制
class Person(val name: String) {
var age: Int = 0
private set(value: Int) {
log(value)
field = value
}
// 省略
}
4.抽象类与继承
- 抽象
关键字:abstract
abstract class Person(val name: String) {
abstract fun walk()
// 省略
}
- 继承
Kotlin的继承与Java有很大的区别;Kotlin 的继承和接口实现语法是一样的
下面是接口的实现实例:
interface Behavior {
// 接口内的可以有属性
val canWalk: Boolean
// 接口方法的默认实现
fun walk() {
if (canWalk) {
// do something
}
}
}
class Person(val name: String): Behavior {
// 重写接口的属性
override val canWalk: Boolean
get() = true
}
而且Kotlin还提供了接口默认实现方法,这个在Java一直到Java8才引入;
Kotlin 的类,默认是不允许继承的,除非这个类明确被 open 关键字修饰了。另外,对于被 open 修饰的普通类,它内部的方法和属性,默认也是不允许重写的,除非它们也被 open 修饰了:
ps:这个好处就在于符合设计模式的开闭原则----对修改关闭,对拓展开放
open class Person() {
val canWalk: Boolean = false
fun walk()
}
class Boy: Person() {
// 报错
override val canWalk: Boolean = true
// 报错
override fun walk() {
}
}
在继承的行为上面,Kotlin 和 Java 完全相反;Java 的继承是默认开放的,Kotlin 的继承是默认封闭的;这个在很多Java的技术类书籍类都抨击,因为Java的继承滥用会导致更多问题;而Kotlin就很好避开了这一点;
5.嵌套
Java中嵌套有两种:非静态内部类、静态内部类
- 静态内部类
class A {
val name: String = ""
fun foo() = 1
class B {
val a = name // 报错
val b = foo() // 报错
}
}
特点就是:内外部无法相互访问,国中国的关系
- 非静态内部类(关键字:inner)
class A {
val name: String = ""
fun foo() = 1
// 增加了一个关键字
// ↓
inner class B {
val a = name // 通过
val b = foo() // 通过
}
}
内部类可以访问外部类的方法,属性;
追问:为何要这样设计?
因为在Java中若是不加stattic关键字,内部类默认是非静态内部类;这样内部类会持有外部类的对象;这样就很容易导致外部类内存无法释放;导致内存泄漏
然而,Kotlin这样的设计,从设计角度就将默认犯错的风险完全抹掉了
6.Kotlin中的特殊类
6.1 数据类
数据类(Data Class),顾名思义,就是用于存放数据的类;它的格式如下:
// 数据类当中,最少要有一个属性
↓
data class Person(val name: String, val age: Int)
数据类的好处就在于会自动生成一些有用的方法。分别如下
- equals()
- hashCode()
- toString()
- componentN() 函数
- copy()
实例:
val tom = Person("Tom", 18)
val jack = Person("Jack", 19)
println(tom.equals(jack)) // 输出:false
println(tom.hashCode()) // 输出:对应的hash code
println(tom.toString()) // 输出:Person(name=Tom, age=18)
val (name, age) = tom // name=Tom, age=18
println("name is $name, age is $age .")
val mike = tom.copy(name = "Mike")
println(mike) // 输出:Person(name=Mike, age=18)
val (name, age) = tom”这行代码,其实是使用了数据类的解构声明;
copy方法实现了拷贝的同时,还能修改某个属性。(而且这个只是浅拷贝,深拷贝另有他法)
6.2密封类
密封类,是更强大的枚举类
- 枚举
enum class Human {
MAN, WOMAN
}
fun isMan(data: Human) = when(data) {
Human.MAN -> true
Human.WOMAN -> false
// 这里不需要else分支,编译器自动推导出逻辑已完备
}
所谓枚举,就是一组有限的数量的值;配合when一起,甚至可以推导出逻辑是否完备;但是它有一定局限性
下面的例子(有点难理解)
//== 这里是对比结构是否相等
println(Human.MAN == Human.MAN)
//=== 可以理解为指针对比,相当于对比内存地址
println(Human.MAN === Human.MAN)
输出
true
true
这里就出现了枚举的结构和引用都是相等的;如果出现以下场景,它就局限了:
我们要枚举的值来自于不同的对象引用呢?
回答:kotlin引入了新的关键字sealed,中文直译就是"密封",下面就是一个密封类的例子:
sealed class Result<out R> {
data class Success<out T>(val data: T, val message: String = "") : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
data class Loading(val time: Long = System.currentTimeMillis()) : Result<Nothing>()
}
这种代码在业务代码中回来处理Result回调会常用到;而且在处理回调时就可以用以下代码来写:
fun display(data: Result) = when(data) {
is Result.Success -> displaySuccessUI(data)
is Result.Error -> showErrorMsg(data)
is Result.Loading -> showLoading()
}