类
在 kotlin 中通过关键字 class 来定义类:
class Invoice {
}
类的声明包括类名,类头(指定其类型参数,主构造函数等。)和被大括号包围的类内容。类头和类内容都是可选的,如果类没有内容,可以省略花括号。
class Empty
构造函数
Kotlin 中的类可以有一个主要构造函数和一个或多个辅助构造函数。主构造函数是类头的一部分:它在类名后面(和可选的类型参数)。
class Person constructor(firstName: String) {
}
如果主构造函数没有任何注释或可见性修饰符,那么可以省略 constructor 关键字:
class Person(firstName: String) {
}
主构造函数不能包含任何代码。初始化代码可以放在前缀为 init 关键字的初始化程序块中:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
请注意,初始化程序块中可以使用主构造函数的参数。它们也可以用于初始化在类中定义的参数:
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
实际上,为了声明属性并从主构造函数初始化它们,Kotlin 有一个简洁的语法:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
与常规属性大致相同,主构造函数中声明的属性可以是mutable(var)或只读(val)。
如果构造函数有注释或可见性修饰符,则 constructor 关键字是需要的,修饰符在它之前:
class Customer public @Inject constructor(name: String) { ... }
次要构造函数
类也可以声明次要构造函数,以 constructor 为前缀:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类具有主构造函数,则每个辅助构造函数需要通过另一个辅助构造函数直接或间接地委派给主构造函数。使用此关键字对同一类的另一个构造函数进行委派:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
如果一个非抽象类没有声明任何构造函数(主要的或次要的),那么它将具有没有参数的生成的主构造函数。构造函数的可见性将会是公开的。如果你不希望你的类有一个公开的构造函数,你需要声明一个具有非默认可见性的空主构造函数:
class DontCreateMe private constructor () {
}
请注意:在 JVM(虚拟机)中,如果所有主构造函数的参数都有默认值的话,编译器将生成一个使用默认值的额外的无参数构造函数。这使得更容易使用带有诸如 Jackson 或者 JPA 库的 Kotlin,通过无参数构造函数创建类实例。
class Customer(val customerName: String = "")
创建类的实例
要创建一个类的实例,我们调用构造函数,就像调用一个常规函数一样:
val invoice = Invoice()
val customer = Customer("Joe Smith")
请注意,Kotlin 没有 new 关键字。
创建嵌套,内部和匿名内部类的实例在嵌套类中做了描述。
类成员
类可以包含
构造函数和初始化程序块
函数
属性
嵌套和内部类
对象声明
继承
在 kotlin 中,所有的类都有着一个共同的父类 Any,下面是一个没有父类声明的类:
class Example // Implicitly inherits from Any
Any 不是 java.lang.Object;特别地,除 equals(),hashCode()和 toString()之外它不具有任何成员。参看 Java interoperability 了解更多详情。
声明一个明确的父类,需要在类头后加冒号再加父类:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果类有主构造函数,则基类可以而且是必须在主构造函数中立即初始化。
如果类没有主构造函数,则必须在每一个构造函数中用 super 关键字初始化基类,或者委托给另一个构造函数做这件事。注意在这种情形中不同的次要构造函数可以调用基类不同的构造方法:
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
open 注解与 java 中的 final 相反:它允许别的类继承这个类。默认情形下,kotlin 中所有的类都是 final ,对应 Effective Java,第 17 条:Design and document for inheritance or else prohibit it.
重写方法
正如我们前面提到的,我们坚持在 kotlin 做明确的事情。不像 java ,kotlin 要求把可以复写的成员都明确注解出来,并且重写它们:
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
对于 Derived.v() 来说 override 注解是必须的。如果没有加的话,编译器会提示。如果没有 open 注解,像 Base.nv() ,在子类中声明一个同样的函数是不合法的,要么加 override 要么不要复写。在 final 类(就是没有 open 注解的类)中,open 类型的成员是不允许的。
一个被标记为 override 的成员是 open 的,它可以在子类中被复写。如果你不想被重写就要加 final:
open class AnotherDerived() : Base() {
final override fun v() {}
}
重写属性
重写属性的工作方式与重写方法类似;在超类上声明然后在派生类上重新声明的属性必须以 override 修饰,并且它们必须具有兼容类型。每个声明的属性都可以被具有初始化器的属性或具有 getter 方法的属性覆盖。
open class Foo {
open val x: Int get { ... }
}
class Bar1 : Foo() {
override val x: Int = ...
}
你还可以使用 var 属性重写 val 属性,反之亦然。这是允许的,因为 val 属性通常会声明一个 getter 方法,将其替换为 var,并在派生类中声明一个 setter 方法。
请注意,您可以在主要构造函数中使用 override 关键字作为属性声明的一部分。
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}
重写规则
在 Kotlin 中,实现继承由以下规则来规定:如果一个类从它的直接父类继承同一个成员的多个实现,那么它必须重写该成员并提供自己的实现(可能使用其中一个继承的)。为表示使用父类中提供的方法,我们在尖括号中使用 super 和超类型名称来限定,例如 super:
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()
}
}
可以同时从 A 和 B 中继承方法,而且 C 继承 a() 或 b() 的实现没有任何问题,因为它们都只有一个实现。但是 f() 有两个实现,因此我们在 C 中必须复写 f() 并且提供自己的实现来消除歧义。
抽象类
一个类或一些成员可能被声明成 abstract 。一个抽象方法在它的类中没有实现方法。请注意,我们不需要给一个抽象类或函数添加 open 注解,它默认是带有的。
我们可以用一个抽象成员去重写一个带 open 注解的非抽象方法。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
伴随对象
在 Kotlin 中,不像 Java 和 C,类是没有 static(静态)方法的。在大多数情况下,建议简单地使用包级别的功能。
如果你要写一个没有实例类就可以调用的方法,但需要访问到类的内部(比如说一个工厂方法),你可以把它写成它所在类的一个成员。
更具体地说,如果你在类中声明了一个伴随对象,则可以使用与使用 Java / C# 中的静态方法一样的语法来调用其成员,只使用类名作为限定词。