Kotlin从入门到精通 | 第四章 类型初步

本章节主要介绍Kotlin的类型定义和简单使用
20211117161519

类的定义

Kotlin类默认为public,类内无内容可省略

20211117104518

Kotlin类成员变量,方法同Java类似

20211117104610

Kotlin类默认带无参构造器,如果需要定义其他构造器,使用constructor关键字创建

20211117104737

也可以直接定义到类上
20211117104843

类的实例化

直观感受是,省略了new关键字,获得对象再也不需要new

Java

1
2
3
SimpleClass simpleClass = new SimpleClass(9);
System.out.println(simpleClass.x);
simpleClass.y();

Kotlin

1
2
3
val simpleClass = SimpleClass(9)
println(simpleClass.x)
simpleClass.y()

接口的定义

基本和Java一致

20211117105205

接口的实现

  • implements关键字换成了:
  • @override注解换成了override关键字

20211117105309

抽象类的定义

由于Kotlin的类默认是final,所以需要添加open关键字,使之可以被继承

20211117105425

类的继承

  • extends关键字换成了:,跟接口实现保持了一致
  • 需要调用被继承方的构造器(默认为无参构造器)

20211117105546

类的属性(成员变量)

  • var:默认带gettersetter
  • val:默认带getter

也可以自己定义gettersetter方法

1
2
3
4
5
6
7
8
9
10
class Person(age: Int, name: String) {
var age: Int = age
get() {
return field
}
set(value) {
field = value
}
var name: String = name
}

属性引用

1
2
3
4
5
6
7
8
fun main() {
val ageRef = Person::age // 未绑定 Receiver
val person = Person(18, "Bennyhuo")
val nameRef = person::name // 绑定 Receiver
// 属性引用
ageRef.set(person, 20)
nameRef.set("Andyhuo")
}

接口属性

接口可以定义属性,但是不能赋值

1
2
3
4
5
6
7
8
9
10
11
12
interface Guy {
var moneyLeft: Double
get() {
return 0.0
}
set(value) {
}

fun noMoney() {
println("no money called.")
}
}

接口属性没有backing field

我们尝试跟上面的类一样定义gettersetter方法

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Guy {
var moneyLeft: Double
get() {
return field
}
set(value) {
field = value
}

fun noMoney() {
println("no money called.")
}
}

将会获得编译器提示”Property in an interface cannot have a backing field”
20211117111756

扩展方法

这是Kotlin的大杀器,非常好用,一些工具类完全可以用扩展方法来替代,并且使用起来非常方便

可以为现存的类定义新的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
operator fun String.minus(right: Any?) = this.replaceFirst(right.toString(), "")

operator fun String.times(right: Int): String {
return (1..right).joinToString("") { this }
}

operator fun String.div(right: Any?): Int {
val right = right.toString()
return this.windowed(right.length, 1, transform = {
it == right
}) // [false, false, false, false ... false, true, ..., true]
.count { it }
}

fun main() {
val value = "HelloWorld World"

println(value - "World")
println(value * 2)

val star = "*"
println("*" * 20)

println(value / 3)
println(value / "l")
println(value / "ld")
}

空指针安全特性

空类型安全

注意:String和String?不是一个类型

1
2
3
4
var nonNull: String = "Hello"
nonNull = null // 编译器报错
var nullable: String? = "Hello"
nonNull = null // 编译通过

强转为不可空类型

1
2
var nullable: String? = "Hello"
val length = nullable!!.length

安全访问

1
2
3
var nullable: String? = "Hello"
nullable = null
val length = nullable?.length

?.的语法结构,我最早是在TypeScript里面看到的,基本逻辑就是,如果为空,则不会继续往下执行,一定程度上杜绝了NPE异常,而现在Kotlin把它引入了,非常棒

elvis运算符(?:)

1
2
3
var nullable: String? = "Hello"
nullable = null
val length: Int = nullable?.length ?: 0
  • nullable == null 时,length = 0
  • nullable != null 时,length = nullable!!.length

平台类型

Kotlin对于Java类库,是百分百支持的,但是Java里面没有String?这种类型,Kotlin怎么处理呢?是当成非空还是可空类型?
答案是:Kotlin编译器不判断,有用户自己来判断是否可空还是非空
比如在Java里面定义了一个Person

1
2
3
4
5
public class Person {
public String getTitle() {
// ...
}
}

那么在Kotlin里面调用时,person.title的类型是String!,这个String!就是平台类型,可以非空也可以可空

1
2
val person = Person()
val title: String! = person.title

智能类型转换

自动转换为子类型

1
2
3
4
val kotliner: Kotliner = Person("benny", 20) // Person是Kotliner的子类
if(kotliner is Person) {
println(kotliner.name) // 自动转换类型为 Person,不用向Java一样进行强转
}

可空转非空

1
2
3
4
5
var value: String? = null
value = "benny"
if(value != null) {
println(value.length) // 可空转非空,所以我们也可以说,非空类型是对应的可空类型的子类
}

不支持智能类型转换的场景

在线程不安全的调用下,智能类型转换失效

1
2
3
4
5
6
var tag: String? = null
fun main() {
if(tag != null) {
println(tag.length) // 虽然判断不为空,但是其他线程可能对它进行修改
}
}

类型的安全转换(as?)

1
2
val kotliner: Kotliner = ...
println(kotliner as? Person).name) // 安全转换,失败返回null

针对类型的智能转换,有几个建议

  • 尽可能使用val来声明不可变引用,让程序的含义更加清晰确定
  • 尽可能减少函数对外部变量的访问,也为函数式编程提供基础(函数式编程的精髓就是拒绝副作用)
  • 必要时创建局部变量指向外部变量,避免因它变化引起程序错误