《Kotlin核心编程》读书笔记-面向对象
内容摘自《Kotlin核心编程》第3章,在其基础上对一些概念进行拓展了解
本章主要讲Kotlin中一些面向对象的类和方法讲解以及一些相对Java的优化点。
类和构造方法
定义类
与Java相同使用class结构体来声明一个类
1 | |
上述代码就定义了一个鸟对象(蓝色、1岁、会飞)。
Kotlin声明类的语法非常类似于Java,但还是存在一些不同
- 不可变属性成员:通过
val声明引用不可变的属性成员,在反编译成class时,该参数是由final进行修饰 - 属性默认值:除非显式的声明参数延迟初始化,否则必须指定属性的默认值
- 不同的访问修饰符:Kotlin类中的成员默认是
public的,Java的默认可见域是protected
定义接口
Kotlin中的接口与Java 8类似,既包含抽象方法的声明也包含实现。在Java 8引入了一个新特性-接口方法支持默认实现。而Kotlin是兼容Java 6,也提供了这种实现。
Java 8中接口的实现
1 | |
其中fly()是一个默认方法,其他继承该接口的类都不需要实现该方法。
Kotlin中接口的实现
1 | |
拓展知识:
Kotlin是如何支持接口的默认方法?
首先对Kotlin定义的接口进行转换,转成容易理解的Java代码
1 | |
Kotlin内部通过一个DefaultImpls这个静态内部类提供了fly()的默认实现。
Kotlin的方法中还支持声明属性,但内部通过观察反编译的Java代码可知是提供了一个get()提供支持,但是无法对接口定义的属性直接进行赋值,也是因为这个原因。
1 | |
构造类对象
1 | |
在Kotlin中并不会使用到new关键字,可以直接进行类的声明
如果构造方法中需要添加参数,直接在Bird()内添加参数即可,不需要进行方法的重载而产生多个方法。
要实现这种功能,需要依赖下面提到的Kotlin中的相关构造语法
主从构造函数
Kotlin中的每个类最多存在一个主构造函数以及多个从构造函数。
主构造函数
主构造函数:是类头的一部分,跟在类名之后
1 | |
一般情况下constructor可进行省略即第一种方式,特殊情况下必须显示(存在注解或者可见性修饰符)。class Person @Deprecated(message = "111") public constructor(name: String)(一般不会这样写~)
主构造函数中不能包含任何代码,如果需要初始化要在init语句块中实现,在主构造函数中()内的属性有两种定义方式:
class Person(name:String)此时的name是局部变量,无法在其他方法中直接进行使用,只可以在init语句块或属性声明进行使用1
2
3
4
5
6
7
8
9class Person(name:String){
init{
val length1 = name.length
}
val length2 = name.length
fun test(){
// name.length 无法调用
}
}将上述代码反编译成Java代码
1
2
3
4
5
6
7
8
9
10
11public final class Person{
private final int length2;
public Person(String name){
int length1 = name.length();
this.length2 = name.length();
}
public final void test{
//name.length 无法调用
}
}观察Java代码可知
init{}以及声明时相关逻辑都会在Person(String name)中执行,所以不会出错。class Person(val name:String)此时的name是全局变量,可以在任意地方进行使用。1
2
3
4
5
6
7
8
9class Person constructor(val name: String) {
init {
val length1 = name.length
}
val length2 = name.length
fun test() {
val length3 = name.length
}
}将上述代码反编译成Java代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public final class Person {
private final int length2;
@NotNull
private final String name;
@NotNull
public final String getName() {
return this.name;
}
public final void callName() {
int length3 = this.name.length();
}
public Person(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
int var2 = this.name.length();
this.length2 = this.name.length();
}
}此时的
name就是一个全局变量,可以在任意地方使用。
从构造函数
从构造函数:由两部分组成:对其他构造方法的委托;另一部分由{}构成的代码块。如果存在主构造函数,从构造函数都需要直接或间接委托给主构造函数(通过this(...))。
1 | |
从构造函数参数不能声明val/var。
1 | |
构造方法默认参数
Java在方法重载时需要额外的添加方法,导致方法过多。在Kotlin中可以通过给构造方法中的参数指定默认值,从而避免不必要的方法重载。当省略相应的参数时需要使用默认值。
1 | |
由于参数默认值的存在,在创建一个类对象时,最好指定参数的名称(命名参数),否则必须按照实际参数的顺序进行赋值。
例如上述代码中的weight=1000.00,就是对weight参数进行指定赋值
命名参数:在函数调用时使用命名的函数参数,在函数有大量参数或者默认参数时使用方便。
init语句块
由于主构造函数不能包含任何的代码,所以引入了init语句块的语法,可以作为实例的初始化方法。
1 | |
对color属性在初始化时进行操作。
在构造方法中还可以拥有多个init语句块,他们会在对象创建时按照类从上到下的顺序先后执行。
1 | |
多个init语句块有利于对初始化的操作进行职能分离,在复杂的业务开发中可以起到很大的作用。
变量延迟初始化
一般地,属性声明为非空类型必须在构造函数中进行初始化,否则无法正常编译。Kotlin可以不用在类对象初始化的时候就必须有值。提供了by lazy、lateinit两种语法来实现延迟初始化的效果。
by lazy(懒初始化)
如果是用val声明的变量,可以用该语法来修饰
1 | |
语法特点如下:
- 修饰变量必须是
val - 只有再被首次调用时,才会进行赋值操作,一旦被赋值不会再被修改
lazy()接收一个lambda表达式并返回一个lazy<T>实例的函数,第一次访问该属性时,会执行对应的lazy中的lambda表达式并记录结果,后续访问时只是返回所记录的值。
另外可以对lazy属性进行设置,共支持三种属性:
LazyThreadSafetyMode.SYNCHRONIZED(默认属性):加上同步锁,在同一时刻只允许一个线程对lazy属性进行初始化,所以是线程安全。LazyThreadSafetyMode.PUBLICATION:允许多个线程进行初始化操作LazyThreadSafetyMode.NONE:若确定初始化总是发生单线程上,可以设置该参数,就不会存在线程方面的开销。
by lazy内部实现原理涉及到属性委托相关概念,后面会讲到相关内容。
lateinit(延迟初始化)
lateinit允许编译器识别非空类型属性的值然后跳过空检查,使之正常编译。
lateinit主要用于var声明变量,然而不支持修饰基本类型(Int,Long),因为基本类型的属性在类加载后的准备阶段会被初始化为默认值。需要用Integer这类包装类来替代。
1 | |
使用
lateinit关键字的时候,只是跳过了编译器的校验,如果在使用时没有进行赋值还是会出错。
拓展
除了by lazy还有一种方案可以实现变量延迟初始化,通过使用Delegates.notNull<T>,也是利用了委托这个概念实现的。
1 | |
不同的访问控制原则
与Java一样,Kotlin也提供了各种修饰符来描述类、方法,属性的可见性。
限制修饰符
用于指定
类、方法或属性的修改或者重写权限,就会用到限制修饰符。
Kotlin定义的类是默认final即不可继承和修改,使程序变得更加安全,但是会在开发过程中带来很多的不便。在Java中类是默认可以被继承,除非主动添加final修饰符。
Kotlin提供了open修饰符使类可以被继承,若需要一个方法可以被重写,也需要添加open修饰。
1 | |
除了open限制修饰符,Kotlin提供了final、abstract,两者的效果与Java对应修饰符一致。
| 修饰符 | 含义 | 与Java比较 |
|---|---|---|
| open | 允许被继承或重写 | Java默认类设置 |
| abstract | 抽象类或抽象方法 | 效果一致 |
| final | 不允许被继承与重写(默认设置) | 与Javafianl修饰符效果一致 |
可见性修饰符
不管是
类、对象、接口、方法、属性都具有可见性修饰符,Kotlin提供了以下四种修饰符,在不同的场景下有不同的功能。
public
Kotlin的默认修饰符,表示声明随处可用。与Java中的public功能一致
protected
Kotlin设置该修饰符后,只允许类及子类访问,在Java中还允许同包下类文件访问
private
表示该类私有,只能在当前文件中进行访问,Java中不允许对类进行private修饰
internal
Kotlin特有修饰符,只允许在模块中进行访问。
模块:一起编译的Kotlin文件的集合。包括以下几种:
- 一个Eclipse项目
- 一个Intellij IDEA项目
- 一个Maven项目
- 一个Gradle项目
- 由Ant任务执行编译的代码
提供该修饰符的原因:保证类的安全性,保证只在当前模块中调用,外部如果需要调用只能拷贝源码。
| 修饰符 | 含义 | 与Java比较 |
|---|---|---|
public |
Kotlin默认修饰符 全局可见 |
等同于Java中的public效果 |
private |
私有修饰符 类内修饰,只有本类可见 类外修饰,文件内可见 |
只有类内可见 |
protected |
受保护修饰符 本类及子类可见 |
作用域除了本类与子类,还包括包内可见 |
internal |
内部可见修饰符 模块内可见 |
无 |
private修饰符功能代码解释
1 | |
解决多继承问题
Java是不支持类的多继承,Kotlin亦是如此。但是Kotlin可以通过特殊的语法提供多种不同的多继承解决方案。多继承最大的问题就是导致继承关系的语义混淆。
多继承困惑
容易导致钻石问题(菱形继承问题),在类的多重继承下,会在继承关系下产生歧义。并且会导致代码维护上的困扰以及代码的耦合性增加。
接口实现多继承
一个类可以实现多个接口,在Java中是很常见的。Kotlin中的接口还可以声明抽象的属性,这个可以帮助Kotlin来通过接口实现多继承。
1 | |
上述定义的NewFlyer、NewAnimal接口,都设置了kind(),就会引起继承上的歧义问题。Kotlin通过提供super<T>用来指定继承哪个父类接口方法。
内部类实现多继承
内部类:将一个类的定义放在另一个类的内部,内部类可以继承一个与外部类无关的类,保证了内部类的独立性。内部类会带有一个对外部类对象的引用。
Kotlin实现内部类
在Java中是如下实现内部类的
1 | |
Kotlin仿照上述实现
1 | |
这个时候,InnerKotlin属于嵌套类。
嵌套类:不包含对外部类实例的引用,无法调用其外部类的属性。
真正的实现方案
1 | |
利用inner关键字就可以实现内部类。
内部类多继承方案
1 | |
- 可以在一个类内部定义多个内部类,每个内部类的实例都有自己的独立状态并且与外部的信息相互独立
- 通过内部类继承外部类,可以在实例对象中获得外部类不同的状态和行为
- 可以通过
private修饰内部类,避免其他类访问内部类,保证封装性。
使用委托代替多继承
委托是Kotlin新引入的语法
参考链接
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!