Kotlin(2) 泛型與集合

前言

以一個java老鳥的角度,如何去看 kotlin。 Java源代碼應該如何用Kotlin重構。 如何正確學習kotlin並且應用到實際開發中。本文將會探究。

本文分兩大塊,重難點和潛規則。

重難點:Kotlin中可以獨立出來講解的大塊知識點。提供單獨Demo。這部分大多數是Kotlin開創的新概念(相比於Java)。

潛規則:Kotlin是谷歌用來替換Java的,它和java百分百完全兼容,但是實際上java轉成kotlin之後,需要我們手動修改很多東西,甚至某些部分必須打散重構來達到最優編碼。其中,kotlin的某些特性和java不同,甚至完全反轉。這部分知識點比較零碎,單獨Demo不方便提供,就以小例子的形式來寫。

正文大綱

  • 重難點

    • lambda以及操作符
    • 高階函數以及操作符
    • Kotlin泛型
    • 集合操作
    • 協程
    • 操作符重載
  • 潛規則

    • Kotlin文件和類不存在一對一關係

    • 共生體

    • 繼承

    • 修飾符

    • 空指針問題

正文

重難點

Kotlin泛型

類型擦除

我們在編碼java的時候,寫一個泛型類,可能是這樣的

class Plate<T>{
    T t;
 
	Plate(T t){
        this.t = t;
    }
    
    T get(){
        return t;
    }
    
    void set(T t){
        this.t =
            t;
    }
}

以上是java代碼。

Java泛型是僞泛型,在編譯之後,所有的泛型寫法都會被移除,而會用實際的類型去替換。

mian函數運行的時候, 被移除。而原來的T,就變成了Object。

所以,Plate的字節碼反編譯過來就應該是

class Plate{
    Object t;
 
	Plate(Object t){
        this.t = t;
    }
    
    Object get(){
        return t;
    }
    
    void set(Object t){
        this.t = t;
    }
}

那麼既然運行的時候,泛型限制全都沒有了。那麼怎麼保證 泛型的作用呢?

答案:編碼的時候,編譯器幫我們進行校驗。

strList.add(123);//報錯

PECS 法則 和 上下邊界的問題

public class Panzi<T> {
    T mT;
    public Panzi(T t) { mT = t;}
    public T get() { return mT; }
    public void set(T t) {  mT = t; }
}

class Fruit {}
class Banana extends Fruit {}
class Apple extends Fruit {}
 Panzi<Apple> applePanzi = new Panzi<>(new Apple());
 Panzi<Fruit> fruitPanzi = new Panzi<>(new Fruit());
 fruitPanzi = applePanzi;

雖然 Apple和Fruit是父子繼承關係。但是Panzi 和 Panzi是半毛錢關係都沒有,如果你想fruitPanzi = applePanzi ,把後者賦值給前者,是會報編譯錯誤的。

如果想讓裝水果的盤子和 裝 蘋果的盤子發生一點關係,能夠後者賦值給前者.

Panzi<Apple> applePanzi = new Panzi<>(new Apple());
Panzi<? extends Fruit> fruitPanzi = new Panzi<>(new Fruit());
fruitPanzi = applePanzi;

那就必須使用到上下邊界的關鍵字 extends

