Kotlin 学习之类和继承
类和继承
类
在 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) {
// ...
}
就像普通的属性,在主构造函数中的属性可以是可变的( var
)或只读的( val
)。
如果构造函数有注解或者可见性修饰符,constructor
关键字就不可或缺,而且修饰符要放在它前面。
class Customer public @Inject constructor(name: String) { ... }
参看 可见性
二级构造函数
类也可以有二级构造函数,需要加前缀 constructor
:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有主构造函数,每个二级构造函数都要,或直接或间接通过另一个二级构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this
关键字:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
如果一个非抽象类没有声明构造函数(主构造函数或二级构造函数),它会产生一个没有参数的构造函数。该构造函数的可见性是 public 。如果你不想你的类有公共的构造函数,你就得声明一个拥有非默认可见性的空主构造函数:
class DontCreateMe private constructor () {
}
注意:在 JVM 虚拟机中,如果主构造函数的所有参数都有默值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。这使得Kotlin 可以更简单的使用像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例的库。
class Customer(val customerName: String = "")
创建类的实例
我们可以像使用普通函数那样使用构造函数创建类实例:
val invoice = Invoice()
val customer = Customer("Joe Smith")
注意 Kotlin 没有 new
关键字。
创建嵌套类、内部类或匿名类的实例参见嵌套类
Class Members
类可以包含:
- 构造函数和初始化代码块
- 函数
- 属性
- 嵌套类和内部类
- 对象声明
继承
Kotlin 中所有的类都有共同的父类 Any
,它是一个没有父类声明的类的默认父类:
class Example // 隐式继承于 Any
Any
不是 java.lang.Object
; 尤其是除了 equals()
, hashCode()
and toString()
没有任何其他成员了。
参看 Java interoperability 了解更多信息。
声明一个明确的父类,需要在类头后加冒号再加父类:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果类有主构造函数,则基类可以而且是必须在主构造函数中使用参数立即初始化。
I如果类没有主构造函数,则必须在每一个构造函数中用 super
关键字初始化基类,或者在代理另一个构造函数做这件事。注意在这种情形中不同的二级构造函数可以调用基类不同的构造方法:
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
open
注解与java中的 final
相反:它允许别的类继承这个类。默认情形下,kotlin中所有的类都是 final
,对应Effective Java,Item 17: Design and document for inheritance or else prohibit it.
复写方法
像之前提到的,我们在 kotlin 中坚持做明确的事。不像 java ,kotlin 需要把可以复写的成员(我们称之为open)都明确注解出来,并且重写它们:
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
}
调用父类的实现
在派生类中的代码可以使用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关键字配合外部类的类名super@Outer
:
class Bar : Foo() {
override fun f() { /* ... */ }
override val x: Int get() = 0
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
}
}
}
复写规则
在 kotlin 中,实现继承通常遵循如下规则:如果一个类从它的直接父类继承了同一个成员的多个实现,那么它必须复写这个成员并且提供自己的实现(或许只是直接用了继承来的实现)。为表示使用父类中提供的方法我们用 super<Base>
表示:
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# 它没有静态方法。在大多数情形下,我们建议只用包级别的函数。
如果你要写一个没有实例类就可以调用的方法,但需要访问到类内部(比如说一个工厂方法),你可以把它写成它所在类的一个 对象的声明。
更高效的方法是,你可以在你的类中声明一个伴随对象 ,这样你就可以像 java/c#那样把它当做静态方法调用,只需要它的类名做一个识别就好了。
更多相关文章
- 如何在函数中将两个参数从1个类传递给另一个?
- 回调函数在Android监听机制中的体现
- javaScript函数中执行C#代码中的函数
- JavaScript unshift()函数移入数据到数组第一位
- Java 8 新特性-菜鸟教程 (3) -Java 8 函数式接口
- 如何将动态参数传递给jquery函数
- Java多线程六:线程优先级和yield()让步函数
- 不幸的是,在声明按钮时,模拟器中出现了错误
- javaScript里的string的match函数返回值的问题