类
Kotlin 中定义类有些地方不同于 java。
不需要 public
一个类默认就是 public 的,所以不用显示地声明一个类为 public。
不需要花括号
如果一个类是空实现,可以不需要 {}
class MyClass
主构造方法
Kotlin 规定每个类允许有一个主构造方法和多个次要构造方法。
主构造方法是类名的一部分
主构造方法定义类名之后,用 constructor 关键字声明:
class MyClass contructor(username: String){
...
}
这种形式定义的构造方法就是主构造方法。它的声明属于类头的一部分。
constructor 可以省略
如果主构造方法没有任何注解修饰或者访问修饰符,则 constructor 可以省略,这种情况下更像是在定义一个函数而不是类,比如:
class MyClass(username:String){
...
}
主构造器中不包含代码
你没看错,kotlin 中主构造方法中没有代码(没有方法体)。那么初始化成员变量怎么办?答案是放在 init 代码块:
class MyClass(username: String){
init {
println("init MyClass")
username = ""
}
}
init 块中的代码会在主构造器被调用时调用,相当于将主构造方法的方法头和方法体分开定义,方法头放在类头后面,方法体放在类体内(init 块)。
构造参数可以用于 init 和属性初始化
如上面的代码中所示,在 init 块中,可以直接使用主构造方法中定义的参数。
此外,主构造方法参数也可以用于给属性赋初值:
class MyClass(username: String){
private val username = username
}
没有 new 关键字
当实例化对象时,直接调用构造方法即可,没有new 关键字(同 swift)。当你创建 MyClass 实例时,你会发现 init 中的代码被执行,控制台中会打印 init MyClass字样:
var myClass = MyClass("Chris")
默认构造方法(无参构造方法)
如果一个非抽象类,没有定义任何构造方法,编译器会自动提供一个默认的主构造方法(无参),访问级别 public。
此外,如果主构造方法的所有参数都提供了默认值,比如:
class Person(val username:String = "xxx")
那么编译器会自动创建一个无参构造方法,同时这个无参构造方法会使用所提供的默认值初始化。这样做的目的是为了和一些 Java 框架(比如 Spring)兼容(IoC框架通常使用无参构造方法创建实例)。
次要构造方法
次要构造方法不是必须的,如果有,那么可以有多个。次要构造方法是放在类体内定义的(而非类头)。次要构造方法没有方法名,只有关键字 constructor:
constructor(username: String, age: Int){
...
}
kotlin 中的次要构造方法必须直接或间接地调用主构造方法,这点同 swift 一样(参考 swift 中指定构造方法 的概念):
constructor(username: String, age: Int):this(username){
println(username+","+age)
this.age = age
}
注意this(username) 的写法,这里调用了主构造方法。注意,主构造方法先调用,然后才是次要构造方法体内的代码。
间接调用主构造方法
constructor(username: String, age: Int, address: String):this(username,age){
this.address = address
}
这个次要构造方法没有直接调用主构造方法,而是调用了另外一个次要构造方法,这样就相当于间接地调用了主构造方法。
私有的主构造方法
某些情况下,你可能想让一个类不能够从外部实例化,那么你可以让主构造函数变成 private(这时 constructor 不可省略) :
class Person private constructor(username: String)
在定义主构造方法的同时定义属性
Kotlin 提供一种简化的构造方法定义形式,在定义构造参数的同时就对属性进行初始化:
class Person (private val username: String, private val age:Int)
如你所见,类的成员变量或属性现在直接在主构造方法中定义,代替了构造参数。它们和构造参数的区别在于,作用域不同。在主构造方法定义的属性,其中整个类的作用域内有效,而构造参数仅在 init 中有效,或者在定义属性初始化值时有效。
属性
非 optional 属性必须初始化
这样声明一个属性:
private var username: String
IDE 提示属性必须被初始化,要么是一个 abstract 属性。如果你使用 abstract 修饰,则带来两个问题:一,abstract 和 pirvate 不兼容,你必须将 private 去掉,二,类必须是一个 abstract 类。
如果你要对 username 进行初始化,那么必须注意,对于非 optional 属性,你不能赋值为 null(或者你将它改成 optional 的)。
在 init 块中初始化
不需要在声明变量时就初始化,你也可以选择在 init 块中初始化。Kotlin 运行时会自动检测你在 init 中的初始化动作并判定为属性已经初始化:
init{
println(username)
this.username = username
}
这样,属性 username 上的警告就会消失。注意,init 块中username 引用的是构造参数的 username , this.username 引用的才是 username 属性。
get 方法
Kotlin 中 get 方法的定义是极度简化的:
val age
get() = 20
age 是一个只读属性所以用 val,同时其 get 方法只是一个返回整型的表达式,因此函数使用了简化的赋值形式。age 的类型可以通过 get 的返回值推断,因此无需明确指定。
自动提供的 get/set 方法
以下是一个读写属性的例子:
var address: String = address
在不明确定义 get/set 的情况下,kotlin 编译器自动生成对应的 get/set 方法。
内置变量 field 和 value
如果想在 get 方法中直接访问变量所对应的私有变量的值(backing field),可以用 field 关键字(类似于 O-C 中以 _ 开头的私有变量):
get(){
return field
}
在 set 方法中,也可以直接对 field 赋值:
set(value){
field = value
}
这里,value 并不是 kotlin 的关键字,它只是一个参数名,你可以修改为其它。这点不同于 swift ,swift 中通过 newValue 关键字来表示即将赋给属性的新值。
实际上,默认的 get/set 实现就是上面的样子,同时 IDE 会提示冗余的实现,意思是它们与默认实现相同,建议你删除。
修改可见性
可以仅仅修改 get/set 的访问级别,而不修改默认实现:
private var name:String
private set
private get
注意,get 方法的访问级别必须和属性的访问级别相同。
延迟初始化
与 java 不同,kotlin 的属性要求必须明确提供初值,或者修改为 optional。这带来了一些不便。为了解决这个问题,kotlin提供了延迟初始化的概念。
lateinit var name:String
lateinit 关键字告诉 kotlin,不检测该属性的初初始化情况,这个值会在实例化之后进行初始化。但是需要程序员遵守以下规则:
- 非空(非optional)类型的属性必须中构造方法中初始化。
- 如果是依赖注入或者UnitTest,可以使用 lateinit 关键字修饰该属性。但存在如下限制:
- 只能用于类体中声明的属性,不能用于主构造器中声明的属性。
- 该属性不能定义 set/get 方法。
- 只能用于非空(非 optional)属性,且类型不是原生数据类型(如 Int、Double)。
继承
类默认不可继承
在 java 中除了 Final 修饰的类外,都是可继承的。但是中 kotlin 中,默认所有类都 final 的、不可继承的。如果需要继承某个类,那么需要显式地将这个类标记为 open。
open class Parent(name:String, age: Int){}
必须调用父类的构造方法
要继承某个类,使用: 关键字(kotlin 中取消了 extend 关键字)。同时需要显式地调用父类构造方法。
父类的构造方法可能有参数,也可能没有参数。
如果没有参数很简单,这样就可以了:
class Child:Parent()
如果父类有参数,那么继承时必须传递参数给父类的构造方法。这有两种传递方法:利用主构造器传参,或者利用次要构造器传参。
利用主构造器传参
定义子类主构造器,利用子类主构造器中的参数,来调用父类的构造方法,。
class Child(name:String, age:Int): Parent(name, age){}
利用次要构造器传参
如果一个类没有主构造方法时怎么办?在次要构造器中调用。
如果一类没有主构造方法,同时他又要继承某个类,那么他必须在次要构造方法中调用父类构造方法:
class Child: Parent {
constructor(name:String, age:Int): super(name, age){
}
}
这里,:super(name, age) 表示调用父类的构造方法。
重写
必须使用 override
kotlin 中,重写时必须使用 override 关键字。这与 java 不同。
默认不可重写
父类中的方法默认是 final 的,不可以被重写。必须标记为 open 才能被子类重写。
这对于属性重写也是一样的。无论方法还是属性的重写,都必须中父类中明确标记为 open。
open 可以被 final 中断
父类的 open 方法或属性被子类重写之后仍然是 open 的,子类的子类仍然可以重写这个方法/属性,但是如果你想终止这种“可重写”的关系延续,那么你可以在子类中,将该方法修饰为 final,这样子类的子类将不能重写该方法/属性:
open class Child:Person(){
final override func name() {
}
}
在主构造器中 override
在主构造器中重写属性也是可以的:
class Child(override val name: String): Parent(){}
调用父类实现
通过 super 调用父类的实现。
override fun method(){
super.method()
...
}
重写 getter 方法
override val name: String
get() = super.name+" and child"
var 不能重写为 val
Kotlin 中,属性的可变性可以被子类重写。但是只能是将 val 属性重写为 var 属性(不可变->可变),但反过来不行。换句话说,只能将只读 属性重写为读写 属性。
|