Kotlin 学习笔记(六)—— Kotlin类与对象之类和继承


Kotlin学习笔记系列教程

Kotlin 学习笔记(一)—— 概述、学习曲线、开发工具、参考资料
Kotlin 学习笔记(二)—— 基础语法
Kotlin 学习笔记(三)—— 习惯用法
Kotlin 学习笔记(四)—— Kotlin基础之基本类型
Kotlin 学习笔记(五)—— Kotlin基础之控制流、返回与跳转、 包与导入


Kotlin中使用关键字class声明类

1
2
class Invoice {
}

类声明由类名、类头(指定其类型参数、主构造函数等)以及花括号包围的类体构成。类头和类体都是可选的;如果一个类没有类体,可以省略花括号。

1
class Empty

构造函数

在Kotlin中的一个类可以有一个主构造函数和一个或多个次构造函数,主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。

1
2
class Person constructor(firstName: String) {
}

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个constructor关键字。

1
2
class Person(firstName: String) {
}

主构造函数不能包含任何的代码。初始化代码可以放到以init关键字作为前缀的初始化块initializer blocks)中。
在实例初始化期间,初始化块按照它们出现在类体重的顺序执行,与属性初始化器交织在一起:

1
2
3
4
5
6
7
8
9
class Kotlin1(fileName: String){
val time = fileName
init {
var size = time.length
}
}

注意:主构造的参数可以在初始化块中使用,它们也可以在类体内声明的属性初始化器中使用:

1
val time = fileName

事实上,声明属性以及从主构造函数初始化属性,Kotlin有简洁的语法:

1
2
3
class Kotlin1(fileName: String,val size: Int,var age: Int){
//....
}

与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。
如果构造函数有注解或可见性修饰符,这个constructor关键词是必须的,并且这些修饰符在它的前面:

1
class Customer public @Inject constructor(name: String) { …… }

次构造函数

1
2
3
4
5
6
7
8
class Kotlin1{
constructor(fileName: String, size: Int, age: Int) {
val time = fileName
}
}

如果类有一个主构造函数,每个次构造函数需要委托给主构造函数,可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用this关键字即可:

1
2
3
4
5
6
7
class Kotlin1(fileName: String){
constructor(fileName: String, size: Int, age: Int) : this(fileName){
}
}

请注意,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块:

1
2
3
4
5
6
7
8
9
10
11
class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
}

如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数,构造函数的可见性是public,如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:

1
2
3
class Kotlin2 private constructor(){
}

1
2
3
4
5
注意:在JVM上,如果主构造函数的所有参数都有默认值,编译器会生成一个额外的无参构造函数,它将使用默认值,这使得Kotlin更易于像Jackson或者JPA这样的通过无参构造函数创建类的实例的库。
class Kotlin2 (val fileName: String = ""){
}

###创建类的实例
要创建一个类的实例,我们就像普通函数一样调用构造函数:

1
val kotlin2 = Kotlin2("music")

注意Kotlin没有new 关键字
创建嵌套类、内部类和匿名内部类的类实例在嵌套类中有述。

类成员

类可以包含:

  • 构造函数和初始化块
  • 函数
  • 属性
  • 嵌套类和内部类
  • 对象声明

继承

在Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:

1
class Example // 从 Any 隐式继承

1
注意:Any 并不是 java.lang.Object;尤其是,它除了 equals()、hashCode()和toString()外没有任何成员。 更多细节请查阅Java互操作性部分。

要声明一个显式的超类型,我们把类型放到类头的冒号之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
open class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
}
class Kotlin2 : Kotlin1("music",20,20) {
}

1
类上的 open 标注与 Java 中 final相反,它允许其他类从这个类继承。默认情况下,在Kotlin中所有的类都是final,对应于 Effective Java书中的第17条:**要么为继承而设计,并提供说明文档,要么就禁止继承**。

如果派生类有一个主构造函数,其基类型可以(并且必须)用基类的主构造函数参数就地初始化。

如果累没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:

1
2
3
4
5
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

覆盖方法

我们之前提到过,Kotlin力求清晰显示,与Java 不同,Kotlin 需要显示标注可覆盖的成员(我们称之为开放)和覆盖后的成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
open class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
open fun addSize(){}
fun addAge(){}
}
class Kotlin2 : Kotlin1("music",20,20) {
override fun addSize() {
super.addSize()
}
}

