Kotlin 类的创建与继承
Kotlin
官方在线编程网址:
本文参考自:
- 《第一行代码 Android 第3版》
- 朱涛 · Kotlin 编程第一课 (geekbang.org)
- Kotlin之类的继承及属性和方法的重写_c1392851600的博客-CSDN博客
基本写法
创建
通过 class
关键字创建一个类:
1 |
|
实例化方式:
1 |
|
继承
默认所有非抽象类都是不可以被继承的。
如果需要将其变为父类,则需要在 class
前使用 open
关键字。
1 |
|
如果需要继承另一个类,则使用 :
符号:
1 |
|
重写
重写父类的方法
需要注意的是如果父类的方法没有 open
声明,那么该方法是不允许被重写的。重写方法的时候需要使用 override
关键字注明。还有一点就是子类可以选择不重写父类的方法。
1 |
|
再次需要注意的是,如果一个类集成了多个类/接口,并且父类/接口中有相同名字的方法需要重写的时候,那么子类这时候必须重写该方法,并且如果子类想区分开父类的方法的时候,可以使用 super
关键字调用不同父类的方法。
1 |
|
重写父类的变量
父类变量的重写的时候有几个需要注意的地方:
被重写的变量也要有
open
的声明。子类可以使用
var
类型的变量去重写父类val
类型的变量,但是不能使用val
类型的变量去重写父类var
类型的变量。如果使用
val
类型的变量去重写父类的var
类型的变量, 那么子类这个val
类型的变量会多一个set
方法, 而val
类型的变量是不允许有set
方法的。
1 |
|
构造函数
Kotlin
将构造函数分为了两种:主构造函数和次构造函数。
主构造函数
主构造函数是最常使用的构造函数,每个类默认都会有一个不带参数的主构造函数。
(特殊情况:如果在没有实现主构造函数的情况下实现了次构造函数,则会失去该不带参数的主构造函数。)
当然也是可以显式地为其指明参数。主构造函数的特点就是没有函数体,直接定义在类名的后面即可:
1 |
|
我们在实例化的时候需要传入构造函数中的所有的参数:
1 |
|
由于主构造函数没有函数体,所以我们无法直接在其中编写一些逻辑。但是 Kotlin
给我们提供了一个 init
结构体,所有主构造函数的逻辑都可以写在里面:
1 |
|
根据 Java
语言继承特性中的一个规定:子类的构造函数必须调用父类中的构造函数。这个规定在 Kotlin
中也要遵守。
所以在上文中 class Student : Person()
以及 class Student(val studentNo: String, var grade: Int) : Person()
中 Person
类后面的一对空括号表示 Student
类的主构造函数在初始化的时候会调用 Person
类的无参数构造函数,即使在自身的主构造函数为默认的无参数的时候也是不能省略的。
现在我们将 Person
类改造一下,将姓名和年龄都放到主构造函数中,即:
1 |
|
因为我们自定义了主构造函数,所以默认提供的无参数的主构造函数就不存在了,所以我们也需要修改一下 Student
类的表示方法:
1 |
|
注意,我们在 Student
类的主构造函数中增加 name
和 age
这两个字段时,不能再将它们声明成 val
,因为在主构造函数中声明成 val
或者 var
的参数将自动成为该类的字段,这就会导致和父类中同名的 name
和 age
字段造成冲突。因此,这里的 name
和 age
参数前面我们不用加任何关键字,让它的作用域仅限定在主构造函数当中即可 。
现在就可以通过如下的代码来创建一个 Student
类的实例并使用了:
1 |
|
次构造函数
其实你几乎是用不到次构造函数的,Kotlin
提供了一个给函数设定参数默认值的功能,基本上可以替代次构造函数的作用。
任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可以用于实例化一个类,这一点和主构造函数没有什么不同,只不过它是有函数体的。
Kotlin
规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。
比如以下的代码:
1 |
|
特殊情况:
那么接下来我们就再来看一种非常特殊的情况:类中只有次构造函数,没有主构造函数。
这种情况真的十分少见,但在 Kotlin
中是允许的。
当一个类没有显式地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。
我们结合代码来看一下:
1 |
|
注意这里的代码变化,首先 Student
类的后面没有显式地定义主构造函数,同时又因为定义了次构造函数,所以现在 Student
类是没有主构造函数的。那么既然没有主构造函数,继承 Person
类的时候也就不需要再加上括号了(因为主构造函数会调用父类的构造函数)。
另外,由于没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将 this
关键字换成了 super
关键字,这部分就很好理解了,因为和 Java
比较像,也就不再多说了。
函数的命名参数以及参数默认值
类的构造函数同样适用于命名参数以及参数默认值,所以我们在极大多数的情况下通过这两个特性配合主构造函数来可以实现次构造函数的功能。
命名参数
简单理解,就是它允许我们在调用函数的时候传入“形参的名字”。
如同以下形式:
1 |
|
让我们看一个更具体的使用场景:
1 |
|
我们可以通过如下的方法使用函数:
1 |
|
可以看到,在这段代码中,我们把函数的形参加了进来,形参和实参用 =
连接,建立了两者的对应关系。对比前面 Java
风格的写法,这样的代码可读性更强了。如果将来你想修改 likeCount
这个参数,也可以轻松做到。这其实就体现出了 Kotlin
命名参数的可读性与易维护性两个优势。
参数默认值
而除了命名参数这个特性,Kotlin
还支持参数默认值,这个特性在参数较多的情况下同样有很大的优势:
1 |
|
我们可以看到,gender
、friendCount
、feedCount
、likeCount
、commentCount
这几个参数都被赋予了默认值。这样做的好处就在于,我们在调用的时候可以省很多事情。比如说,下面这段代码就只需要传 3 个参数,剩余的 4 个参数没有传,但是 Kotlin
编译器会自动帮我们填上默认值。
1 |
|
对于无默认值的参数,编译器会强制要求我们在调用处传参;对于有默认值的参数,则可传可不传。Kotlin
这样的特性,在一些场景下就可以极大地提升我们的开发效率。而如果是在 Java
当中要实现类似的事情,我们就必须手动定义“3 个参数的 createUser
函数”,或者是使用 Builder
设计模式。
在构造函数中的使用案例
通过写出如下的代码:
1 |
|
在给主构造函数的每个参数都设定了默认值之后,我们就可以使用任何传参组合的方式来对 Student
类进行实例化,当然也包含了上文中的两种次构造函数的使用场景。
函数的可见性修饰符
熟悉 Java
的人一定知道,Java
中有 public
、 private
、 protected
和 default
(什么都不写)这4种函数可见性修饰符。
Kotlin
中也有4种,分别是 public
、 private
、protected
和 internal
,需要使用哪种修饰符时,直接定义在 fun
关键字的前面即可。
下面将详细介绍一下 Java
和 Kotlin
中这些函数可见性修饰符的异同。
private
修饰符在两种语言中的作用是一模一样的,都表示只对当前类内部可见。public
修饰符的作用虽然也是一致的,表示对所有类都可见,但是在Kotlin
中public
修饰符是默认项,而在Java
中default
才是默认项。前面我们定义了那么多的函数,都没有加任何的修饰符,所以它们默认都是public
的。protected
关键字在Java
中表示对当前类、子类和同一包路径下的类可见,在Kotlin
中则表示只对当前类和子类可见。Kotlin
抛弃了Java
中的default
可见性(同一包路径下的类可见),引人了一种新的可见性概念,只对同一模块中的类可见,使用的是internal
修饰符。比如我们开发了一个模块给别人使用,但是有一些函数只允许在模块内部调用,不想暴露给外部,就可以将这些函数声明成internal
。
所以,我们可以通过 private
关键字将类的构造函数定义为私有属性:
1 |
|