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