Kotlin学习之-5.1 类和继承
类
Kotlin中类定义使用关键字class
class Invoice {
}
定义一个类需要包括类名,类头(包括它的类型参数,主构造函数等等)和类主体包含在成对的花括号。 类头和类主体是可选的, 如果类没有类主体,那么花括号也可以省略
class Empty
构造函数
Kotlin中的类可以有一个主要的构造函数和多个次要构造函数。主要构造函数是类头的一部分,它直接跟在类名后面,还有可选的类型参数。
class Person constructor(firstName: String) {
}
如果主要构造函数没有任何注解或者可见性修饰符,构造函数关键字constructor
可以省略
class Person(firstName: String) {
}
如果主要构造函数不包含任何代码。初始化代码可以被放置在初始化块语句中,初始化块语句的前面有init
关键字:
class Custom(name: String) {
init {
logger.info("Customer initialized with value $name")
}
}
注意主要构造函数的参数可以在初始化块语句中使用。它们也可以在类主体中属性初始化语句中使用。
class Customer(name: String) {
val customKey = 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)
}
}
如果一个非抽象类没有定义任何主要或者次要构造函数,它将会生成一个没有参数的主要构造函数。这个主要构造函数是公共可见的。如果不想让一个类有公共的构造函数,你需要定义一个私有的空的主要构造函数
calss DontCreateMe private custructor() {
}
注意在JVM上,如果一个主要构造函数的所有参数都有默认值,那么编译器会生成一个附加的没有参数的构造函数,这个构造函数会使用这些默认值。 这样使得像Jackson 或者JPA等这些第三方库可以更容易的创建一个对象。
创建一个类的对象
创建一个类的对象,我们就像调用普通函数一样调用它的构造函数就可以。
val invoice = Invoice()
val customer = Customer("Jack Chanson")
注意,Kotlin不支持new
关键字
类成员
类成员包括:
- 构造函数和初始化块代码
- 函数
- 属性
- 内嵌类’Nested and Inner Classes’
- 对象定义
继承
所有Kotlin中的类都有一个共有的父类Any
, 这是默认的父类,不需要声明。
class Example // 隐式的继承了Any
Any
不是一个java.lang.Object
;特别地,它只有equals()
, hashCode()
, toString()
这几个方法。
要显式地定义一个父类,我们在类头的类型后面加上冒号,然后跟着父类
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果类有一个主要构造函数,那么基类必须被定义在那里,并使用主要构造函数的参数
如果类没有主要构造函数,那么每个次要构造函数必须使用关键字super
来初始化基类,或者代理至另外一个使用了关键字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条的建议。
复写方法
如上所述,我们坚持在Kotlin中写的简单明了。不像在Java,Kotlin需要显式地给可以被复写的成员写上注解。
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
override
注解对于方法Derived.v()
来说是必须的。如果这个缺失了,编译器会编译失败。 如果一个方法没有open
注解,像例子中的Base.nv()
, 那么在子类中定义一个同样签名的方法是不允许的,不管有没有override
注解。 在一个final的类中,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 Bar(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}
复写规则
Kotlin中,实现继承要按照如下规则进行:如果一个类从父类中继承了一个成员的多个实现,它必须复写这个成员并且提供它自己的实现(也可是使用其中一个继承的)。为了表示是从哪个父类中继承的实现,我们使用super
关键字加上尖括号中的父类名来表示super<Base>
。
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") }
fun b() { print("b") }
}
class C() : A(), B {
// 强制要求函数f()被复写
override fun f() {
super<A>.f() // 调用A 的f方法
super<B>.f() // 调用B 的f方法
}
}
可以同时从A和B中继承,并且对于a() 和b() 来说是没有问题的,因为C在类中仅继承了一个实现。但是对于函数f() 来说,就继承了2个实现,因此我们必须复写C 中的f() 方法,并且提供自己的实现来消除歧义。
抽象类
一个类和它的成员可以被定义成抽象的abstract
, 一个抽象成员不需要在当前的类中实现。注意我们不需要标注一个抽象的类或者方法是公有open
的,抽象的类或者成员自动是公有open
的。
我们可以用一个抽象成员来复写一个非抽象的公有成员
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
伙伴对象Companion Object
在Kotlin中,不像Java和C#, 类没有静态成员。 在大多数情况下,建议使用简单的包级别的函数来代理。
如果需要写一个函数,让它在访问的时候无需持有该类的对象,但还能够访问类的内部信息(例如一个工厂方法),可以在类的内部定义一个对象作为成员来实现。
更具体的说,可以在类里定义一个伙伴对象,这样就可以使用类名来访问类的成员,这样就和Java/C#中的静态方法是一样的了。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发