类声明
Kotlin使用class
关键字声明类:
class Foo {
}
声明分为声明头和声明体。其中声明体是可选的,如下:
class Bar
构造函数
Kotlin构造函数分为主构造函数(primary constructor)和辅助构造函数(secondary constructor)。
主构造函数
主构造函数在类头中,紧跟类名之后。如果不修改主构造函数的可见性也不为主构造函数添加注解,那么constructor
关键字是可选的。主构造函数不包含任何代码,但是主构造函数将会执行init
关键字标记的代码块。并且主构造函数的形参可以在init
代码块中访问,并用于初始化类成员变量:
class Person(name: String) {
init {
this.name = name.toUpperCase()
}
}
// 带有可见性声明或者是注解的主构造函数,contructor 关键字不能省略!!!
class Person private @Inject constructor(name: String) {
// ...
}
我们甚至可以直接在主构造函数中声明并初始化类属性(能做的也仅止于此了~)。如下:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
辅助构造函数
类可以在类体中定义若干个辅助构造函数。辅助构造函数使用constructor
标记:
class Person(name: String) {
constructor(parent: Person, name: String): this(name) {
parent.children.add(this)
}
}
如上,如果函数定义了一个主构造函数,那么所有的辅助构造函数必须使用this
调用主构造函数(直接调用或者通过调用其他辅助构造函数间接调用)。
如果没有主构造函数或者辅助构造函数被定义,编译器将自动生成无参主构造函数。主构造函数的默认访问权限为public
,如果需要修改可见性,可以定义一个空的无参构造函数同时指定可见性。
class DontCreateMe private constructor() {
}
如果运行在JVM上,类的主构造函数的所有参数都有默认值,编译器将会自动生成无参构造函数,并使用默认参数值初始化。这有益于Kotlin类用于某些Java库中。
创建类的实例
Kotlin中没有new
关键字。所以相对于Java,省略掉new
就可以了~ 如下:
val apple = Apple()
val person = Person("Tom")
类继承
类似于Java中的Object类,Any
类是Kotlin中所有其它类的默认基类。Any
类不是Java中的Object类,Any
类只包含equals()
、hashCode()
和toString()
方法。
描述继承关系的方法与C++类似使用冒号,而不是使用Java中的“extend”或“implement”,Kotlin中没有这俩关键字。
Koltin中的类默认是final
不可以被继承,对于需要被继承的类,必须显式地标记为open
。
- 如果派生类有主构造函数,那么必须在派生类主构造函数调用基类的构造函数完成基类初始化。
- 如果派生类没有主构造函数,那么就必须在派生类的每一个构造函数中都调用
super()
函数完成基类的初始化。
// 需要被继承的基类,必须用open标记!
open class Base(p: Int)
// 派生类有主构造函数,必须在主构造函数之后调用基类的构造函数
class Derived_1(p: Int): Base(p)
// 派生类没有主构造函数,必须在所有的辅助构造函数中调用基类的构造函数
class Derived_2 {
constructor(p: Int): super(p)
constructor(): super(0)
}
方法覆盖(override)
不同于Java中可选的注解,Kotlin中方法覆盖需要强制使用关键字open
和override
。
基类中被覆盖的方法必须使用open
关键字标记,而派生类中覆盖基类方法的新方法必须使用override
关键字标记:
open class Base {
open fun f1() {}
fun f2() {}
}
class Derived(): Base() {
override fun f1() {}
}
如上,基类中只有f1()
是可以被覆盖的,并且在派生类中覆盖f1()
必须使用override
关键字。
方法的标记总是不能和类的标记冲突,也即含有open
函数的类必须也是open
的!
如果派生类是open
的,含有override标记的方法会默认成为open
,这与之前的默认规则不同。如果不想让派生类中覆盖的方法进一步被覆盖,那就需要添加final
关键字。如下:
open AnotherDerived(): Base() {
final override fun f1() {}
}
属性覆盖
属性覆盖和方法覆盖差不多,同样的open
和override
使用规则!
可以通过指定新的 get 方法,或提供初始值来覆盖属性。
可以使用var
属性覆盖val
属性,这相当于在派生类中新增一个 set 方法;但是不能使用val
属性覆盖var
属性,这相当于继承了基类的 set 方法却无法再派生类中使用。
interface Foo {
val count: Int
val x: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
override val x: Int get {...}
}
调用基类的方法
一句话!使用 super
~
如果是内部类需要访问外部类的基类。假设外部类名为 Outer
,那么调用方法为super@Outer
。如下:
class Bar : Foo() {
override fun f() { /* ... */ }
override val x: String get() = "..."
inner class Baz {
fun g() {
super@Bar.f() // Calls Foo's implementation of f()
println(super@Bar.x) // Uses Foo's implementation of x's getter
}
}
}
覆盖规则
- 如果继承来的多个名称(来自接口或者基类)冲突,派生类必须覆盖这个方法;
- 在派生类中访问基类(或者接口)名称的实现,如果存在多个实现那么必须使用
super<BaseType>
语法指定基类。
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // interface members are 'open' by default
fun b() { print("b") }
}
class C() : A(), B {
// The compiler requires f() to be overridden:
override fun f() {
super<A>.f() // call to A.f()
super<B>.f() // call to B.f()
}
}
抽象类
包含抽象方法的类必须是抽象类。
抽象类和抽象方法都是用abstract
关键字标记。
和接口一样,抽象类和抽象接口默认为open
!
可以用一个抽象方法覆盖非抽象方法。