上期主要分享了 From Java To Kotlin 1 :空安全、擴展、函數、Lambda。 這是 From Java to Kotlin 第二期。 From Java to Kotlin 關鍵在於 **思維的轉變**。 ...
上期主要分享了 From Java To Kotlin 1 :空安全、擴展、函數、Lambda。
這是 From Java to Kotlin 第二期。 From Java to Kotlin 關鍵在於 思維的轉變。
表達式思維
Kotlin 中大部分語句是表達式。 表達式思維是一種編程思維。 編程思維是一種非常抽象的概念,很多時候是只可意會不可言傳的。 不過,從某種程度上看,學習編程思維,比學習編程語法更重要。因為編程思維決定著我們的代碼整體的架構與風格,而具體的某個語法反而沒那麼大的影響力。當然,如果對 Kotlin 的語法沒有一個全面的認識,編程思維也只會是空中樓閣。就像,我們學會了基礎的漢字以後開始寫作文:學了漢字以後,如果沒掌握寫作的技巧,是寫不出好的文章的。同理,如果學了 Kotlin 語法,卻沒有掌握它的編程思維,也是寫不出優雅的 Kotlin 代碼的。
下麵我們看一段 Kotlin 代碼
//--- 1
var i = 0
if (data != null) {
i = data
}
//--- 2
var j = 0
if (data != null) {
j = data
} else {
j = getDefault()
println(j)
}
//--- 3
var k = 0
if (data != null) {
k = data
} else {
throw NullPointerException()
}
//--- 4
var x = 0
when (data) {
is Int -> x = data
else -> x = 0
}
//--- 5
var y = 0
try {
y = "Kotlin".toInt()
} catch (e: NumberFormatException) {
println(e)
y = 0
}
這些代碼,如果我們用平時寫 Java 時的思維來分析的話,是挑不出太多毛病的。但是站在 Kotlin 的角度,就完全不一樣了。利用 Kotlin 的語法,我們完全可以將代碼寫得更加簡潔,就像下麵這樣:
//--- 1
val i = data ?: 0
//--- 2
val j = data ?: getDefault().also { println(it) }
//--- 3
val k = data?: throw NullPointerException()
//--- 4
val x = when (data) {
is Int -> data
else -> 0
}
//--- 5
val y = try {
"Kotlin".toInt()
} catch (e: NumberFormatException) {
println(e)
0
}
這段代碼看起來就簡潔了不少,所以從 Java 轉到 Kotlin 要格外註意思維轉變,培養表達式思維。
這裡有個疑問:Kotlin 為什麼就能用這樣的方式寫代碼呢?其實這是因為:if、when、throw、try-catch 這些語法,在 Kotlin 當中都是表達式。
那麼,這個“表達式”到底是什麼呢?其實,與表達式(Expression)對應的,還有另一個概念,我們叫做語句(Statement)。
-
表達式(Expression),是一段可以產生值的代碼;
-
語句(Statement),則是一句不產生值的代碼。
我們可以簡單來概括一下:表達式(Expression)有值,而語句(Statement)不總有。
用一個更詳細的例子解釋:
val a = 1 // statement
println(a) // statement
// statement
var i = 0
if (data != null) {
i = data
}
// 1 + 2 是一個表達式,但是對b的賦值行為是statement
val b = 1 + 2
// if else 整體是一個表達式
// a > b是一個表達式, 子表達式
// a - b是一個表達式, 子表達式
// b - a是一個表達式, 子表達式。
fun minus(a: Int, b: Int) = if (a > b) a - b else b - a
// throw NotImplementedError() 是一個表達式
fun calculate(): Int = throw NotImplementedError()
這段代碼是描述了常見的 Kotlin 代碼模式,從它的註釋當中,我們其實可以總結出這樣幾個規律:
-
賦值語句,就是典型的 statement;
-
if 語法,既可以作為語句,也可以作為表達式;
-
語句與表達式,它們可能會出現在同一行代碼中,比如 val b = 1 + 2;
-
表達式還可能包含“子表達式”,就比如這裡的 minus 方法;
-
throw 語句,也可以作為表達式。
看到這裡,可能又有一個疑問,那就是:calculate() 這個函數難道不會引起編譯器報錯嗎?
// 函數返回值類型是Int,實際上卻拋出了異常,沒有返回Int
// ↓ ↓
fun calculate(): Int = throw NotImplementedError()
要想搞清楚這個疑問, 需要理解Kotlin的類型系統。
小結
- Koltin表達式思維是指時刻記住 Kotlin 大部分的語句都是表達式,它們可以產生返回值。利用這種思維,往往可以大大簡化代碼邏輯。
Kotlin 的類型系統
類、類型和子類型
-
類(class)是指一種數據類型,類定義定義對象的屬性和方法,可以用來創建對象實例,例如
class Person(val name: String)
,用於表示一個人的屬性和行為。 -
類型(type)是指一個_變數或表達式 **的 **數據類型_。類型可以用來描述變數或表達式的特征和限制(取值範圍和可用的操作)。在Kotlin中,每個變數或表達式都有一個確定的類型,例如Int、String、Boolean等,類型可以是可空的或非空的,例如
String?
或String
。 -
子類型(subtype)是指一個類型的子集,即一個類型的值可以賦值給另一個類型的變數或表達式。例如
class Student(name: String, val grade: Int) : Person(name)
中,Student
是Person
的子類型,String
是String?
的子類型 。
在 Kotlin 中,類和類型之間有一定的對應關係,但並不完全相同。一個類可以用於構造多個類型, 例如泛型類 List<T>
可以構造出 List<String>
、List<Int>
等不同的類型。一個類型也可以由多個類實現,例如介面類型 Runnable
可以由多個實現了 run()
方法的類實現。
子類型化
先看一段代碼:
非可空類型的 strNotNull:String ,可以賦值給 可空類型的strNullable:String? ; 可空類型的strNullable:String? 不可以賦值給 非可空類型的 strNotNull:String。
可以看出每一個Kotlin類都可以用於構造至少兩種類型。
根據子類型化的定義,String 是 String?的子類型。
看到這裡可能有個疑問?沒有繼承關係,String 並沒有 繼承 String?,為啥String是 String? 的子類型。
其實我也有, 經常開發 Java 會有一個誤區:認為只有繼承關係的類型之間才可以有父子類型關係。
因為在Java中,類與類型大部分情況下都是“等價”的(在Java泛型出現前)。事實上,“繼承”和“子類型化”是兩個完全不同的概念。子類型化的核心是一種類型的替代關係。
子類型化, 以下內容引用自維基百科
在編程語言理論中,子類型(動名詞,英語:subtyping(也有翻譯為子類型化))是一種類型多態的形式。這種形式下,子類型(名詞,英語:subtype)可以替換另一種相關的數據類型(超類型,英語:supertype)。也就是說,針對超類型元素進行操作的子程式、函數等程式元素,也可以操作相應的子類型。如果 S 是 T 的子類型,這種子類型關係通常寫作 S <: T,意思是在任何需要使用 T 類型對象的_環境中,都可以安全地使用_ S 類型的對象。
由於子類型關係的存在,某個對象可能同時屬於多種類型,因此,子類型(英語:subtyping)是一種類型多態的形式,也被稱作子類型多態(英語:subtype polymorphism)或者包含多態(英語:inclusion polymorphism)。
子類型與面向對象語言中(類或對象)的繼承是兩個概念。子類型反映了類型(即面向對象中的介面)之間的_關係_;而繼承反映了一類對象可以從另一類對象創造出來,是_語言特性 _的實現。因此,子類型也稱介面繼承;繼承稱作實現繼承。
子類型 - 維基百科,自由的百科全書
子類型化可表示為:
S <:T
以上S是T的子類,這意味著在需要T類型 值 的地方,S類型的 值 同樣適用,可以用 S 類型的 值 替換。
所以在前面的例子中, 雖然String與String?看起來沒有繼承關係,然而在我們需要用String?類型值的地方,顯然可以傳入一個類型為String的值,這在編譯上不會產生問題。反之卻不然。 所以String?是String的父類型。
繼承強調的是一種“實現上的復用”,而子類型化是一種類型語義的關係,與實現沒關係。對於 Java 語言,由於一般在聲明父子類型關係的同時也聲明瞭繼承的關係,所以造成了某種程度上的混淆。
類型系統
Kotlin 的類型還分為可空類型和不可空類型。Any 是所有非空類型的根類型;而 Any? 是所有可空類型的根類型。 我們猜測 Kotlin 的類型體系可能是這樣的:那Any 與 Any? 之間是什麼關係呢?
Any 、Any?與 Java 的 Object
Java 當中的 Object 類型,對應 Kotlin 的“Any?”類型。但兩者並不完全等價,因為 Kotlin 的 Any 可以沒有 wait()、notify() 之類的方法。因此,我們只能說 Kotlin 的“Any?”與 Java 的 Object 是大致對應的。
下麵是Java 代碼,它有三個方法,分別是可為空的 Object 類型、不可為空的 Object 類型,以及無註解的 Object 類型。
public class TestTypeJava {
@Nullable // 可空註解
public Object test() { return null; }
// 預設
public Object test1() { return null; }
@NotNull // 不可空註解
public Object test2() { return 1; }
}
上面的代碼通過 Convert Java File to Kotlin File
轉換成 Kotlin:
class TestTypeJava {
// 可空註解
fun test(): Any? {
return null
}
fun test1(): Any? { // 可以看出預設情況下, Java Object 對應 Kotlin Any?
return null
}
// 不可空註解
fun test2(): Any {
return 1
}
}
可以看出預設情況下,沒有註解標記可空信息的時候, Java Object 對應 Kotlin Any?。
有些時候Java代碼包含了可空性的信息,這些信息使用註解來表達。當代碼中出現了這樣的信息時,Kotlin就會使用它。因此Java中的@Nullable String被Kotlin當作String?,而@NotNull String就是String
如果沒有是否可空註解, Java類型會變成 Kotlin 中的平臺類型(後面會解釋)。
瞭解了 Any 和 Any?的關係,可以畫出關係圖
Unit 與 Void 與 void
先看一段 Java 代碼
public class PrintHello {
public void printHelloWorld() {
System.out.println("Hello World!");
}
}
轉成 Kotlin
class PrintHello {
fun printHelloWorld():Unit { // Redundant 'Unit' return type
println("Hello World!")
}
}
Java 的 void
關鍵字在 Kotlin 里是沒有的,取而代之的是一個叫做 Unit
的東西,
Unit 和 Java 的 void
真正的區別在於,void
是真的表示什麼都不返回,而 Kotlin 的 Unit
卻是一個真實存在的類型:
public object Unit {
override fun toString() = "kotlin.Unit"
}
它是一個 object
,也就是 Kotlin 里的單例類型或者說單例對象。當一個函數的返回值類型是 Unit
的時候,它是需要返回一個 Unit
類型的對象的:
fun printHelloWorld():Unit {
println("Hello World!")
return Unit // return Unit 可以省略
}
只不過因為它是個 object
,所以唯一能返回的值就是 Unit
本身。
這兩個 Unit
是不一樣的,上面的是 Unit
這個類型,下麵的是 Unit
這個單例對象,它倆長得一樣但是是不同的東西。註意了,這個並不是 Kotlin 給Unit
的特權,而是 object
本來就有的語法特性。如果有需要,也可以用同樣的格式來使用別的單例對象,是不會報錯的:
包括也可以這樣寫:
val unit: Unit = Unit
也是一樣的道理,等號左邊是類型,等號右邊是對象——當然這麼寫沒什麼實際作用啊,單例可以直接用。
object Zhangsan
fun getZhangsan(): Zhangsan { // 單例可以直接使用
return Zhangsan
}
因此,在結構上,Unit
並沒有任何特別之處,它只是 Kotlin 的 object
。除了對於函數返回值類型和返回值的自動補充之外,它的特殊之處更多地在於語義和用途的角度。它是由官方規定的,用於表示「什麼也不返回」的場景的返回值類型。但這隻是它被規定的用法而已,本質上它是一個實實在在的類型。在 Kotlin 中,不存在真正沒有返回值的函數,所有「沒有返回值」的函數實質上的返回值類型都是 Unit,而返回值也都是 Unit 這個單例對象。這是 Unit 和 Java 的 void 在本質上的不同之處。
Unit 相比 void 帶來什麼不同
Unit 去除了無返回值函數的特殊性和有返回值函數之間的本質區別,從而使得很多事情變得更加簡單,這種通用性為我們帶來了便利。
例子: 函數類型的函數參數
雖然不能說Java中的所有函數調用都是表達式,但是可以說Kotlin中的所有函數調用都是表達式。
是因為存在特例void,在Java中如果聲明的函數沒有返回值,那麼它就需要用void來修飾。如:
public void printHelloWorld() {
System.out.println("Hello World!");
}
因為 void 不是類型,所以 函數printHelloWorld()無法匹配 () -> Unit 函數類型
class VoidTest {
fun printHelloWorld1():Unit { // 作為參數時,就有函數類型 () -> Unit
println("Hello World!")
}
fun runTask(task: () -> Any) {
when (val result = task()) {
Unit -> println("result is Unit")
String -> println("result is a String: $result")
else -> println("result is an unknown type")
}
}
@Test
fun main1() {
val var1 = ::printHelloWorld1 // () -> Unit
runTask (var1) // () -> Unit
runTask { "This is string" } //:() -> String
runTask { 42 } // () -> Int
}
}
現在有了 Unit , fun printHelloWorld1():Unit 作為參數時,就有函數類型 () -> Unit 。
註意:在 Java 當中,Void 和 void 不是一回事(註意大小寫),前者是一個 Java 的類,後者是一個用於修飾方法的關鍵字。如下所示:
public final class Void {
@SuppressWarnings("unchecked")
public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");
private Void() {}
}
JAVA中Void類是一個不可實例化的占位符類,用來保存一個引用代表Java關鍵字void的Class對象。它的作用是在反射或泛型中表示void類型。 例如:Map介面的put方法需要兩個類型參數,如果我們只需要存儲鍵而不需要存儲值,就可以使用Void類作為類型參數
Map<String, Void> map = new HashMap<>(); map.put("key", null);。
瞭解了 Unit
和 Unit?
的關係後,可以畫出關係圖
Nothing
Nothing 是 Kotlin 所有類型的子類型。 Noting 的概念與 Any? 恰好相反。
Nothing 也叫底類型(BottomType)。
Nothing的源碼是這樣的:
public class Nothing private constructor()
可以看到它本身雖然是 public 的,但它的構造函數是 private 的,這就導致我們沒法創建它的實例;而且它不像 Unit 那樣是個 object:
public object Unit {
override fun toString() = "kotlin.Unit"
}
而是個普通的 class;並且在源碼里 Kotlin 也沒有幫我們創建它的實例。 這些條件加起來,結果就是:Nothing 這個類既沒有、也不會有任何的實例對象。 基於這樣的前提,當我們寫出這個函數聲明的時候:
fun nothing(): Nothing {
}
我們可能無法找到一個合適的值來返回,但是在編寫代碼時,我們必須返回一個值。這種情況下,我們遇到了一個悖論,即必須返回一個值,但卻永遠找不到合適的返回值
Nothing的作用: 作為函數 永遠不會返回結果
的提示
fun nothing() : Nothing {
throw RuntimeException("Nothing!")
}
根據Nothing的特性, Nothing 專門用於拋異常。
public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)
@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()
從上面這段代碼可以看出,Kotin 源碼中 throw 表達式的返回值類型是 Nothing。
throw 這個表達式的返回值是 Nothing 類型。而既然 Nothing 是所有類型的子類型,那麼它當然是可以賦值給任意其他類型的。 所以表達式思維中的問題就可以解答了
// 函數返回值類型是Int,實際上卻拋出了異常,沒有返回Int
// ↓ ↓
fun calculate(): Int = throw NotImplementedError()
作用二
Nothing 類的構造函數是私有的,因此我們無法構造出它的實例。當 Nothing 類型作為函數參數時,一個有趣的現象就出現了:
// 這是一個無法調用的函數,因為找不到合適的參數
fun show(msg: Nothing) {}
show(null) // 報錯
show(throw Exception()) // 雖然不報錯,但方法仍然不會調用
在這裡,我們定義了一個 show 函數,它的參數類型是 Nothing。由於 Nothing 的構造函數是私有的,我們將無法調用 show 函數,除非我們拋出異常,但這沒有意義。 這個概念在泛型星投影的時候是有應用的,具體後面會解釋。
作用三
而除此之外,Nothing 還有助於編譯器進行代碼流程的推斷。比如說,當一個表達式的返回值是 Nothing 的時候,就往往意味著它後面的語句不再有機會被執行。如下圖所示:
瞭解了 Nothing 和 Nothing?的關係後,可以畫出關係圖
平臺類型
image.png
平臺類型在Kotlin中表示為type!(如String!,Int!, CustomClass!)。 Kotlin平臺類型本質上就是Kotlin不知道可空性信息的類型,即可以當作可空類型,也可以當作非空類型。平臺類型只能來自Java,因為Java中所有的引用都可能為null,而Kotlin中對null有嚴格的檢查和限制。 但是在Kotlin中是禁止聲明平臺類型的變數的。
image.png
具體的代碼示例如下:
// Java 代碼
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// Kotlin 代碼
fun main() {
val person = Person() //
val name = person.name // name 是 String! 類型
println(name.length) // 可能拋出空指針異常
person.name = null // 允許賦值為 null
}
在這個例子中, name 是平臺類型,
因為它們來自於 Java 代碼。Kotlin 編譯器不會檢查它們是否為 null,所以需要程式員自己負責。如果要避免空指針異常,可以使用安全調用運算符(?.)或非空斷言運算符(!!)來處理平臺類型。
println(name?.length) // 安全調用,如果 name 為 null 則返回 null
println(name!!.length) // 非空斷言,如果 name 為 null 則拋出異常
平臺類型是指 Kotlin 和 Java 的互操作性問題, 在混合項目中要多加註意。
小結
-
Any 是所有非空類型的根類型,而 Any? 才是所有類型的根類型。
-
Unit 與 Java 的 void 類型相似,代表一個函數不需要返回值;而 Unit? 這個類型則沒有太多實際的意義。
-
當 Nothing 作為函數返回值時,意味著這個函數永遠不會返回結果,而且還會截斷程式的後續流程。Kotlin 編譯器也會根據這一點進行流程分析。
-
當 Nothing 作為函數參數時,就意味著這個函數永遠無法被正常調用。這在泛型星投影的時候是有一定應用的。
-
Nothing 可以看作是 Nothing? 的子類型,因此,Nothing 可以看作是 Kotlin 所有類型的底類型。
-
正是因為 Kotlin 在類型系統中加入了 Unit、Nothing 這兩個類型,才讓大部分無法產生值的語句搖身一變,成為了表達式。這也是“Kotlin 大部分的語句都是表達式”的根本原因。
泛型:讓類型更加安全
Kotlin 的泛型與 Java 一樣,都是一種語法糖,即只在源代碼中有泛型定義,到了class級別就被擦除了。 泛型(Generics)其實就是把類型參數化,真正的名字叫做類型參數,它的引入給強類型編程語言加入了更強的靈活性。
泛型的優點
-
類型安全:泛型可以在編譯時檢查類型,從而避免了在運行時出現類型不匹配的錯誤。這可以提高程式的可靠性和穩定性。
-
代碼重用:泛型可以使代碼更加通用和靈活,從而可以減少代碼的重覆和冗餘。例如,我們可以編寫一個通用的排序演算法,可以用於任何實現了 Comparable 介面的類型。
在 Java 中,我們常見的泛型有:泛型類、泛型介面、泛型方法和泛型屬性,Kotlin 泛型系統繼承了 Java 泛型系統,同時添加了一些強化的地方。
泛型介面/類(泛型類型)
定義泛型類型,是在類型名之後、主構造函數之前用尖括弧括起的大寫字母類型參數指定:
聲明泛型介面
Java:
//泛型介面
interface Drinks<T> {
T taste();
void price(T t);
}
Kotlin:
//泛型介面
interface Drinks<T> {
fun taste(): T
fun price(t: T)
}
聲明泛型類
Java
abstract class Color<T> {
T t;
abstract void printColor();
}
class Blue {
String color = "blue";
}
class BlueColor extends Color<Blue> {
public BlueColor(Blue1 t) {
this.t = t;
}
@Override
public void printColor() {
System.out.println("color:" + t.color);
}
}
Kotlin
abstract class Color<T>(var t: T/*泛型欄位*/) {
abstract fun printColor()
}
class Blue {
val color = "blue"
}
class BlueColor(t: Blue) : Color<Blue>(t) {
override fun printColor() {
println("color:${t.color}")
}
}
泛型欄位
定義泛型類型欄位,可以完整地寫明類型參數,如果編譯器可以自動推定類型參數,也可以省略類型參數:
abstract class Color<T>(var t: T/*泛型欄位*/) {
abstract fun printColor()
}
聲明泛型方法
Kotlin 泛型方法的聲明與 Java 相同,類型參數要放在方法名的前面:
Java
public static <T> T fromJson(String json, Class<T> tClass) {
T t = null;
try {
t = tClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return t;
}
Kotlin
fun <T> fromJson(json: String, tClass: Class<T>): T? {
/*獲取T的實例*/
val t: T? = tClass.newInstance()
return t
}
泛型約束
Java 中可以通過有界類型參數來限制參數類型的邊界,Kotlin中泛型約束也可以限制參數類型的上界:
Java
public static <T extends Comparable<T>> T maxOf(T a, T b) {
if (a.compareTo(b) > 0) return a;
else return b;
}
Kotlin
fun <T : Comparable<T>> maxOf(a: T, b: T): T {
return if (a > b) a else b
}
image.png
where關鍵字: 多個上界用 where
Java 中多約束: &
public static <T extends CharSequence & Comparable<T>> List<T> test(List<T> list, T threshold) {
return list.stream().filter(it -> it.compareTo(threshold) > 0).collect(Collectors.toList());
}
Kotin 中多約束:where
//多個上界的情況
fun <T> test(list: List<T>, threshold: T): List<T>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it }
}
所傳遞的類型T必須同時滿足 where 子句的所有條件,在上述示例中,類型 T 必須既實現了 CharSequence 也實現了 Comparable。
泛型形參&泛型實參
泛型類:
泛型函數:
泛型的型變
不變
先看一段 Java 代碼,我們知道在Java中 ,List無法賦值給List
public class JavaGeneryc {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple());
List<Fruit> fruits = apples; // 編譯錯誤
for (Fruit fruit : fruits) {
System.out.println(fruit);
}
}
}
class Fruit {
// 父類
}
class Apple extends Fruit {
// 子類
}
image.png
但是到了Kotlin這裡我們發現了一個奇怪的現象
fun main2(args: Array<String>) {
val stringList:List<String> = ArrayList<String>()
val anyList:List<Any> = stringList//編譯成功
}
image.png
在Kotlin中竟然能將List賦值給List,不是說好的Kotlin和Java的泛型原理是一樣的嗎?怎麼到了Kotlin中就變了?其實我們前面說的都沒錯,關鍵在於這兩個List並不是同一種類型。我們分別來看一下兩種List的定義:
雖然都叫List,也同樣支持泛型,但是Kotlin的List定義的泛型參數前面多了一個 out關鍵詞(加上out 發生協變 ),這個關鍵詞就對這個List的特性起到了很大的作用。 普通方式定義的泛型是不變的,簡單來說就是不管類型A和類型B是什麼關係,Generic與Generic(其中Generic代表泛型類)都沒有任何關係。比如,在Java中String是Oject的子類型,但List並不是List的子類型,在Kotlin中泛型的原理也是一樣的。Kotin 使用 out 才發生了變化。
**
out 位置與 in 位置
函數參數的類型叫作in位置,而函數返回類型叫作out位置
協變 :保留子類型化關係
如果在定義的泛型類和泛型方法的泛型參數前面加上out關鍵詞,說明這個泛型類及泛型方法是協變,簡單來說類型A是類型B的子類型,那麼Generic也是Generic的子類型,
**
image.png
協變點 (out 位置)
函數返回值類型為泛型參數。
協變的特征
只能消費,只能取
-
子類型化會被保留(Producer是Producer的子類型)
-
T只能用在out位置
image.png
interface Book
interface EduBook : Book
class BookStore<out T : Book> {
fun getBook(): T {
TODO()
}
}
fun covariant(){
// 教材書店
val eduBookStore: BookStore<EduBook> = BookStore<EduBook>()
// 書店
val bookStore: BookStore<Book> = eduBookStore // 協變,教輔書店是書店的子類型
val book: Book = bookStore.getBook()
val eduBook : EduBook = eduBookStore.getBook()
}
image.png
協變小結
•子類型 Derived 相容父類型 Base •生產者 Producer<Derived>相容 Producer
逆變: 反轉子類型化關係
如果在定義的泛型類和泛型方法的泛型參數前面加上in關鍵詞,說明這個泛型類及泛型方法是逆變,簡單來說類型A是類型B的子類型,那麼Generic是Generic****的子類型,類型父子關係反轉。
**
逆變點 (in 位置)
函數參數類型為泛型參數。
逆變的特征
只能生產,只能放入
-
子類型化會被反轉(Consumer是 Consumer的子類型)
-
T只能用在in位置
image.png
垃圾不能扔到乾垃圾桶,但是可以扔到垃圾桶。 乾垃圾可以扔到垃圾桶,也可以扔到垃圾桶。 由此可以看出垃圾桶可以替代乾垃圾桶, 所以乾垃圾桶是父類型。
open class Waste
// 乾垃圾
class DryWaste : Waste()
// 垃圾桶
class Dustbin<in T : Waste> {
fun put(t: T) {
TODO()
}
}
fun contravariant(){
val dustbin: Dustbin<Waste> = Dustbin<Waste>()
val dryWasteDustbin: Dustbin<DryWaste> = dustbin
val waste = Waste()
val dryWaste = DryWaste()
dustbin.put(waste)
dustbin.put(dryWaste)
// dryWasteDustbin.put(waste)
dryWasteDustbin.put(dryWaste)
}
聲明為 in ,在 out 位置使用,是會報錯的。
image.png
逆變小結
-
子類型 Derived 相容父類型 Base
-
消費者 Consumer相容 Consumer< Derived>
-
記憶小技巧: in 表示逆變, in 倒序過來是 ni(逆)。
型變小結
協變 | 逆變 | 不變型 |
---|---|---|
Producer | Consumer | MutableList: |
類的子類型化保留了:Producers是 Producer<Animal>的子類型 | 子類型化反轉了:Consumer是 Consumer的子類型 | 沒有子類型化 |
T只能在out 位置 | T只能在 in 位置 | T可以在任何位置 |
泛型中的out與in與 Java 上下界通配符關係
在Kotlin中out代表協變,in代表逆變,為了加深理解我們可以將Kotlin的協變看成Java的上界通配符,將逆變看成Java的下界通配符:
//Kotlin使用處協變
fun sumOfList(list: List<out Number>)
//Java上界通配符
void sumOfList(List<? extends Number> list)
//Kotlin使用處逆變
fun addNumbers(list: List<in Int>)
//Java下界通配符
void addNumbers(List<? super Integer> list)
小結
Java 泛型 | Java 中代碼示例 | Kotlin 中代碼示例 | Kotlin 泛型 |
---|---|---|---|
泛型類型 | class Box | class Box | 泛型類型 |
泛型方法 | T fromJson(String json, ClasstClass) | funfromJson(json: String, tClass: Class): T? | 泛型函數 |
有界類型參數 | class Box<T extends Comparable | class Box<T : Comparable> | 泛型約束 |
上界通配符 | void sumOfList(List<? extends Number> list) | fun sumOfList(list: List) | 使用處協變 |
下界通配符 | void addNumbers(List<? super Integer> list) | fun addNumbers(list: List) | 使用處逆變 |
總的來說,Kotlin 泛型更加簡潔安全,但是和 Java 一樣都是有類型擦除的,都屬於編譯時泛型。
下期分享:
星投影
註解 @UnsafeVariance
內聯特化(內聯強化) reified
系列
From Java To Kotlin:空安全、擴展、函數、Lambda很詳細,這次終於懂了
From Java To Kotlin 2:Kotlin 類型系統與泛型
作者:Seachal
出處:http://www.cnblogs.com/ZhangSeachal
如果,您認為閱讀這篇博客讓您有些收穫,不妨點擊一下左下角的【好文要頂】與【收藏該文】
如果,您希望更容易地發現我的新博客,不妨點擊一下左下角的【關註我】
如果,您對我的博客內容感興趣,請繼續關註我的後續博客,我是【Seachal】
我的GitHub
我的CSDN
我的簡書
本博文為學習、筆記之用,以筆記記錄作者學習的知識與學習後的思考或感悟。學習過程可能參考各種資料,如覺文中表述過分引用,請務必告知,以便迅速處理。如有錯漏,不吝賜教!