一. 类
Kotlin中的类用class
关键字声明:
class Invoice{
}
类的声明包含类的名称、类头部(包括类的类型参数,主构造函数等)以及用大括号包围的类体。类头和类体都是可选项;如果类没有类体,大括号也可以省略。
class Empty
主构造函数
一个Kotlin类可以有一个主构造函数和一个或多个次构造函数。主构造函数是类头部的一部分,通常跟在类名(以及参数类型)后面
class Person constructor(firstName: String){
}
如果主构造函数没有任何注解或者可见性修饰符,那么关键字constructor
可以省略
class Person(firstName: String){
}
主构造函数不能有任何代码。初始化的代码可以放到初始化块(initializer block)
中,初始化块使用关键字init
作为前缀。 在类实例初始化期间,初始化块以及类的属性按照它们在类中的顺序初始化。
下面这段代码:
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
使用如下代码获取一个实例:
InitOrderDemo demo = InitOrderDemo("hello")
打印结果如下:
First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5
可以看到,属性
和初始化块
是按照顺序一个一个初始化的。
注意:主构造函数里的参数可以在初始化块中被使用,也可以用来初始化类属性。
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
事实上,Kotlin有简洁的语法在主构造函数中声明及初始化属性:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
主构造函数的属性可以声明为变量(var
)或只读变量(val
)
如果主构造函数有注解
或者可见性
修饰符,constructor
关键字就不能省略,并且修饰符要放在它前面:
class Customer public @Inject constructor(name: String) { //... }
次构造函数
次构造函数在类体中声明,也使用constructor
关键字,如下:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有主构造函数,那么次构造函数必须委托给主构造函数,可以直接委托或间接通过其它次构造函数委托。委托使用关键字this
,如下:
class Student(var name: String) {
constructor(name: String, age: Int) : this(name) {
}
constructor(name: String, age: Int, gender: Int) : this(name, age){
}
}
注意:初始化块中的代码实际上会成为主构造函数的一部分。而委托给主构造函数是次构造函数的第一个语句,因此初始化块中的代码将先于次构造函数被执行。即使类没有主构造函数,委托仍然会隐式发生,初始化块仍然会先于次构造函数执行。
如果一个非抽象类没有声明任何构造函数,它将产生一个没有参数的主构造函数,其可见性为public
。如果不想让某个类有public
构造函数,需要声明一个空的非默认可见性主构造函数,如下:
class DontCreateMe private constructor () {
}
注意:在JVM中,如果主构造函数的所有参数都有默认值,编译器将使用默认值生成一个额外的无参构造函数。
创建类的实例
创建类的实例前面的文章中已经提到过。和Java的区别是不用new
关键字。
val invoice = Invoice()
val customer = Customer("Joe Smith")
类的成员
类可以包含以下内容:
- 构造函数 和 初始化块
- 方法/函数
- 属性
- 嵌套类 和 内部类
- 对象声明
二. 继承
所有的Kotlin类都有一个共同的父类Any
,它是所有没声明父类的类的默认父类。和Java中的java.lang.Object
类不同的是,Any
类有且只有equals()
, hashCode()
和toString()
三个成员。
要声明一个父类,我们把父类类型放在子类头部的后面,用:
隔开
open class Base(p: Int)
class Derived(p: Int) : Base(p)
Kotlin类的
open
注解和Java中的final
正好相反,它意味着允许其它类来继承此类。Kotlin中所有的类都是默认为final的,这种设计契合《Effective Java》中所阐述的:要么设计好被继承,要么禁止继承。
如果子类有主构造函数,那么在继承时必须立即使用父类的主构造函数来初始化父类。
如果子类没有主构造函数,那么其每个次构造函数都必须使用super关键字来初始化父类,或者通过委托其它次构造函数来完成这件事。需要注意子类中的次构造函数可以任意实现父类中不同的构造函数。
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
重写方法
正如上面所提及的,Kotlin力求清晰明确。可以被重写的成员和重写的成员都要明确地标注。
open class Base {
open fun v() {} //可以被重写的方法用关键字 open 标注
fun nv() {}
}
class Derived() : Base() {
override fun v() {} //重写的方法用关键字 override 标注
}
如果方法 Derived.v()
没有override
注解,编译器将会报错。如果父类中的方法没有open
注解,比如上面的Base.nv()
方法,在其子类中定义同名方法将被视为非法。在一个final
类中,成员的open
注解将被视为无效。
一个被标注为 override
的成员默认是 open
的,它可以被其子类重写。如果想禁止 override
成员被重写,可以将其标注为final,如下:
open class AnotherDerived() : Base() {
final override fun v() {}
}
重写属性
重写属性和重写方法方式一样
open class Employee {
// Use "open" modifier to allow child classes to override this property
open val baseSalary: Double = 30000.0
}
class Programmer : Employee() {
// Use "override" modifier to override the property of base class
override val baseSalary: Double = 50000.0
}
也可以重写属性的get和set方法:
open class Person(open var name: String, open var age: Int){
}
class Student : Person("", 0){
override var age: Int = 10
get() = if(field > 100) 100 else field
set(value) {
field = if(value > 0) value else throw IllegalArgumentException("age can not be zero or negative")
}
}
fun main(args: Array<String>) {
val student = Student()
person.age = 111
println(student.age) //将打印出100
val student1 = Student()
student1.age = -1 //将抛出异常
println(student1.age)
}
上面这段代码是在子类Student
中重写父类Person
中age
属性的get
和set
方法,在get方法中限定获取到的age大小不超过100,在set方法中限定age不能为0或负值。
子类的初始化顺序
在子类的实例化过程中,初始化父类是其第一步,然后才执行子类的初始化逻辑
open class Base(val name: String) {
init { println("Initializing Base") }
open val size: Int = name.length.also { println("Initializing size in Base: $it") }
}
class Derived(name: String, val lastName: String) : Base(name.capitalize().also { println("Argument for Base: $it") }) {
init { println("Initializing Derived") }
override val size: Int = (super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}
调用代码var derivedClass = Derived("hello", "world")
初始化子类Derived
,打印出来的结果如下:
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10
调用父类中的属性和方法
在子类中可以通过关键字super
来调用父类中的属性和方法,如下:
open class Foo {
open fun f() { println("Foo.f()") }
open val x: Int get() = 1
}
class Bar : Foo() {
override fun f() {
super.f()
println("Bar.f()")
}
override val x: Int get() = super.x + 1
}
在子类的内部类中可以通过关键字[super + @ + 外部类名]
来调用外部类父类的方法和属性,如下:
class Bar : Foo() {
override fun f() { /* ... */ }
override val x: Int get() = 0
inner class Baz {
fun g() {
super@Bar.f() // 调用Foo类中的方法f()
println(super@Bar.x) // 调用Foo类中属性x的get方法
}
}
}
重写规则
在Kotlin中,如果一个类继承了多个父类,并且父类具有相同的成员,那么它必须重写这些成员并提供自己的实现方式。如果要调用某个父类中的成员,为了区分调用的成员来自哪个父类,可以使用关键字super<BaseClassName>
来调用。
open class A { //父类A
open fun f() { print("A") }
fun a() { print("a") }
}
interface B { //接口B
fun f() { print("B") } // 接口的成员默认是 open 的
fun b() { print("b") }
}
class C() : A(), B { //继承类A和接口B
// 编译器将要求必须重写方法f()
override fun f() {
super<A>.f() // 调用类A的方法A.f()
super<B>.f() // 调用接口B的方法B.f()
}
}
抽象类
抽象类使用abstract
来声明,在Kotlin中,抽象类或抽象方法不需要用open
来注释,因为它默认就是open
的。
可以把一个非抽象open方法重写为抽象方法:
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
当然,抽象方法只能写在抽象类中。
伴生对象
这个之后再详细介绍。
一个知识点:Kotlin中没有static
方法。