extends 在泛型中表示指定上界,也就是說,實際類型都必須在Fruit之下(包括Fruit自己)。那麼既然apple也是Fruit的子類,那麼賦值就可以做到。

  • PECS法則

    剛纔說到了上邊界 extends。而下邊界是 super關鍵字

    Panzi<? extends Fruit> extendsFruit = new Panzi<>(new Apple());
    Panzi<? super Fruit> superFruit = new Panzi<>(new Fruit());
    

    super關鍵字,在泛型中表示定義下界,實際類型必須在Fruit之上,同時也在Object之下(包括Fruit和Object)

    所以會出現這麼一個情況:

    Panzi<? extends Fruit> extendsFruit = new Panzi<>(new Apple());
    Panzi<? super Fruit> superFruit = new Panzi<>(new Object());
    

    我們有這麼兩個Panzi,前者是 Fruit作爲泛型上界,一個是Fruit作爲下界。

    現在,我們從Panzi中去調用get/set方法。會發現。

    PE:

     extendsFruit.set(new Apple()); // 編譯報錯!
     Fruit fruit2 = extendsFruit.get();// 編譯正常
    

    爲何?因爲 Fruit作爲上界,我get出來的類型可以確定一定是Fruit類型的。但是我set進去的時候,JVM無法判定實際類型(因爲泛型被擦除,JVM只人爲set(Object t) 的參數是一個Object),JVM要求是Object,但是你卻給了一個Apple,編譯器無法處理。所以乾脆 java的泛型,? extends 定義了上界,只允許get,不允許set。這就是PECS中的PE,意思就是 Pruducer Extends ,生產者 Extends,只取不存。

    相對應:

    CS: 則是 Cunsumer super 消費者只存不取。

    Object object = superFruit.get(); //get,get出來雖然不報錯,但是沒有任何意義。因爲不能確定類型,只知道是一個Object,無法調用API
    superFruit.set(new Fruit());// 但是set進去的時候,可以確定一定是一個Fruit的
    

    這就是java泛型的 PECS法則.

kotlin泛型使用實例

java泛型裏面比較糾結的難點就是類型擦除和PECS法則了。

那麼kotlin泛型,原理上和java泛型和沒有區別。只是寫法上有了區別。

open class Fruit
class Apple : Fruit()
class Banana : Fruit()
class Panzi<T>(t: T) {
    var t: T = t
    fun get(): T {
        return t
    }
    fun set(t: T) {
        this.t = t
    }
}
    fun test1() {
        // 試試能不能相互賦值
        var fruitPanzi: Panzi<Fruit> = Panzi(Fruit()) //一個水果盤子
        var applePanzi: Panzi<Apple> = Panzi(Apple()) //一個蘋果盤子
        //試試相互賦值
		//        fruitPanzi = applePanzi  // 編譯報錯
		//        applePanzi = fruitPanzi  // 編譯報錯
        //雙方完全是不相干的類,不能相互賦值 ,
    }

    /**
     * 加邊界之後再賦值
     */
    fun test2() {
        //如果你非要認爲蘋果盤子歸屬於水果盤子,那麼可以這樣
        var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit()) //一個水果盤子
        var applePanzi2: Panzi<Apple> = Panzi(Apple()) //一個蘋果盤子

        fruitPanzi2 = applePanzi2  //那麼這就是out決定泛型上邊界的案例
    }

    /**
     * PECS法則,OUT表示 ? extends 決定上界,上界生產者只取不存
     */
    fun test3() {
        //看一下get set方法
        // 決定上界之後的泛型,只取不存
        var fruitPanzi2: Panzi<out Fruit> = Panzi(Fruit())
        fruitPanzi2.get()
		//        fruitPanzi2.set(Apple())  // 這裏編譯報錯,和java泛型的表現一樣
    }

    /**
     * PECS法則,IN表示 ? super 決定下界,下界消費者,只存不取
     */
    fun test4() {
        //試試泛型下界 in
        var fruitPanzi: Panzi<in Fruit> = Panzi(Fruit())
        fruitPanzi.set(Fruit())//可以set,但是看看get
        val get = fruitPanzi.get()//不會報錯,get出來的類型就完全不能確定了,只知道是 頂級類Any? 的子類,獲得它也沒有意義

    }

集合操作

Kotlin的集合,並沒有重新開創一套規則,它的底層依然是java的Collection。Kotlin提供了可變集合和不可變集合的接口。

  • 不可變集合:List,Set,Map (內部元素不可以 增減 或者 修改,在定義的時候就已經將容量和內部元素定死)

  • 不可變集合: MutableList , MutableSet , MutableMap (聲明的時候可以隨意指定初始值,後續可以隨意增刪和修改內部元素)

集合操作分爲:對象的創建api的調用

對象的創建

方式有多種,以不可變集合 List 爲例,kotlin的List底層和Java的List一致,底層數據結構是數組。

靜態指定元素值

fun main() {
    val listOf = listOf<String>("str1", "str2", "str3")
    listOf.forEach { print("$it ") }
}

執行結果:

str1 str2 str3

