一、类的基本概念
类的概念就是把数据和处理数据的代码封装成一个单一的实体。在 Java 中,数据存储在一个私有字段中,通过提供访问器方法:getter 和 setter 来访问或者修改数据
在 Java 中以下的示例代码是很常见的,Point 类包含很多重复的代码:通过构造函数把参数赋值给有着相同名称的字段,通过 getter 来获取属性值
public final class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public final int getX() {
return this.x;
}
public final int getY() {
return this.y;
}
}
使用 Kotlin 来声明 Point 类则只需要一行代码,两者完全等同
class Point(val x: Int, val y: Int)
Kotlin 也使用关键字 class 来声明类,类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成,类头与类体都是可选的,如果一个类没有类体,可以省略花括号。此外,Kotlin 中类默认是 publish(公有的) 且 final (不可继承)的
Kotlin 区分了主构造方法(在类体外部声明)和次构造方法(在类体内部声明),一个类可以有一个主构造函数和多个次构造函数,此外也允许在初始化代码块中添加额外的初始化逻辑
二、主构造函数
主构造函数是类头的一部分,跟在类名(和可选的类型参数)后,主构造函数的参数可以是可变的(var)或只读的(val)
class Point constructor(val x: Int, val y: Int) {
}
如果主构造函数没有任何注解或者可见性修饰符,可以省略 constructor 关键字
class Point(val x: Int, val y: Int) {
}
//如果不包含类体,则可以省略花括号
class Point(val x: Int, val y: Int)
如果构造函数有注解或可见性修饰符,则 constructor 关键字是必需的,并且这些修饰符在它前面
class Point public @Inject constructor(val x: Int, val y: Int) {
}
主构造函数不能包含任何的代码,初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中,初始化块包含了在类被创建时执行的代码,主构造函数的参数可以在初始化块中使用。如果需要的话,也可以在一个类中声明多个初始化语句块
此外,要创建一个类的实例不需要使用 Java 中的 new 关键字,像普通函数一样调用构造函数即可
class Point(val x: Int, val y: Int) {
init {
println("initializer blocks , x value is: $x , y value is: $y")
}
}
fun main(args: Array<String>) {
Point(1, 2) // initializer blocks , x value is: 1 , y value is: 2
}
主构造函数的参数也可以在类体内声明的属性初始化器中使用
class Point(val x: Int, val y: Int) {
private val localX = x + 1
private val localY = y + 1
init {
println("initializer blocks , x value is: $x , y value is: $y")
println("initializer blocks , localX value is: $localX , localY value is: $localY")
}
}
fun main(args: Array<String>) {
Point(1, 2)
//initializer blocks , x value is: 1 , y value is: 2
//initializer blocks , localX value is: 2 , localY value is: 3
}
三、次构造函数
类也可以声明包含前缀 constructor 的次构造函数
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数,可以直接委托或者委托到同一个类的另一个次构造函数以此进行间接委托,用 this 关键字来进行指定即可
class Point(val x: Int, val y: Int) {
private val localX = x + 1
private val localY = y + 1
init {
println("initializer blocks , x value is: $x , y value is: $y")
println("initializer blocks , localX value is: $localX , localY value is: $localY")
}
constructor(base: Int) : this(base + 1, base + 1) {
println("constructor(base: Int)")
}
constructor(base: Long) : this(base.toInt()) {
println("constructor(base: Long)")
}
}
fun main(args: Array<String>) {
Point(100)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
Point(100L)
//initializer blocks , x value is: 101 , y value is: 101
//initializer blocks , localX value is: 102 , localY value is: 102
//constructor(base: Int)
//constructor(base: Long)
}
初始化块中的代码实际上会成为主构造函数的一部分,委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行
即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块。如果一个非抽象类没有声明任何(主或次)构造函数,会默认生成一个不带参数的公有主构造函数
四、属性
在 Java 中,字段和其访问器的组合被称作属性。在 Kotlin 中,属性是头等的语言特性,完全替代了字段和访问器方法。在类中声明一个属性和声明一个变量一样是使用 val 和 var 关键字。val 变量只有一个 getter ,var 变量既有 getter 也有 setter
fun main(args: Array<String>) {
val user = User()
println(user.name)
user.age = 200
}
class User() {
val name: String = "leavesC"
var age: Int = 25
}
五、自定义访问器
访问器的默认实现逻辑很简单:创建一个存储值的字段,以及返回属性值的 getter 和更新属性值的 setter。如果需要的话,也可以自定义访问器
例如,以下就声明了三个带自定义访问器的属性
class Point(val x: Int, val y: Int) {
val isEquals1: Boolean
get() {
return x == y
}
val isEquals2
get() = x == y
var isEquals3 = false
get() = x > y
set(value) {
field = !value
}
}
如果仅需要改变一个访问器的可见性或者为其添加注解,那么可以定义访问器而不定义其实现
fun main(args: Array<String>) {
val point = Point(10, 10)
println(point.isEquals1)
//以下代码会报错
//point.isEquals1 = true
}
class Point(val x: Int, val y: Int) {
var isEquals1: Boolean = false
get() {
return x == y
}
private set
}
六、延迟初始化
一般地,非空类型的属性必须在构造函数中初始化,但像使用了 Dagger2 这种依赖注入框架的项目来说就十分的不方便了,为了应对这种情况,可以用 lateinit 修饰符来标记该属性,用于告诉编译器该属性会在稍后的时间被初始化
用 lateinit 修饰的属性或变量必须为非空类型,并且不能是原生类型
class Point(val x: Int, val y: Int)
class Example {
lateinit var point: Point
var point2: Point
constructor() {
point2 = Point(10, 20)
}
}
如果访问了一个未经过初始化的 lateinit 变量,则会抛出一个包含具体原因(该变量未初始化)的异常信息
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property point has not been initialized