类和对象
1、类修饰符
介绍类之前,先来讲一下类的修饰符。Java中的类和方法默认都是open的,而在Kotlin中默认都是final。
1.1、类属性修饰符
如下:
abstract // 抽象类
final // 类不可继承,默认属性
open // 类可继承,类默认是final的
override // 重写父类或者接口中的成员
annotation // 注解类
enum // 枚举类
定义一个类如下,有的属性能被子类重写,有的被修饰为final:
open class Base {
//不可以被子类重写的属性
val age: Int = 25
//可以被子类重写的属性
open val value: String = "Q"
//不可被子类重写的方法
final fun base() {}
//可以被子类重写的方法
open fun other() {}
}
class Sub : Base() {
override val value: String = "B"
override fun other() {
println(value)
}
}
abstract修饰的抽象类通常包含一些没有实现并且必须在子类重写的抽象成员。抽象的属性始终是open的,则无需再用open修饰。如果实现的子类还是open的,那么接口默认是open的,可以显示的在override前面用final修饰,表示该方法不可再修改。
1.2、类访问权限修饰符
如下:
private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见
2、类
类可以包含:构造函数、代码块、成员变量、方法、内部类等。Kotlin的类默认是final的,如果可以继承需要用open关键字声明。
2.1、定义类
class关键字定义类。
class Item {
}
定义一个空类:
class Empty
2.2、构造器
①无参主构造器:如果不指定构造器,就是默认无参构造器
class Item constructor() {
}
constructor()可以省去不写,或者写在类内部。
class Item {
constructor() {}
}
②有参主构造器:参数用在init{...}块中初始化变量:
class Item constructor(num: Int) {
var num: Int = 0
init {
this.num = num
}
}
constructor关键字可以省略,简写成:
class Item(num: Int) {
var num: Int = 0
init {
this.num = num
}
}
不用init{...}块初始化,可以在类主体定义的属性初始化代码中使用主构造器传入的参数:
class Item constructor(num: Int) {
var num: Int = num
}
这个还可以把成员变量直接定义在主构造器constructor中:
class Item(@NotNull var num: Int) {
//只有主构造器可以这样写!!!
}
③次级构造器:不能写在与类class平级的地方,写在类体内部。
class Item {
private var num: Int = 0
constructor(num: Int) {
}
}
上面的例子由于主构造器为默认的无参构造器无需显示调用。
class Item constructor() {
constructor(@NotNull num: Int, age: Int) : this() {
//可以不调用默认的无参constructor(),也就是this()
}
}
但是对于有参的主构造器,每个次构造器都需要直接或者通过其它构造器间接调用。
class Item constructor(var num: Int) {
constructor(num: Int, age: Int) : this(num) {
}
}
否则编译失败,提示需要调用主构造器:
会不会自动尝试调用默认的无参constructor()呢?可以验证一下,加上一个次级无参构造器:
class Item constructor(var num: Int) {
constructor(num: Int, age: Int) {
}
constructor() {
}
}
再次运行,报错,提示没有调用主构造器:
需要让每个次级构造器都调用主构造器即可,或者调用其它的构造器间接调用主构造器。
//每个次级构造器都调用主构造器
class Item constructor(var num: Int) {
constructor(num: Int, age: Int) : this(num) {
}
constructor() : this(1) {
}
}
//调用其它的构造器间接调用主构造器
class Item constructor(var num: Int) {
constructor(num: Int, age: Int) : this() {
}
constructor() : this(1) {
}
}
2.3、类成员
类中定义成员变量和声明变量一样,变量的定义参考《Kotlin基本语法》。
class Item {
var num: Int = 1
}
非空成员变量必须在定义的时候初始化。不初始化成员变量会报错:
那么如果不想给变量赋初始值呢,有三个方法:
①使用lateinit描述关键字属性,表示变量延迟初始化
class Empty
class Item {
lateinit var num: Empty //延迟初始化该Empty类型的成员
}
Kotlin基本类型的属性上不允许使用 lateinit 修饰符。
②可以把类和成员都声明为abstract抽象的:
abstract class Item {
abstract var num: Int
}
③或者将成员声明为可空?并赋null:
class Item {
var num: Int? = null
}
2.4、seeter和getter方法
在该变量的定义下方,声明setter或者getter。用field表示该变量的引用,用来返回值或者赋值。
var num: Int = 0
get() = field
set(value) {
field = value
}
两种写法:
var num = 1
get() = field
//另一种写法
var num = 1
get() {
return field
}
例:
定义一个类Item,给成员num添加setter和getter:
class Item constructor(num: Int) {
var num: Int = 0
get() {
return field * 2
}
set(value) {
field = value + 2
}
}
这样,每次给num设置值都是传入的value + 2,每次get到的值都是num成员实际值的*2:
var item: Item = Item(0)
item.num = 12 //此时num值为12 + 2 = 14
println("${item.num}") //获取到的num值为14 * 2 = 28
注意:
①val修饰的成员变量不可以有set方法,因为它是只读的变量。
②不能在setter和getter中直接访问成员变量。
例:
class Item constructor(num: Int) {
var num: Int = 0
get() {
return field * 2
}
set(value) {
num = value + 2 //这里直接使用成员变量num
}
}
运行直接奔溃,具体机制还不太了解。应该是死循环,最终栈溢出了。
③setter和getter和普通函数一样可以加修饰符:private、public、protected
例:把setter方法改为private,外部就不能给num变量赋值,但是还是可以读取的。
var num: Int = 0
get() {
return field * 2
}
private set(value) {
field = value + 2
}
2.5、类成员方法
class Item {
var num: Int? = null
private fun cal() {
//
}
public fun printNum() {
println(num)
}
}
注:有些方法是隐藏的,不能定义。
真的佩服自己,总能踩到常人所不能及之坑。随手定义一个函数,和隐藏getter和setter的JVM签名冲突了。原来如此!
3、抽象类
用关键字abstract将类声明为抽象的
①抽象类中的方法无需实现
②声明为抽象的成员变量也可以不初始化
③抽象类或抽象成员无需再用open标注注解(类默认是final的,抽象类除外)
abstract class Animal {
abstract var num: Int
var kind: Int = 1
abstract fun whatAbout()
fun getInfo(): Int {
return kind
}
}
4、嵌套类
嵌套类有点像Java中的静态内部类,不过不需要用static关键字显示的声明。在Kotlin中定义的内部类没有显示修饰为static的内部类和Java中的内部静态类一样。所以可以直接通过外部类名访问内部类,无需通过外部类实例。
class Animal {
class Heart {
}
}
直接创建内部类对象实例:
var heart: Animal.Heart = Animal.Heart()
5、内部类
使用inner关键字声明一个内部类。内部类会带有一个对外部类的对象的引用,内部类可以访问外部类成员属性和成员函数。
class Animal {
inner class Heart {
}
}
通过外部类实例,间接访问内部类,再创建内部类对象实例:
var heart: Animal.Heart = Animal().Heart()
带有一个对外部类的对象的引用,使得内部类可以引用外部类的成员。使用this@OuterClass表示外部this引用。普通的this则代表内部类自身的引用。
class Animal {
inner class Heart {
var outerClass = this@Animal //外部类的引用
var innerClass = this //内部类自身的引用
}
}
6、密封类
密封类对定义的类继承做了严格的限制:所有的子类都必须嵌套在父类中。使用sealed关键字修饰密封类,并且该关键字还隐含了一个open修饰符,无需再额外的给sealed密封类添加open修饰符。
Kotlin 1.0中密封类所有的子类必须是嵌套的,并且子类不能创建为data类:
sealed class People {
class Man : People()
class Woman : People()
}
Kotlin 1.1中密封类允许在同一个文件的任何位置定义密封类的子类:
sealed class People {
class Man : People()
}
class Woman : People()
7、匿名内部类*
直接实现接口的方法,创建接口类型的对象实例。和Java开发一样,需要注意匿名内部类带来的内存泄漏隐患。
例:
先定义一个接口,方便后面直接实现该接口。
interface Function {
fun work()
}
直接创建该接口的匿名内部类,注意Kotlin创建匿名内部类的语法。
class Worker {
var other = 1
var one: Function = object : Function {
override fun work() {
println(other) //匿名内部类访问外部类成员
}
}
}
参考资料:
Kotlin 类和对象
Kotlin学习系列之:Kotlin的构造函数
kotlin之构造函数(constructor)