第8章 泛型

第8章 泛型

通常情況的類和函數(shù),我們只需要使用具體的類型即可:要么是基本類型,要么是自定義的類。但是在集合類的場景下,我們通常需要編寫可以應(yīng)用于多種類型的代碼,我們最簡單原始的做法是,針對每一種類型,寫一套刻板的代碼。這樣做,代碼復(fù)用率會很低,抽象也沒有做好。我們能不能把“類型”也抽象成參數(shù)呢?是的,當(dāng)然可以。

Java 5 中引入泛型機(jī)制,實(shí)現(xiàn)了“參數(shù)化類型”(Parameterized Type)。參數(shù)化類型,顧名思義就是將類型由原來的具體的類型參數(shù)化,類似于方法中的變量參數(shù),此時類型也定義成參數(shù)形式,我們稱之為類型參數(shù),然后在使用時傳入具體的類型(類型實(shí)參)。

我們知道,在數(shù)學(xué)中泛函是以函數(shù)為自變量的函數(shù)。類比的來理解,編程中的泛型就是以類型為變量的類型,即參數(shù)化類型。這樣的變量參數(shù)就叫類型參數(shù)(Type Parameters)。

本章我們來一起學(xué)習(xí)一下Kotlin泛型的相關(guān)知識。

8.1 為何引入泛型

《Java編程思想 》(第4版)中提到:有許多原因促成了泛型的出現(xiàn),而最引人注意的一個原因,就是為了創(chuàng)建容器類 (集合類)。

集合類可以說是我們在寫代碼過程中最最常用的類之一。我們先來看下沒有泛型之前,我們的集合類是怎樣持有對象的。在Java中,Object類是所有類的根類。為了集合類的通用性,把元素的類型定義為Object,當(dāng)放入具體的類型的時候,再作相應(yīng)的強(qiáng)制類型轉(zhuǎn)換。

這是一個示例代碼:

class RawArrayList {
    public int length = 0;
    private Object[] elements; // 把元素的類型定義為Object

    public RawArrayList(int length) {
        this.length = length;
        this.elements = new Object[length];
    }

    public Object get(int index) {
        return elements[index];
    }

    public void add(int index, Object element) {
        elements[index] = element;
    }
}

一個簡單的測試代碼如下

public class RawTypeDemo {

    public static void main(String[] args) {
        RawArrayList rawArrayList = new RawArrayList(4);
        rawArrayList.add(0, "a");
        rawArrayList.add(1, "b");
        System.out.println(rawArrayList);

        String a = (String)rawArrayList.get(0); 
        System.out.println(a);

        String b = (String)rawArrayList.get(1);
        System.out.println(b);

        rawArrayList.add(2, 200);
        rawArrayList.add(3, 300);
        System.out.println(rawArrayList);

        int c = (int)rawArrayList.get(2);
        int d = (int)rawArrayList.get(3);
        System.out.println(c);
        System.out.println(d);

        String x = (String)rawArrayList.get(2); //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        System.out.println(x);

    }

}

我們可以看出,在使用原生態(tài)類型(raw type)實(shí)現(xiàn)的集合類中,我們使用的是Object[]數(shù)組。這種實(shí)現(xiàn)方式,存在的問題有兩個:

  1. 向集合中添加對象元素的時候,沒有對元素的類型進(jìn)行檢查,也就是說,我們往集合中添加任意對象,編譯器都不會報(bào)錯。

  2. 當(dāng)我們從集合中獲取一個值的時候,我們不能都使用Object類型,需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換。而這個轉(zhuǎn)換過程由于在添加元素的時候沒有作任何的類型的限制跟檢查,所以容易出錯。例如上面代碼中的:

String x = (String)rawArrayList.get(2); //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

對于這行代碼,編譯時不會報(bào)錯,但是運(yùn)行時會拋出類型轉(zhuǎn)換錯誤。能不能讓編譯器來解決這樣的樣板化的類型轉(zhuǎn)換代碼呢?當(dāng)我們向rawArrayList 添加元素的時候

