kotlin 中的类
// Kotlin中的一个类
class Bird {
val weight: Double = 500.0
val color: String = "blue"
val age: Int = 1
fun fly() {
} // 全局可见
}
把上述代码反编译成Java的版本,然后分析它们具体的差异:
public final class Bird {
private final double weight = 500.0D;
@NotNull
private final String color = "blue";
private final int age = 1;
@NotNull
public final double getWeight() {
return this.weight;
}
@NotNull
public final String getColor() {
return this.color;
}
public final int getAge() {
return this.age;
}
public final void fly() {
}
}
虽然Kotlin中类声明的语法非常近似Java,但也存在很多不同:
- 1)不可变属性成员。Kotlin用
val
在类中声明引用不可变的属性成员,是利用Java中的final
修饰符来实现的,使用var
声明的属性则反之引用可变。 - 2)属性默认值。因为Java的属性都有默认值,所以在声明时我们不需要指定默认值。而在Kotlin中,除非显式地声明延迟初始化,不然就必须指定默认值。
- 3)不同的可访问修饰符。Kotlin类中的成员默认是全局可见,而Java的默认可见域是包作用域,因此在Java版本中,我们必须采用
public
修饰才能达相同的效果。
可带有属性和默认方法的接口
众所周知,Java 8 引入了一个新特性——接口方法支持默认实现:
// Java 8 中的接口
public interface Flyer {
String kind();
default void fly() {
System.out.println("I can fly");
}
}
这使得我们在向接口中新增方法时,之前继承过该接口的类则可以不需要实现这个新方法。
接下来再来看看在Kotlin中如何声明⼀个接口:
// Kotlin 中的接口
interface Flyer {
val speed: Int
fun kind()
fun fly() {
println("I can fly")
}
}
与 java 类似,它还支持抽象属性,但是抽象属性不能有默认值,而在 Java 中接口中是可以有默认值的(Java 接口声明的成员变量默认是public static final
的),这一点与Java不同。
假如在kotlin给它强行设置默认值,则编译器会报错:
interface Flyer {
val height = 1000 //error Property initializers are not allowed in interfaces
}
Kotlin 提供了另外⼀种方式来实现这种效果,通过覆写成员的get()
方法:
interface Flyer {
val height
get() = 1000
}
若没有指定默认行为,则在实现该接口的类中必须对该属性进行初始化。
更简洁地创建对象
Kotlin 中并没有new
关键字,可以直接调用构造函数创建对象:
val bird = Bird()
在Java中构造方法重载存在两个缺点:
- 如果要支持任意参数组合来创建对象,那么需要实现的构造方法将会非常多。
- 每个构造方法中的代码会存在冗余
构造方法默认参数
要解决构造方法过多的问题,在 Kotlin 中你只需要给构造方法的参数指定默认值,从而避免不必要的方法重载。
class Bird(val weight: Double = 0.0, val age: Int = 0, val color: String = "blue")
// 可以省略 {}
调用:
val bird1 = Bird(color = "black")
val bird2 = Bird(weight = 1000.00, color = "black")
需要注意的是,由于参数默认值的存在,我们在创建类对象时,最好指定参数的名称,否则必须按照实际参数的顺序进行赋值。不然容易出现参数类型匹配不上的错误警告。
init 语句块
Kotlin引入了⼀种叫作init
语句块的语法,它属于上述构造方法的⼀部分,两者在表现形式上却是分离的。
当构造方法的参数没有val
或var
修饰的时候,可以在init
语句块被直接调用。
class Bird(
weight: Double = 0.00, // 参数名前没有 val
age: Int = 0,
color: String = "blue"
) {
val weight: Double
val age: Int
val color: String
init {
this.weight = weight // 构造方法参数可以在 init 语句块被调用
this.age = age
this.color = color
}
}
如果我们需要在初始化时进行其他的额外操作,那么我们就可以使用init
语句块来执行。比如:
class Bird(weight: Double, age: Int, color: String) {
init {
println("do some other things")
println("the weight is $weight")
}
}
其实它们还可以用于初始化类内部的属性成员的情况:
class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
val weight: Double = weight // 在初始化属性成员时调用 weight
val age: Int = age
val color: String = color
}
除此之外,我们并不能在其他地方使用。以下是一个错误的用法:
class Bird(weight: Double, age: Int, color: String) {
fun printWeight() {
print(weight) // Unresolved reference: weight
}
}
事实上,构造方法可以拥有多个init
块,它们会在对象被创建时按照类中从上到下的顺序先后执行。
看看以下代码的执行结果:
class Bird(weight: Double, age: Int, color: String) {
val weight: Double
val age: Int
val color: String
init {
this.weight = weight
println("The bird's weight is ${
this.weight}.")
this.age = age
println("The bird's age is ${
this.age}.")
}
init {
this.color = color
println("The bird's color is ${
this.color}.")
}
}
fun main(args: Array<String>) {
val bird = Bird(1000.0, 2, "blue")
}
运行结果:
The bird's weight is 1000.0.
The bird's age is 2.
The bird's color is bule.
可以发现,多个init
语句块有利于我们进一步对初始化的操作进行职能分离,这在复杂的业务开发(如Android)中显得特别有用。
注意:正常情况下,Kotlin规定类中的所有非抽象属性成员都必须在对象创建时被初始化值。
下面代码会运行报错:
class Bird(val weight: Double, val age: Int, val color: String) {
val sex: String
fun printSex() {
this.sex = if (this.color == "yellow") "male" else "female"
println(this.sex)
}
}
fun main(args: Array<String>) {
val bird = Bird(1000.0, 2, "blue")
bird.printSex()
}
运行结果
Error:(2, 1) Property must be initialized or be abstract
Error:(5, 8) Val cannot be reassigned
由于sex
必须被初始化值,上述的printSex
方法中,sex
会被视为二次赋值,这对val
声明的变量来说也是不允许的。
我们可以把sex
变成用var
声明并指定默认值,但是假如我们不想要默认值,可以声明为可空类型String?
,这样默认值就是null
。
然而实际上也许我们又不想让sex
具有可空性,而只是想稍后再进行赋值。
延迟初始化:by lazy 和 lateinit
更好的做法是让sex
能够延迟初始化,即它可以不用在类对象初始化的时候就必须有值。
在Kotlin中,我们主要使用 lateinit
和 by lazy
这两种语法来实现延迟初始化的效果。
class Bird(val weight: Double, val age: Int, val color: String) {
val sex: String by lazy {
if (color == "yellow") "male" else "female"
}
}
fun main() {
val bird = Bird(1000.0, 2,