Kotlin学习笔记之 6
6.Kotlin 继承
-
Any
Any
是所有类的超类,就像java
中的Object
。但它并不等同于Object
,除了equals()``hashCode()``toString()
没有其他任何成员。 -
继承和实现':'
之前我们已经知道了接口是
interface
,可被继承的类是open
,抽象类是abstract
。跟java不同的是,java中
extends
是继承,implements
是实现。而在kotlin中跟在:
后面,不管是接口还是类,没有先后顺序。也就是说:
既可以继承也可以实现,互相用,
隔开open class Person3 { open var sex: String = "unknow" init { println("基类初始化") } } interface Grand { fun make() } class Student : Grand, Person3() { override fun make() { } override var sex: String = "male" }
-
基类构造和子类构造
首先我们先看下最基础的构造,在经过编译之后分别是什么情况的
-
默认定义
```js //kt class Person(name: String) { } //decompiled public final class Person { public Person(@NotNull String name) { //无视,这段代码之后会出现很多次 Intrinsics.checkParameterIsNotNull(name, "name"); super(); } } ```
我们看到经过编译之后,就是定义了一个
final
的classPerson
,name
只是一个构造函数的传参而已 -
var
定义我们尝试着在构造传参
name
前加一个var
看看,会发现编译之后的差别不是一点点。```js //kt class Person(var name: String) { } //decompiled public final class Person { @NotNull private String name; @NotNull public final String getName() { return this.name; } public final void setName(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.name = var1; } public Person(@NotNull String name) { Intrinsics.checkParameterIsNotNull(name, "name"); super(); this.name = name; } } ```
编译过程帮我们创建了一个同名的属性name,并且提供了
name
的set
和get
方法,也就是说一旦主构造函数中出现了var
(我这边故意重点说了主构造函数,因为次构造函数中无法使用var
),那么所描述的这个参数就可以变得在类中可以被访问和修改,name
就变成了Person
的一部分了。
-
OK,我们接下去看一下两种情况下作为基类被继承了之后会有什么地方的不同。
前面我们有提到,kotlin中无论使用什么修饰符,对于参数本身来说,编译成java代码之后始终是private
的,只是通过get
和set
方法来体现修饰符,
如果我们的子类是这么定义的:
//kt
class Sun(name : String): Person(name){
}
//decompiled
public final class Sun extends Person {
public Sun(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super(name);
}
}
看起来完全没有问题。这时候我们把name
写上var
,我们先不反编译,直接按照上面的理解,应该是这样的
```js
//kt
class Sun(var name : String): Person(name){
}
//decompiled
public final class Sun extends Person {
@NotNull
private String name;
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public Sun(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
}
```
乍一看,没有问题。但是实际上这么写IDE直接会报错。Sun
的方法getName
和setName
是public final
的,由于父类也有相同的方法getName
,setName
。犯了java的大忌,子类出现了父类的同名函数,父类的函数还他么是final
的。
也就是说,除了父类是非final
字段,子类不能出现相同的字段。
综上所述,只要不是open
形容的字段,都不能在子类出现重复的字段名。无论是val
还是var
。
-
var
和val
复写关系val
无法复写var
,不要当做概念去背。前面我们说过,一个属性必须要用
open
来形容才能被子类复写。这是其一其二,kotlin中我们是直接去操作属性的,因为编译成java文件是通过
getter
和setter
方法做访问和修改。var
是可以访问并修改,val
只能访问无法修改。父类中本身可以访问并修改的,在子类中变得只能访问了,是不允许的。继承关系我们只能拓展而不能缩减,子类必须包含父类。
-
子类构造函数的影响
首先,一个类
class
肯定会有一个构造函数。那么对于子类来说需要注意什么呢。我们先来看一个隐式主构造函数的类:
open class Parent{ }
我们前面说到,一旦没有显式的跟在类名后面申明
constructor
,或者()
,并且在类体中没有出现次级构造函数constructor
。我们就认为有一个隐式的无参的主构造函数。这时候我们继承一下,并且尝试着什么都不做
class Sun : Parent{ }
你会发现
Person
下面有根红线报错了,写道This type has a constructor, and thus must be initialized here
父类有构造器,必须要进行初始化。也就是说我们必须显式或者隐式的去调一次
Parent
的无参的构造函数。方式有很多种,我们先看第一种:
class Sun : Parent(){ }
直接在父类后面来个括号即可。
你会发现加
()
和不加的前后经过编译得到的java文件是一样的,都是public final class Sun extends Parent { }
实际上,这边我们用有参构造去讲,会更加容易理解。比如我们针对
Parent
类添加一个有参的主构造函数,并且保留无参构造函数。open class Parent(name : String){ }
子类分别调用和不调用父类的构造函数,所编译出来的效果是明显不同的。
//kt class Sun1 : Parent{ } class Sun2 : Parent(){ } class Sun3 : Parent("tom"){ } //decompiled public final class Sun1 extends Parent { } public final class Sun2 extends Parent { } public final class Sun3 extends Parent { public Sun3() { super("tom"); } }
Sun1
是无法编译通过的,所以出来的decompiled
实际上是没有意义,上面说过调用父类的无参构造和不调用最终出来的结果是一样的,所以我们直接看Sun2
即可
Sun2
和Sun3
连在一起看,首先我们知道,在java中,每一个类如果没有单独的去申明他的构造函数,那么他们都将有一个默认的无参构造函数。并且又有继承,所以Sun2
实际上是这样的
public final class Sun2 extends Parent {
public Sun2() {
super();
}
}