rawArrayList.add(0, "a");

就限定其元素類型只能為String,那么在后面的獲取元素的時候,自動強(qiáng)制轉(zhuǎn)型為String 呢?

 String a = (String)rawArrayList.get(0);

這個元素類型 String 的信息,我們存放到 一個“類型參數(shù)”中,然后在編譯器層面引入相應(yīng)的類型檢查和自動轉(zhuǎn)換機(jī)制,這樣就可以解決這個類型安全使用的問題。這也正是引入的泛型的基本思想。

泛型最主要的優(yōu)點(diǎn)就是讓編譯器追蹤參數(shù)類型,執(zhí)行類型檢查和類型轉(zhuǎn)換。因?yàn)橛删幾g器來保證類型轉(zhuǎn)換不會失敗。如果依賴我們程序員自己去追蹤對象類型和執(zhí)行轉(zhuǎn)換,那么運(yùn)行時產(chǎn)生的錯誤將很難去定位和調(diào)試,然而有了泛型,編譯器 可以幫助我們執(zhí)行大量的類型檢查,并且可以檢測出更多的編譯時錯誤。在這一點(diǎn)上,泛型跟我們第3章中所講到的“可空類型”實(shí)現(xiàn)的空指針安全,在思想上有著異曲同工之妙。

8.2 在類、接口和函數(shù)上使用泛型

泛型類、泛型接口和泛型方法具備可重用性、類型安全和高效等優(yōu)點(diǎn)。在集合類API中大量地使用了泛型。在Java 中我們可以為類、接口和方法分別定義泛型參數(shù),在Kotlin中也同樣支持。本節(jié)我們分別介紹Kotlin中的泛型接口、泛型類和泛型函數(shù)。

8.2.1 泛型接口

我們舉一個簡單的Kotlin泛型接口的例子。

interface Generator<T> { // 類型參數(shù)放在接口名稱后面: <T> 
    operator fun next(): T  // 接口函數(shù)中直接使用類型 T
}

測試代碼

fun testGenerator() {
    val gen = object : Generator<Int> { // 對象表達(dá)式
        override fun next(): Int {
            return Random().nextInt(10)
        }
    }
    println(gen.next())
}

這里我們使用object 關(guān)鍵字來聲明一個Generator實(shí)現(xiàn)類,并在lambda表達(dá)式中實(shí)現(xiàn)了next() 函數(shù)。

Kotlin 中 Map 和 MutableMap 接口的定義也是一個典型的泛型接口的例子。

public interface Map<K, out V> {
    ...
    public fun containsKey(key: K): Boolean
    public fun containsValue(value: @UnsafeVariance V): Boolean
    public operator fun get(key: K): V?
    ...
    public val keys: Set<K>
    public val values: Collection<V>
    public val entries: Set<Map.Entry<K, V>>
}

public interface MutableMap<K, V> : Map<K, V> {
    public fun put(key: K, value: V): V?
    public fun remove(key: K): V?
    public fun putAll(from: Map<out K, V>): Unit
    ...
}

例如,我們使用 mutableMapOf 函數(shù)來實(shí)例化一個可變Map

>>> val map = mutableMapOf<Int,String>(1 to "a", 2 to "b", 3 to "c")
>>> map
{1=a, 2=b, 3=c}

其中,mutableMapOf 函數(shù)簽名如下

fun <K, V> mutableMapOf(vararg pairs: Pair<K, V>): MutableMap<K, V>

這里類型參數(shù) K,V 當(dāng)泛型類型被實(shí)例化和使用時,它將被一個實(shí)際的類型參數(shù)所替代。在 mutableMapOf<Int,String> 中,放置K, V 的位置被具體的Int 和 String 類型所替代。

