Kotlin 類和對(duì)象
從這篇文章開始我們一起正式進(jìn)入 Kotlin 面向?qū)ο蟮氖澜?,Kotlin 實(shí)際上也是一門面向?qū)ο蟮恼Z(yǔ)言但同時(shí)又兼顧了函數(shù)式編程語(yǔ)言。只不過函數(shù)在 Kotlin 中的地位被提升至一等公民。但是在 Kotlin 中也是有類、對(duì)象、屬性、方法等。
1. Kotlin 中的類
在 Kotlin 中類和 Java 中概念基本是一致的,都是使用 class
關(guān)鍵字來聲明一個(gè)類,一個(gè)類中可以用屬性表示一個(gè)類的狀態(tài),可以用方法來表示一個(gè)類的行為。但是與 Java 不同的是 Kotlin 中的類聲明默認(rèn)就是 final
和 public
, 所以在 Kotlin 中不能直接繼承一個(gè)類,因?yàn)槟J(rèn)類是 final 的,此外也不需要像 Java 中一樣顯式使用 public
修飾符。
//Student.java
public class Student {//public修飾符
private String name;
private String nickName;
private int age;
public Student(String name, String nickName, int age) {
this.name = name;
this.nickName = nickName;
this.age = age;
}
}
//SeniorStudent.java
public class SeniorStudent extends Student {//直接繼承Student類
public SeniorStudent(String name, String nickName, int age) {
super(name, nickName, age);
}
}
而在 Kotlin 中不能直接繼承一個(gè)類,如果需要繼承一個(gè)類則需要在基類上加 open
關(guān)鍵字修飾。
open class Student(
private val name: String,
private val nickName: String,
private val age: Int
)//Student類被繼承需要加open關(guān)鍵字,此外Kotlin中構(gòu)造器初始化也省去了很多模版代碼
class SeniorStudent(
private val name: String,
private val nickName: String,
private val age: Int
) : Student(name, nickName, age)//在Kotlin中繼承不再使用extends關(guān)鍵字而是使用:來替代
2. 類的定義
在 Kotlin 中和 Java 一樣都是使用 class 關(guān)鍵字修飾對(duì)應(yīng)類的名稱即可。在類中會(huì)有屬性描述類的對(duì)象狀態(tài),方法描述類的對(duì)象方法。
class Bird {
val color: String = "green"//類的屬性描述類的對(duì)象的狀態(tài)
val age: Int = 3
fun fly() {//類的方法描述類的對(duì)象的行為
println("I can fly!")
}
}
我們可以上述 Kotlin 代碼反編譯成 Java 代碼,會(huì)發(fā)現(xiàn)雖然 Kotlin 和 Java 聲明方法基本類似,但是還是存在一些不同的
public final class Bird {//可以看到j(luò)ava中自動(dòng)加上public,進(jìn)一步證明了在Kotlin默認(rèn)是public訪問,而java默認(rèn)是包可見。
//此外還可看到Bird使用了final修飾,所以也就進(jìn)一步證明Kotlin中默認(rèn)所有都是final修飾,也就意味這個(gè)類默認(rèn)是不能被繼承的。
@NotNull
private final String color = "green";//final修飾,是因?yàn)樵贙otlin中使用的是val修飾成員變量,所以可以看到kotlin val就是使用Java中的final實(shí)現(xiàn)的。那么如果使用var修飾就不需要final了。
private final int age = 3;
@NotNull
public final String getColor() {//由于是val修飾,所以color屬性只會(huì)有對(duì)應(yīng)getter方法,沒有setter方法
return this.color;
}
public final int getAge() {
return this.age;
}
public final void fly() {//可以看到fly函數(shù)是final修飾,也就進(jìn)一步證明Kotlin中默認(rèn)所有都是final修飾,那么這個(gè)fly是不能被子類重寫的
String var1 = "I can fly!";
boolean var2 = false;
System.out.println(var1);
}
}
3. 更簡(jiǎn)單構(gòu)造類的對(duì)象
在 Kotlin 中構(gòu)造對(duì)象不再需要 new
關(guān)鍵字了,而是直接調(diào)用類的構(gòu)造器方法就可以創(chuàng)建一個(gè)對(duì)象了。例如以下代碼:
val bird = Bird() // 省略了new關(guān)鍵字,直接創(chuàng)建Bird對(duì)象
當(dāng)然也可以創(chuàng)建帶參數(shù)的對(duì)象,Kotlin 只需要將上述 Bird
類修改為帶默認(rèn)參數(shù)的構(gòu)造器即可,而在 Java 中則需要增加一個(gè)重載構(gòu)造器函數(shù),但是相比你會(huì)發(fā)現(xiàn) Kotlin 更為方便和簡(jiǎn)潔。
Java 實(shí)現(xiàn):
class Bird {
private String color;
private int age;
public Bird(String color, int age) {
this.color = color;
this.age = age;
}
public void fly() {
println("I can fly!");
}
}
Bird brid = new Bird("blue", 7);//java創(chuàng)建一個(gè)帶參數(shù)Bird對(duì)象
Kotlin 實(shí)現(xiàn):
class Bird(val color: String = "green", val age: Int = 3) {
fun fly() {
println("I can fly!")
}
}
val brid = Bird(color = "blue", age = 7)//創(chuàng)建一個(gè)帶參數(shù)Bird對(duì)象
4. 類的構(gòu)造器函數(shù)
在 Kotlin 中構(gòu)造器函數(shù)是存在 “主從” 關(guān)系,這點(diǎn)是 Java 中不存在的,也就是常說的主構(gòu)造器函數(shù)和從構(gòu)造器函數(shù)。比如在上述 Bird
類中需要新增一個(gè)帶類型 (type) 屬性的構(gòu)造器,就可以定義從構(gòu)造器,從構(gòu)造器是利用 constructor
關(guān)鍵字聲明。
class Bird(val color: String = "green", val age: Int = 3) { //主構(gòu)造器
constructor(
color: String = "green",
age: Int = 3,
type: String
) : this(color, age) {//使用constructor聲明從構(gòu)造器,:this(color, age)從構(gòu)造器直接委托調(diào)用主構(gòu)造器函數(shù)
//do logical
}
fun fly() {
println("I can fly!")
}
}
fun main() {
val smallBird = Bird(color = "blue", age = 8, type = "small")
}
需要注意的是,在 Kotlin 中默認(rèn)類都會(huì)存在一個(gè)無參主構(gòu)造器函數(shù),除非我們手動(dòng)指定。此外如果一個(gè)存在主構(gòu)造器,那么從構(gòu)造器函數(shù)就會(huì)直接或間接委托調(diào)用主構(gòu)造器,直接委托給主構(gòu)造器就類似上述例子中的 : this(color, age)
,當(dāng)然可以通過從構(gòu)造器 A 委托從構(gòu)造器 B,然后從構(gòu)造器 B 委托給主構(gòu)造器,從而達(dá)到間接委托作用。
class CustomView : View {
constructor(context: Context) : this(context, null)//從構(gòu)造器A委托調(diào)用從構(gòu)造器B
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)//從構(gòu)造器B委托調(diào)用從構(gòu)造器C
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {//從構(gòu)造器C委托調(diào)用主構(gòu)造器
}
}
5. init 初始化塊
與 Java 不同的是在 Kotlin 中還存在 init
初始化塊的概念,它屬于構(gòu)造器函數(shù)一部分,只是在代碼形式看似兩者是分離的。如果我們需要在初始化時(shí)進(jìn)行其他的額外操作時(shí),這時(shí)候就需要 init
語(yǔ)句塊來執(zhí)行,有個(gè)有趣的點(diǎn)需要注意的是,在 init
初始化塊中,是可以直接訪問構(gòu)造器函數(shù)中參數(shù)的。
class Bird(val color: String = "green", val age: Int = 3) {
//...
}
//上述代碼實(shí)際上等同于下面代碼
class Bird(color: String = "green", age: Int = 3) {
val color: String = color
val age: String = age
}
//所以針對(duì)沒有val修飾構(gòu)造器函數(shù)參數(shù),只能在init初始化塊中訪問,而一般成員函數(shù)是無法訪問的
class Bird(color: String = "green", age: Int = 3) {//當(dāng)color沒有val修飾
init {
println("color: $color")//可以看到在init塊中使用構(gòu)造器函數(shù)中的color參數(shù)
}
fun printInfo() {
println(color)//非法訪問
}
}
對(duì)于 init
初始化塊,是可以存在多個(gè)的,它們執(zhí)行順序是從上到下依次執(zhí)行。
class Bird(color: String = "green", age: Int = 3) {
init {
println("color: $color")//init塊1
}
init {
println("age: $age")//init塊2
}
}
//執(zhí)行的順序是,先輸出init塊1中日志再輸出init塊2中的日志
對(duì)于 init
初始化塊和從構(gòu)造器同時(shí)存在,它們的執(zhí)行順序是怎么樣的呢?是先執(zhí)行完所有的 init 初始化塊,再執(zhí)行從構(gòu)造器函數(shù)中代碼。
可以上述例子修改一下即可:
class Bird(color: String = "green", age: Int = 3) {
init {
println("color: $color")//init塊1
}
init {
println("age: $age")//init塊2
}
constructor(color: String, age: Int, type: String) : this(color, age) {
println("constructor executed")
}
}
fun main() {
val smallBird = Bird(color = "blue", age = 8, type = "small")
}
//輸出結(jié)果
color: blue
age: 8
constructor executed
Process finished with exit code 0
6. 類的 setter,getter 訪問器
與 Java 不同的是,需要手動(dòng)創(chuàng)建 setter,getter 方法;即使現(xiàn)在很多 IDEA 插件工具可以自動(dòng)生成,但是從語(yǔ)言層面來說還是比較啰嗦的。所以 Kotlin 直接在語(yǔ)言的層面省去了。先來對(duì)比一下:
public class Bird {
private String color;
private int age;
private String type;
public Bird(String color, int age, String type) {
this.color = color;
this.age = age;
this.type = type;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
而對(duì)于 Kotlin 只需要簡(jiǎn)單一行即可達(dá)到以上實(shí)現(xiàn):
class Bird(var color: String, var age: Int, var type: String)//var修飾則表示color屬性會(huì)自動(dòng)生成setter,getter方法,如果是val修飾表示只讀,那么只會(huì)生成getter方法
為了進(jìn)一步驗(yàn)證,看看這一行簡(jiǎn)單聲明是否反編譯成 java 代碼是怎么樣的
public final class Bird {
@NotNull
private String color;
private int age;
@NotNull
private String type;
@NotNull
public final String getColor() {
return this.color;
}
public final void setColor(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.color = var1;
}
public final int getAge() {
return this.age;
}
public final void setAge(int var1) {
this.age = var1;
}
@NotNull
public final String getType() {
return this.type;
}
public final void setType(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.type = var1;
}
public Bird(@NotNull String color, int age, @NotNull String type) {
Intrinsics.checkParameterIsNotNull(color, "color");
Intrinsics.checkParameterIsNotNull(type, "type");
super();
this.color = color;
this.age = age;
this.type = type;
}
}
7. 不同訪問控制規(guī)則
7.1 自帶默認(rèn)的 final 修飾
在 Java 中我們經(jīng)常會(huì)控制一個(gè)類不被修改或繼承,則需要 final
修飾符修飾;而在 Kotlin 中不要手動(dòng)添加 final
而是默認(rèn)就是 final
,如果需要讓這個(gè)類或方法被繼承和修改,就需要手動(dòng)添加 open
關(guān)鍵解除這個(gè)禁忌。
open class Animal(color: String, age: Int) {//open關(guān)鍵字打開final禁忌,使得Animal可以被繼承
open fun printInfo() {//open關(guān)鍵字打開final禁忌,使得printInfo可以被子類重寫
println("this is animal!")
}
}
class Dog(color: String, age: Int) : Animal(color, age) {
override fun printInfo() {
println("this is dog!")
}
}
我們也可以通過編譯上述代碼,看 Animal 類是否還存在 final 修飾符,來進(jìn)一步證明我們結(jié)論。
public class Animal {//沒有final可以被繼承
public void printInfo() {//沒有final可以被子類重寫
String var1 = "this is animal!";
boolean var2 = false;
System.out.println(var1);
}
public Animal(@NotNull String color, int age) {
Intrinsics.checkParameterIsNotNull(color, "color");
super();
}
}
7.2 可見性修飾符
在 Kotlin 中默認(rèn)修飾符與 Java 則不一樣,在 Kotlin 默認(rèn)是 public
而 Java 則默認(rèn)是 default
(包級(jí)可見性)。此外 Kotlin 中還存在獨(dú)有的 internal
訪問可見修飾符。下面列出一張對(duì)應(yīng)表格
修飾符 | 表示含義 | 與 Java 比較 |
---|---|---|
public | Kotlin 默認(rèn)修飾符,全局可見 | 與 Java 中顯式指定的 public 效果一致 |
protected | 受保護(hù)修飾符,類和子類可見 | 與 Java 一致,除了類和子類可見,其包內(nèi)也可見 |
private | 私有修飾符,只有本類可見,類外文件內(nèi)可見 | 只能類內(nèi)可見 |
internal | 模塊內(nèi)可見 | 無該修飾符 |
8. 總結(jié)
到這里有關(guān) Kotlin 中面向?qū)ο蟮牡谝徽揪徒Y(jié)束,回顧一下本篇文章主要介紹了 Kotlin 中類和對(duì)象定義和創(chuàng)建,以及類的構(gòu)造函數(shù)、init 初始化塊、可見性修飾符,并把這些特性語(yǔ)言一一和 Java 進(jìn)行對(duì)比,幫助快速掌握和理解。下篇文章將繼續(xù) Kotlin 面向?qū)ο蟮诙境橄蠛徒涌凇?/p>