写给 Java 开发者的 Kotlin 教程 (8) - 面向对象 - 基础

banner

话不多说,Kotlin依然是一个门 OOP 语言,我们从今天开始我们来踏上最后一段旅程。

Class 类

KotlinJava 一样,依然保留 class 这个关键字,我们可以使用类似于 Java 的类声明

1
2
class Person {
}
1
2
3
class Person {
//没啥区别
}

相比较 Java 而言, Kotlin 允许省略空声明的 一对{} 即是

1
class Person //合法声明

实例化一个类的时候不再需要 new 这个关键字。

1
val person = Person()
1
Person person = new Person()

构造函数

Kotlin 具有两种 构造函数 分别是 主构造函数次构造函数

主构造函数仅允许出现一次,出现在类定义上

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

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

1
class Person(firstName: String) { ... }

次构造函数 声明在类定义体内

1
2
3
4
5
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}

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

1
2
3
4
5
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}

Initializer 块

除构造函数之外,Kotlin 还有一个特殊的关键字 init,因为 主构造函数 不能申明代码,所以初始化的逻辑只能放在一个单独的代码块中,这块区域被称之为 初始化块

1
2
3
4
5
6
7
8
9
10
11
12
class Person(_firstName: String, _lastName: String) {
var firstName: String
var lastName: String

// 初始化块
init {
this.firstName = _firstName
this.lastName = _lastName

println("Initialized a new Person object with firstName = $firstName and lastName = $lastName")
}
}

值得注意的 init 段是允许出现多次的,执行的顺序是按照书写的顺序执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)

init {
println("First initializer block that prints ${name}")
}

val secondProperty = "Second property: ${name.length}".also(::println)

init {
println("Second initializer block that prints ${name.length}")
}
}

继承

Kotlin 中的所有的类都 继承于 Any 类,

注意:Any 并不是 java.lang.Object;尤其是,它除了 equals()、hashCode()和toString()外没有任何成员。

需要继承类也非常的简单,只需要使用 : 把类型放到类头的冒号之后即可。

1
2
3
open class Base(p: Int)

class Derived(p: Int) : Base(p)
1
2
3
class Derived extend Base{

}

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

覆盖方法 & 覆盖属性

Kotlin 力求清晰显式。与 Java 不同,Kotlin 需要显式标注可覆盖的成员。

1
2
3
4
5
6
7
open class Base {
open fun v() { ... }
fun nv() { ... }
}
class Derived() : Base() {
override fun v() { ... }
}

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

覆盖属性 与方法覆盖类似

1
2
3
4
5
6
7
open class Foo {
open val x: Int get() { …… }
}

class Bar1 : Foo() {
override val x: Int = ……
}

调用超类实现

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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
}

抽象类

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

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

abstract class Derived : Base() {
override abstract fun f()
}

可见性修饰

KotlinJava 类型具有四种可见性修饰关键字 public, private, protectedinternal,前三个和 Java 中的语义是一样的,最后internal 是指在同一个 模块下 可以被访问,这里的模块的定义是:

  • 一个 IntelliJ IDEA 模块;
  • 一个 Maven 项目;
  • 一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
  • 一次 <kotlinc> Ant 任务执行所编译的一套文件。

参考文献