泛型可以用來限制集合類持有的對象類型,這樣使得類型更加安全。當(dāng)我們在一個集合類里面放入了錯誤類型的對象,編譯器就會報(bào)錯:

>>> map.put("5","e")
error: type mismatch: inferred type is String but Int was expected
map.put("5","e")
        ^

Kotlin中有類型推斷的功能,有些類型參數(shù)可以直接省略不寫。mutableMapOf<Int,String> 后面的類型參數(shù) <Int,String> 可以省掉不寫:

>>> val map = mutableMapOf(1 to "a", 2 to "b", 3 to "c")
>>> map
{1=a, 2=b, 3=c}

8.2.2 泛型類

我們直接聲明一個帶類型參數(shù)的 Container 類

class Container<K, V>(var key: K, var value: V)

為了方便測試,我們重寫 toString() 函數(shù)

class Container<K, V>(var key: K, var value: V){ // 在類名后面聲明泛型參數(shù)<K, V> , 多個泛型使用逗號隔開
    override fun toString(): String {
        return "Container(key=$key, value=$value)"
    }
}

測試代碼

fun testContainer() {
    val container = Container<Int, String>(1, "A") // <K, V> 被具體化為<Int, String>
    println(container) // container = Container(key=1, value=A)
}

8.2.3 泛型函數(shù)

在泛型接口和泛型類中,我們都在類名和接口名后面聲明了泛型參數(shù)。而實(shí)際上,我們也可以直接在類或接口中的函數(shù),或者直接在包級函數(shù)中直接聲明泛型參數(shù)。代碼示例如下

class GenericClass {
    fun <T> console(t: T) { // 類中的泛型函數(shù)
        println(t)
    }
}
interface GenericInterface {
    fun <T> console(t: T) // 接口中的泛型函數(shù)
}
fun <T : Comparable<T>> gt(x: T, y: T): Boolean { // 包中的泛型函數(shù)
    return x > y
}

8.3 類型上界

在上面的例子中,我們有看到 gt(x:T, y:T) 函數(shù)的簽名中有個 T : Comparable<T>

fun <T : Comparable<T>> gt(x: T, y: T): Boolean

這里的 T : Comparable<T> ,表示 Comparable<T>是類型 T 的上界。也就是告訴編譯器,類型參數(shù) T 代表的都是實(shí)現(xiàn)了 Comparable<T> 接口的類,這樣等于告訴編譯器它們都實(shí)現(xiàn)了compareTo方法。如果沒有這個類型上界聲明,我們就無法直接使用 compareTo ( > )操作符。也就是說,下面的代碼編譯不通過

fun <T> gt(x: T, y: T): Boolean {
    return x > y // 編譯不通過
}

8.4 協(xié)變與逆變

我們來看一個問題場景。首先,我們有下面的存在父子關(guān)系的類型

open class Food
open class Fruit : Food()
class Apple : Fruit()
class Banana : Fruit()
class Grape : Fruit()

然后,我們有下面的兩個函數(shù)

object GenericTypeDemo {

    fun addFruit(fruit: MutableList<Fruit>) {
        // TODO
    }

    fun getFruit(fruit: MutableList<Fruit>) {
        // TODO
    }
}

這個時候,我們可以這樣調(diào)用上面的兩個函數(shù)

    val fruits: MutableList<Fruit> = mutableListOf(Fruit(), Fruit(), Fruit())
    GenericTypeDemo.addFruit(fruits)
    GenericTypeDemo.getFruit(fruits)

現(xiàn)在,我們又有一個存放Apple的List

val apples: MutableList<Apple> = mutableListOf(Apple(), Apple(), Apple())

由于Kotlin中的泛型跟Java一樣是非協(xié)變的,下面的調(diào)用是編譯不通過的

GenericTypeDemo.addFruit(apples) // type mismatch
GenericTypeDemo.getFruit(apples) // type mismatch

如果沒有協(xié)變,那么我們不得不再添加兩個函數(shù)