通過動態創建過程來指定元素值

fun main() {

    val list = List(3) {
        "str$it"
    }
    list.forEach { print("$it ") }
}

執行結果:

str0 str1 str2 

api的調用

對象已經創建,我們要利用kotlin提供的方法來完成業務代碼。

一級難度api(all,any,count,find,groupBy)
fun testCollectionFun() {
    val ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)
    //那麼是不是所有的元素都大於10

    ages.apply { print("all:") }.all { it > 10 }.apply { println(this) } //結果是false

    //是不是存在任意一個元素大於10
    ages.apply { print("any:") }.any { it > 10 }.apply { println(this) }

    // 符合指定條件的元素個數
    ages.apply { print("count:") }.count { it < 10 }.apply { println(this) }

    //找到第一個符合條件的元素
    ages.apply { print("find:") }.find { it > 10 }.apply { println(this) }

    // 按照條件進行分組
    val groupBy = ages.apply { print("groupBy:") }.groupBy { it > 10 }
    groupBy[false].apply { println(this) }
    groupBy[true].apply { println(this) }

}

fun main() {
    testCollectionFun()
}

針對數組元素的簡單判斷,上述提供了簡明的示例代碼,用List爲例,至於Set和Map類似。可以自主去推斷寫法。

執行結果:

all:false
any:true
count:7
find:100
groupBy:[1, 2, 3, 4, 5, 6, 7]
[100, 200]
二級難度api (filter,map,flatMap,flatten)
  • filter和map
fun testCollectionFun2() {
    //二級難度api
    val ages = listOf<Int>(1, 2, 3, 4, 5, 6, 7, 100, 200)
    // 只保留大於10的元素,並返回一個新數組
    ages.filter { it > 10 }.apply { println(this) }
    //遍歷List的所有元素,根據條件返回值,創建新的元素內容並放到新List中返回出來
    ages.map { if (it > 10) "大於10" else "小於等於10" }.apply { println(this) }
}
fun main() {
    testCollectionFun2()
}

執行結果:

[100, 200]
[小於等於10, 小於等於10, 小於等於10, 小於等於10, 小於等於10, 小於等於10, 小於等於10, 大於10, 大於10]
  • flatMap,因爲稍複雜
// 比如一個學生,作爲一個實體
class Student(name: String, math: Int, chinese: Int) {
    val name: String = name
    val score = Score(math, chinese)
}

//學生的成績分數作爲一個主體
class Score(math: Int, chinese: Int) {
    val math: Int = math
    val chinese: Int = chinese
}

fun testFlatMap() {
    val students = listOf(
            Student("zero", 100, 80),
            Student("alvin", 70, 70),
            Student("lance", 90, 60)
    )
    //我只想統計所有的數學成績的總分和平均分,怎麼辦
    students.flatMap { listOf(it.score.math) }.apply {
        var total = 0
        this.forEach {
            total += it
        }
        println("數學成績的總分是:$total  平均分是:${total / this.size}")
    }
}

fun main() {
    testFlatMap()
}

執行結果:

數學成績的總分是:260  平均分是:86

當面對複雜數據結構時,我們想要提煉出其中的某一層數據,並不關心其他無關字段。就適合使用 flatMap 進行扁平化提煉

  • flatten

意味:“平鋪”

fun testFlatten(){
    val list = listOf(listOf("Str1","Str3"), listOf("Str4","Str2"))
    val listsOfList = list.flatten()
    println("平鋪之前:$list \n平鋪之後$listsOfList")
}

fun main() {
    testFlatten()
}

執行結果:

平鋪之前:[[Str1, Str3], [Str4, Str2]] 
平鋪之後[Str1, Str3, Str4, Str2]

在存在List嵌套的情況下,flatten可以把複合式的數據結構 變成 扁平式的。它和flatMap不同,flatMap適合在複雜數據結構中在指定的層級形成一個集合,方便統計和計算。而flatten則更適用於類型相似的集合嵌套扁平化操作。適用場景還是有差別的。


~ o(* ̄▽ ̄*)ブ小編更新了一份“金三銀四”面試資源包,在後臺私信即可免費領取,手慢無~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章