类的声明
Kotlin 中使用关键字 class 声明类,类声明由类名、类头(指定其类型参数、主构造方法等)以及由花括号包围的类体构成,类头与类体都是可选的。如果一个类没有类体,可以省略花括号。
//一个类的完整声明
class Person(name: String) {
val name = name
}
//省略类体,在构造方法中声明属性,添加默认参数
class Person(val name:string = "xiaoming")
构造方法
在 Kotlin 中的一个类可以有一个主构造方法和一个或多个次构造方法。主构造方法是类头的一部分:它跟在类名(和可选的类型参数)后。
Kotlin引入了constructor和init两个新的关键字,constructor用于一个主构造方法或者次构造方法的声明,init用来引入一个初始化语句块。init主要用来和主构造方法一起使用,因为主构造方法不能包括初始化代码。如果属性用相应的构造方法参数初始化,可以通过把val或var关键字加在参数钱的方式简化。所以以下三种声明方式都是相同的:
class User constructor(_nickname: String) {
val nackname: String
init {
nackname = _nickname
}
}
class User constructor(_nickname: String) {
val nackname = _nickname
}
class User(val nickname:String)
次构造方法
类也可以通过constructor声明更多的次构造方法。如果类有一个主构造函数,每个次构造函数需要委托给主构造函数,可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
请注意,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块:
/* 输出:
* Init block
* Constructor
*/
fun main(args: Array<String>) {
Constructors(5)
}
class Constructors(str :String) {
init {
println("Init block")
}
constructor(i: Int):this(i.toString()){
println("Constructor")
}
}
属性
在Java中,字段和其访问器的组合常常被称为属性,而在Kotlin中,属性是头等的语言特性,完全代替了字段和访问器方法。在类中声明一个属性和声明一个变量一样:属性可以用关键字var 声明为可变的,否则使用只读关键字val。
要使用一个属性,只要用名称引用它即可,就像 Java 中的字段:
fun main(args: Array<String>) {
val xiaoming = Person("xiaoming",false)
println(xiaoming.name)
println(xiaoming.isMarried)
}
class Person(val name:String,var isMarried: Boolean)
自定义访问器
声明一个属性的完整语法是
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略。
我们可以编写自定义的访问器,非常像普通函数,刚好在属性声明内部。
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
属性isSquare不需要字段来保存它的值,它的值是每次访问属性的时候计算出来的。
可见性修饰符
总的来说,Kotlin中的可见性修饰符和Java中的类似。同样可以使用public,protected,provide修饰符,但是默认的可见性是不一样的:如果省略了修饰符,声明就是public。
Kotlin中没有Java中的默认可见性——包私有,而有一个新的修饰符internal,表示只在模块内可见。一个模块就是一组一起编译的Kotlin文件。以下是各个修饰符的可见范围:
修饰符 | 类成员 | 顶层声明 |
---|---|---|
private | 类成员可见 | 文件内可见 |
protected | 和 private一样 + 在子类中可见 | 无 |
internal | 模块中可见 | 模块中可见 |
public | 所有地方可见 | 所有地方可见 |
继承
在 Kotlin 中所有类都有一个共同的超类Any,这对于没有超类型声明的类是默认超类。Kotlin在类名后面使用冒号代替了Java中的extends 和implements关键字。和Java一样,一个类可以实现任意多个接口,但只能继承一个类。
open class Base(p: Int)
class Derived(p: Int) : Base(p)
类上的 open 标注与 Java 中 final 相反,它允许其他类从这个类继承。默认情况下,在 Kotlin 中所有的类都是 final。
如果派生类有一个主构造函数,其基类型可以(并且必须) 用基类的主构造函数参数就地初始化。
如果类没有主构造函数,那么每个次构造函数必须使用super关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
覆盖方法
Kotlin中方法也是默认为final的,你需要给每一个可以被重写的方法添加open修饰符,同时,你必须在子类覆盖父类的方法时在前面添加override修饰符。如果没有标注 open ,则子类中不允许定义相同签名的方法, 不论加不加 override。在一个没有用 open 标注的类中,开放成员是禁止的。在派生类中的代码中,可以使用super关键字调用其超类的函数与属性访问器的实现。
open class Base {
open fun v() {}
fun nv() {}
}
class Derived: Base() {
override fun v() {
super.v()
nv()
}
}
覆盖属性
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有 getter 方法的属性覆盖。
你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。这是允许的,因为一个 val 属性本质上声明了一个 getter 方法,而将其覆盖为 var 只是在子类中额外声明一个 setter 方法。
open class Foo {
open val x: Int get() { …… }
}
class Bar1 : Foo() {
override val x: Int = ……
}
接口
Kotlin 的接口与 Java 8 类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
使用关键字 interface 来定义接口:
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
一个类或者对象可以实现一个或多个接口。
class Child : MyInterface {
override fun bar() {
// 方法体
}
}
你可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现。在接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
覆盖冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题。就像下方:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
Kotlin规定,当子类中有一个方法在父类和接口中有多个实现的话,子类必须覆盖这个方法。在覆盖的方法内子类可以使用super<>语法直接调用父类或接口中的方法。
总结
本文简单的介绍了Kotlin中类和接口的声明与定义,以及两者的继承规则。涉及到的关键字有:
关键字 | 说明 |
---|---|
class | 声明一个类 |
final | 禁止成员覆盖 |
open | 允许一个类子类化或覆盖成员 |
override | 将一个成员标记为超类成员的覆盖 |
private | 将一个声明标记为在当前类或文件中可见 |
protected | 将一个声明标记为在当前类及其子类中可见 |
public | 将一个声明标记为在任何地方可见 |
super | 引用一个方法或属性的超类实现,在次构造函数中调用超类构造函数 |