object GenericTypeDemo {

    fun addFruit(fruit: MutableList<Fruit>) {
        // TODO
    }

    fun getFruit(fruit: MutableList<Fruit>) {
        // TODO
    }

    fun addApple(apple: MutableList<Apple>) {
        // TODO
    }

    fun getApple(apple: MutableList<Apple>) {
        // TODO
    }

}

我們一眼就能看出,這是重復(fù)的樣板代碼。我們能不能讓 MutableList<Fruit> 成為 MutableList<Apple> 的父類型呢? Java泛型中引入了類型通配符的概念來解決這個問題。Java 泛型的通配符有兩種形式:

  • 子類型上界限定符 ? extends T 指定類型參數(shù)的上限(該類型必須是類型T或者它的子類型)。也就是說MutableList<? extends Fruit> 是 MutableList<Apple> 的父類型。 Kotlin中使用 MutableList<out Fruit> 來表示。

  • 超類型下界限定符 ? super T 指定類型參數(shù)的下限(該類型必須是類型T或者它的父類型)。也就是說MutableList<? super Fruit> 是 MutableList<Object>的父類型。Kotlin中使用 MutableList<in Fruit> 來表示。

這里的問號 (?) , 我們稱之為類型通配符(Type Wildcard)。通配符在類型系統(tǒng)中具有重要的意義,它們?yōu)橐粋€泛型類所指定的類型集合提供了一個有用的類型范圍。

Number 類型(簡記為F) 是 Integer 類型(簡記為C)的父類型,我們把這種父子類型關(guān)系簡記為:C => F (C 繼承 F);而List<Number>, List<Integer>的代表的泛型類型信息,我們分別簡記為 f(F), f(C)。

那么我們可以這么來描述協(xié)變和逆變:

當(dāng) C => F 時, 如果有 f(C) => f(F), 那么 f 叫做協(xié)變;
當(dāng) C => F 時, 如果有 f(F) => f(C), 那么 f 叫做逆變。
如果上面兩種關(guān)系都不成立則叫做不變。

協(xié)變與逆變可以用下圖來簡單說明

協(xié)變與逆變

協(xié)變和逆協(xié)變都是類型安全的。

8.4.1 協(xié)變

在Java中數(shù)組是協(xié)變的,下面的代碼是可以正確編譯運(yùn)行的:

        Integer[] ints = new Integer[3];
        ints[0] = 0;
        ints[1] = 1;
        ints[2] = 2;
        Number[] numbers = new Number[3];
        numbers = ints;
        for (Number n : numbers) {
            System.out.println(n);
        }

在Java中,因?yàn)?Integer 是 Number 的子類型,數(shù)組類型 Integer[] 也是 Number[] 的子類型,因此在任何需要 Number[] 值的地方都可以提供一個 Integer[] 值。Java中數(shù)組協(xié)變的意思可以用下圖簡單說明

Java 數(shù)組協(xié)變

Java中泛型是非協(xié)變的。如下圖所示

泛型不是協(xié)變的

也就是說, List<Integer> 不是 List<Number> 的子類型,試圖在要求 List<Number> 的位置提供 List<Integer> 是一個類型錯誤。下面的代碼,編譯器是會直接報(bào)錯的:

        List<Integer> integerList = new ArrayList<>();
        integerList.add(0);
        integerList.add(1);
        integerList.add(2);
        List<Number> numberList = new ArrayList<>();
        numberList = integerList; // 編譯錯誤:類型不兼容

編譯器報(bào)錯提示如下:

編譯錯誤:類型不兼容

Java中泛型和數(shù)組的不同行為,的確引起了許多混亂。就算我們使用通配符,這樣寫:

List<? extends Number> list = new ArrayList<Number>();  
list.add(new Integer(1)); //error  

仍然是報(bào)錯的:

add元素錯誤信息