Kotlin2.addSize()函数上必须加上override标注,如果没写,编译器将会报错。如果函数没有标注open 和 Kotlin1.addAge(),则子类中不允许定义相同签名的函数,不论加不加 override。在一个final 类中,开放成员是禁止的。

标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用final 关键字:

1
2
3
4
5
6
class Kotlin2 : Kotlin1("music",20,20) {
final override fun addSize() {
super.addSize()
}
}

覆盖属性

属性覆盖和方法覆盖类似:在超类中声明然后在派生类中重新声明的属性必须以override 开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化的属性或者具有getter方法的属性覆盖。

1
2
3
4
5
6
7
8
9
10
open class Kotlin1{
open val aa: Int get() {
return 5
}
}
class Kotlin2 : Kotlin1 {
override val aa: Int
get() = super.aa + 5
}

你也可以用一个 var 属性覆盖一个 val 属性,但是反之则不行。这是允许的,因为一个 val 属性本质上声明了 一个 getter 方法,而将其覆盖为 var 只是在子类中额外 声明一个 setter方法。

请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。

1
2
3
4
5
6
7
8
9
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}

派生类初始化顺序

在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
open class Base(val name: String) {
init { println("Initializing Base") }
open val size: Int =
name.length.also { println("Initializing size in Base: $it") }
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {
init { println("Initializing Derived") }
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }

输出:

1
2
3
4
5
6
Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10

这意味着,基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化,如果在基类初始化逻辑中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或者运行时故障,设计一个基类时,应该避免这种在构造函数、属性初始化器以及init 块中使用 open 成员

调用超类实现

派生类中的代码可以使用super 关键字调用其超类的函数与属性访问器的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
open class Kotlin1{
init {
print("first")
}
constructor(fileName: String, size: Int, age: Int){
print("second")
}
open fun addSize(){
print("Kotlin1")
}
fun addAge(){}
}
class Kotlin2 : Kotlin1("music",20,20) {
override fun addSize() {
super.addSize()
print("Kotlin2")
}
}

在一个内部类中访问外部类的超类,可以通过由外部类名限定的super关键字来实现: super@Outer

1
2
3
4
5
6
7
8
9
10
11
class Bar : Foo() {
override fun f() { /* …… */ }
override val x: Int get() = 0
inner class Baz {
fun g() {
super@Bar.f() // 调用 Foo 实现的 f()
println(super@Bar.x) // 使用 Foo 实现的 x 的 getter
}
}
}

覆盖规则

在Kotlin中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现,它必须覆盖这个成员并提供自己的实现(也许用继承来的其中之一)。为了表示采用从哪个超类型继承的实现,我们使用尖括号中超类型名限定的super,例如 super<Base>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
open class Kotlin1{
open fun addSize(){}
}
interface A {
fun addSize(){
println("aa")
}
}
class Kotlin2 : Kotlin1("music",20,20),A {
override fun addSize() {
super<Kotlin1>.addSize()
super<A>.addSize()
}
}

同时继承Kotlin1 和 A 没问题,但是addSize() 由 Kotlin2 继承了两个实现,所以我们必须在 Kotlin2 中覆盖addSize() 并且提供我们自己的实现来消除歧义。

抽象类

类和其中的某些成员可以声明为abstract。 抽象成员在本类中可以不用实现,需要注意的是,我们并不需要用 open 标注一个抽象类火灾函数 – 因为这不言而喻

我们可以用一个抽象成员覆盖一个非抽象的开放成员

1
2
3
4
5
6
7
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}

伴生对象

与Java 或者 C# 不同,在Kotlin 中类没有静态方法,在大多数情况下,它建议简单地使用包级函数。

如果你需要写一个无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内对象声明中的一员。

更具体的讲,如果在你的类内部声明了一个伴生对象,你就可以使用像在Java/C# 中调用静态方法相同的语法来调用其成员,只使用类名作为限定符。


>以上就是类与对象的第一篇–类和继承

个人博客地址:http://outofmemory.top/
CSDN地址:http://blog.csdn.net/dazhaoDai
GitHub地址:https://github.com/dazhaoDai


文章目录
  1. 1. Kotlin学习笔记系列教程
  • 构造函数
    1. 1. 次构造函数
    2. 2. 类成员
    3. 3. 继承
    4. 4. 覆盖方法
    5. 5. 覆盖属性
    6. 6. 派生类初始化顺序
    7. 7. 调用超类实现
    8. 8. 覆盖规则
    9. 9. 抽象类
    10. 10. 伴生对象
  • >以上就是类与对象的第一篇–类和继承
  • |