這通常會讓我們感到困惑:為什么Number的對象可以由Integer實(shí)例化,而ArrayList<Number>的對象卻不能由ArrayList<Integer>實(shí)例化?list中的<? extends Number>聲明其元素是Number或Number的派生類,為什么不能add Integer? 為了弄清楚這些問題,我們需要了解Java中的逆變和協(xié)變以及泛型中通配符用法。

List<? extends Number> list = new ArrayList<>();  

這里的子類型 C 就是 Number類及其子類(例如Number、Integer、Float等) ,表示的是 Number 類或其子類。父類 F 就是上界通配符: ? extends Number。

當(dāng) C => F ,這個關(guān)系成立:f(C) => f(F) , 這就是協(xié)變。我們把 f(F) 具體化為 List<? extends Number>, f(C) 具體化為 List<Integer> 、List<Float>等。 協(xié)變代表的意義就是: List<? extends Number> 是 List<Integer> 、List<Float>等的父類型。如下圖所示

協(xié)變

代碼示例

List<? extends Number> list1 = new ArrayList<Integer>();  
List<? extends Number> list2 = new ArrayList<Float>();  

但是這里不能向list1、list2添加除null以外的任意對象。

        list1.add(null); // ok
        list2.add(null);// ok
        list1.add(new Integer(1)); // error
        list2.add(new Float(1.1f)); // error

List<Integer>可以添加Interger及其子類;
List<Float>可以添加Float及其子類;
List<Integer>、List<Float>等都是 List<? extends Number>的子類型。

現(xiàn)在問題來了,如果能將Float的子類添加到 List<? extends Number>中,那么也能將Integer的子類添加到 List<? extends Number>中, 那么這時候 List<? extends Number> 里面將會持有各種Number子類型的對象(Byte,Integer,F(xiàn)loat,Double等)。而這個時候,當(dāng)我們再使用這個list的時候,元素的類型就會混亂。我們不知道哪個元素會是Integer或者Float 。Java為了保護(hù)其類型一致,禁止向List<? extends Number>添加任意對象,不過可以添加空對象null。

禁止向List<? extends Number>添加任意對象

8.4.2 逆變

我們先用一段代碼舉例

List<? super Number> list = new ArrayList<Object>();  

這里的子類型 C 是 ? super Number , 父類型 F 是 Number 的父類型(例如:Object類)。

當(dāng) C => F , 有 f(F) => f(C) , 這就是逆變。我們把 f (C) 具體化為 List<? super Number> ,f(F) 具體化為List<Object> 。逆變的意思就是說 List<? super Number> 是 List<Object> 的父類型。如下圖所示

逆變

代碼示例:

List<? super Number> list3 = new ArrayList<Number>();  
List<? super Number> list4 = new ArrayList<Object>();  
list3.add(new Integer(3));  
list4.add(new Integer(4));  

在逆變類型中,我們可以向其中添加元素。例如,我們可以向 List<? super Number > list4 變量中添加Number及其子類對象。

8.4.3 PECS

現(xiàn)在問題來了:我們什么時候用extends什么時候用super呢?《Effective Java》給出了答案:

PECS: producer-extends, consumer-super

下面我們通過實(shí)例來說明PECS的具體含義。

首先,我們聲明一個簡單的Stack 泛型類如下

public class Stack<E>{  
    public Stack();  
    public void push(E e):  
    public E pop();  
    public boolean isEmpty();  
}  

要實(shí)現(xiàn)pushAll(Iterable<E> src)方法,將src的元素逐一入棧

public void pushAll(Iterable<E> src){  
    for(E e : src)  
        push(e)  
}  

假設(shè)有一個Stack<Number>(類型參數(shù)E 具體化為 Number 類型)實(shí)例化的對象stack,src有 Iterable<Integer> 與 Iterable<Float>,那么在調(diào)用pushAll方法時會發(fā)生type mismatch錯誤,因?yàn)镴ava中泛型是不可變的,Iterable<Integer>與 Iterable<Float>都不是Iterable<Number>的子類型。

因此,pushAll(Iterable<E> src)方法簽名應(yīng)改為

// Wildcard type for parameter that serves as an E producer  
public void pushAll(Iterable<? extends E> src) {  
    for (E e : src)   // out T, 從src中讀取數(shù)據(jù),producer-extends
        push(e);  
}  

這樣就實(shí)現(xiàn)了泛型的協(xié)變。同時,我們從src中讀取的數(shù)據(jù)都能保證是E類型及其子類型的對象。

現(xiàn)在,我們再看 popAll(Collection<E> dst)方法,該方法將Stack中的元素依次取出add到dst中,如果不用通配符實(shí)現(xiàn):

// popAll method without wildcard type - deficient!  
public void popAll(Collection<E> dst) {  
    while (!isEmpty())  
        dst.add(pop());    
}  

同樣地,假設(shè)有一個實(shí)例化Stack<Number>的對象stack,dst為Collection<Object>;調(diào)用popAll方法是會發(fā)生type mismatch錯誤,因?yàn)镃ollection<Object>不是Collection<Number>的子類型。

因而,popAll(Collection<E> dst) 方法應(yīng)改為:

// Wildcard type for parameter that serves as an E consumer  
public void popAll(Collection<? super E> dst) {  // 保證dst中的元素都是E類型或者E的父類型
    while (!isEmpty())  
        dst.add(pop());   // in T, 向dst中寫入數(shù)據(jù), consumer-super
}  

因?yàn)?pop() 返回的數(shù)據(jù)類型是E, 而dst中的元素都是E類型或者E的父類型,所以我們可以安全地寫入E類型的數(shù)據(jù)。

Naftalin與Wadler將PECS稱為 Get and Put Principle

java.util.Collectionscopy方法中(JDK1.7)完美地詮釋了PECS:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {  
    int srcSize = src.size();  
    if (srcSize > dest.size())  
        throw new IndexOutOfBoundsException("Source does not fit in dest");  
  
    if (srcSize < COPY_THRESHOLD ||  
        (src instanceof RandomAccess && dest instanceof RandomAccess)) {  
        for (int i=0; i<srcSize; i++)  
            dest.set(i, src.get(i));  
    } else {  
        ListIterator<? super T> di=dest.listIterator();   // in T, 寫入dest數(shù)據(jù)
        ListIterator<? extends T> si=src.listIterator();   // out T, 讀取src數(shù)據(jù)
        for (int i=0; i<srcSize; i++) {  
            di.next();  
            di.set(si.next());  
        }  
    }  
}  

8.5 out T 與 in T

正如上文所講的,在 Java 泛型里,有通配符這種東西,我們要用? extends T指定類型參數(shù)的上限,用 ? super T指定類型參數(shù)的下限。

而Kotlin 拋棄了這個東西,直接實(shí)現(xiàn)了上文所講的PECS的規(guī)則。Kotlin 引入了投射類型 out T 代表生產(chǎn)者對象,投射類型 in T 代表消費(fèi)者對象。Kotlin使用了投射類型( projected type ) out T 和 in T 來實(shí)現(xiàn)了類型通配符同樣的功能。

我們用代碼示例簡單講解一下:

public static <T> void copy(List<? super T> dest, List<? extends T> src) {  
        ...
        ListIterator<? super T> di=dest.listIterator();   // in T, 寫入dest數(shù)據(jù)
        ListIterator<? extends T> si=src.listIterator();   // out T, 讀取src數(shù)據(jù)
         ...
}  


List<? super T> dest 是消費(fèi)數(shù)據(jù)的對象,數(shù)據(jù)會被寫入到 dest 對象中,這些數(shù)據(jù)該對象被“吃掉”了(Kotlin中叫in T)。

List<? extends T> src 是生產(chǎn)提供數(shù)據(jù)的對象。src 會“吐出”數(shù)據(jù)(Kotlin中叫out T)。

在Kotlin中,我們把那些只能保證讀取數(shù)據(jù)時類型安全的對象叫做生產(chǎn)者,用 out T標(biāo)記;把那些只能保證寫入數(shù)據(jù)安全時類型安全的對象叫做消費(fèi)者,用 in T標(biāo)記。

如果你覺得太晦澀難懂,就這么記吧:

out T 等價(jià)于? extends T
in T 等價(jià)于 ? super T

8.6 類型擦除

Java和Kotlin 的泛型實(shí)現(xiàn),都是采用了運(yùn)行時類型擦除的方式。也就是說,在運(yùn)行時,這些類型參數(shù)的信息將會被擦除。

泛型是在編譯器層次上實(shí)現(xiàn)的。生成的 class 字節(jié)碼文件中是不包含泛型中的類型信息的。例如在代碼中定義的List<Object>和List<String>等類型,在編譯之后都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。

關(guān)于泛型的很多奇怪特性都與這個類型擦除的存在有關(guān),比如:泛型類并沒有自己獨(dú)有的Class類對象。比如Java中并不存在List<String>.class或是List<Integer>.class,而只有List.class。對應(yīng)地在Kotlin中并不存在MutableList<Fruit>::class, 而只有 MutableList::class 。

類型擦除的基本過程也比較簡單:

  • 首先,找到用來替換類型參數(shù)的具體類。這個具體類一般是Object。如果指定了類型參數(shù)的上界的話,則使用這個上界。

  • 其次,把代碼中的類型參數(shù)都替換成具體的類。同時去掉出現(xiàn)的類型聲明,即去掉<>的內(nèi)容。比如, T get() 就變成了Object get(), List<String> 就變成了List。

  • 最后,根據(jù)需要生成一些橋接方法。這是由于擦除了類型之后的類可能缺少某些必須的方法。這個時候就由編譯器來動態(tài)生成這些方法。

當(dāng)了解了類型擦除機(jī)制之后,我們就會明白是編譯器承擔(dān)了全部的類型檢查工作。編譯器禁止某些泛型的使用方式,也正是為了確保類型的安全性。

本章小結(jié)

泛型是一個非常有用的東西。尤其在集合類中。我們可以發(fā)現(xiàn)大量的泛型代碼。有了泛型,我們可以擁有更強(qiáng)大更安全的類型檢查、無需手工進(jìn)行類型轉(zhuǎn)換,并且能夠開發(fā)更加通用的泛型算法。


Kotlin 開發(fā)者社區(qū)

國內(nèi)第一Kotlin 開發(fā)者社區(qū)公眾號,主要分享、交流 Kotlin 編程語言、Spring Boot、Android、React.js/Node.js、函數(shù)式編程、編程思想等相關(guān)主題。

開發(fā)者社區(qū) QRCode.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,016評論 2 375

推薦閱讀更多精彩內(nèi)容

  • 泛型 泛型(Generic Type)簡介 通常情況的類和函數(shù),我們只需要使用具體的類型即可:要么是基本類型,要么...
    Tenderness4閱讀 1,430評論 4 2
  • 寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使自己記憶和理解的更加深刻,二是可以分享給同樣想學(xué)習(xí)Kot...
    胡奚冰閱讀 1,444評論 1 3
  • 前言 人生苦多,快來 Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,248評論 9 118
  • 都說朋友是互相謙讓,互相幫助,但是找一個真正的朋友真的非常難,而且是太多覺得很好的朋友,因?yàn)橐稽c(diǎn)事情就翻...
    4點(diǎn)半的恩賜閱讀 492評論 0 0
  • 3月的天氣,春天的氣息,雨珠不斷的,不斷的洗刷著玻璃,萬物在雨的沖刷下顯得楚楚動人,郁郁蔥蔥,枯木在雨后也得到了重...
    6ad5b7598ebc閱讀 509評論 4 10