《Thinking in Java》學(xué)習(xí)筆記——15章泛型(一)

簡單泛型

1.泛型的主要目的之一就是用來指定容器要持有什么類型的對象蜀备,而且由編譯器來保證類型的正確性。
2.Java泛型的核心概念:告訴編譯器想使用什么類型惜浅,然后編譯器幫你處理一切細(xì)節(jié)。

一.一個(gè)元組類庫

1.元組(tuple):它是將一組對象直接打包儲存于其中的一個(gè)單一對象。
2.下面程序是一個(gè)二維元組赞枕,它能夠持有兩個(gè)對象:

public class TwoTuple<A, B> {
    public final A first;
    public final B second;
    public TwoTuple(A a, B b) {
        first = a;
        second = b;
    }
    public String toString() {
        return "(" + first + ", " + second + ")";
    }
}

客戶端程序可以讀取first或second所引用的對象,然后可以隨心所欲地使用這兩個(gè)對象坪创。但是炕婶,它們卻無法將其他值賦予first或second。因?yàn)閒inal聲明為你買了安全保險(xiǎn)莱预,實(shí)現(xiàn)了Java編程的安全性原則柠掂,而且這種格式更加簡潔明了。
3.我們可以利用繼承機(jī)制實(shí)現(xiàn)更長的元組依沮。

一個(gè)堆棧類

1.不用LinkedList涯贞,自己實(shí)現(xiàn)的內(nèi)部鏈?zhǔn)酱鎯C(jī)制:

public class LinkedStack<T> {
    private static class Node<U> {
        U item;
        Node<U> next;
        Node() {
            item = null;
            next = null;
        }
        Node(U item, Node<U> next) {
            this.item = item;
            this.next = next;
        }
        boolean end() {
            return item == null && next == null;
        }
    }
     
    private Node<T> top = new Node<T>();
    public void push(T item) {
        top = new Node<T>(item, top);
    }
    public T pop() {
        T result = top.item;
        if (!top.end()) {
            top = top.next;
        }
        return result;
    }
}

2.另一個(gè)例子RandomList:假設(shè)我們需要一個(gè)持有特定類型對象的列表,每次調(diào)用其上的select()方法時(shí)危喉,它可以隨機(jī)地選取一個(gè)元素:

class RandomList<T> {
    private ArrayList<T> storage = new ArrayList<T>();
    private Random rand = new Random(47);
    public void add(T t) {
        storage.add(t);
    }
    public T select() {
        return storage.get(rand.nextInt()storage.size());
    }
}

泛型方法

1.可以在類中包含參數(shù)化方法宋渔,而這個(gè)方法所在的類可以是泛型類,也可以不是泛型類辜限。也就是說皇拣,是否擁有泛型方法,與其所在的類是否是泛型沒有關(guān)系薄嫡。
2.如果使用泛型方法可以取代將整個(gè)類泛型化氧急,那么就應(yīng)該只使用泛型化,另外對于一個(gè)static方法而言岂座,無法訪問泛型類的類型參數(shù)态蒂,所以如果static方法需要使用泛型能力,就必須使其成為泛型方法费什。
3.要定義泛型方法钾恢,只需將泛型參數(shù)列表置于返回值之前:

public <T> void f(T x) {
    System.out.println(x.getClass().getName());
}

如果調(diào)用f()時(shí)傳入的是基本類型手素,自動打包機(jī)制就會介入其中,將基本類型的值包裝為對應(yīng)的對象瘩蚪。

一.可變參數(shù)與泛型方法

1.泛型方法與可變參數(shù)列表能夠很好的共存:

//和java.util.Arrays.asList()方法功能相同
public static <T> List<T> makeList(T... args) {
    List<T> result = new ArrayList<T>();
    for (T items : args) {
        result.add(item);
    }
    return result;
}
二.簡化元組的使用

1.我們現(xiàn)在可以重新編寫之前的元組工具泉懦,使其成為更通用的工具類庫:

public class Tuple {
    public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
        return new TwoTuple<A, B>(a, b);
    }
    ......
}

下面是一個(gè)測試類:

public class TupleTest {
    static TwoTuple<String, Integer> f() {
        return Tuple.tuple("hi", 47);
    }
    static TwoTuple f2() {
        return Tuple.tuple("hi", 47);
    }
    public static void main(String... args) {
        TwoTuple<String, Integer> ttsi = f();
        System.out.println(f());
        System.out.println(f2());
    }
}
/*
Output:
(hi, 47)
(hi, 47)
*/

在這里,方法f()返回一個(gè)參數(shù)化的TwoTuple對象疹瘦,而f2()返回的是非參數(shù)化的TwoTuple對象崩哩。編譯器并沒有關(guān)于f2()的警告信息,因?yàn)槲覀儾]有將其返回值作為參數(shù)化對象使用言沐。在某種意義上邓嘹,它被“向上轉(zhuǎn)型”為一個(gè)非參數(shù)化的TwoTuple。然而险胰,如果試圖將f2()的返回值轉(zhuǎn)型為參數(shù)化的TwoTuple汹押,編譯器就會發(fā)出警告。

擦除的神秘之處

1.ArrayList<String>ArrayList<Integer>很容易被誤認(rèn)為是兩種不同的類型起便,但它們是相同的:

public class Test {
    public static void main() {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        if (c1 == c2)
            System.out.println("true");
        System.out.println(Arrays.toString(c1.getTypeParameters()));
        System.out.println(Arrays.toString(c2.getTypeParameters()));
    }
}
/*
Output:
true
[E]
[E]
*/

首先我們可以看到棚贾,上面的程序會認(rèn)為它們是相同的類型。另外榆综,程序中使用的Class.getTypeParameters()可以返回一個(gè)TypeVariable對象數(shù)組妙痹,表示有泛型聲明所聲明的類型參數(shù),但是我們能夠發(fā)現(xiàn)的只有用作參數(shù)占位符的標(biāo)識符鼻疮。因此我們得出結(jié)論:在泛型代碼內(nèi)部怯伊,無法獲得任何有關(guān)泛型參數(shù)類型的信息。
2.Java泛型是使用擦除來實(shí)現(xiàn)的陋守,這意味著當(dāng)你在使用泛型時(shí)震贵,任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個(gè)對象水评。因此List<String>List<Integer>在運(yùn)行時(shí)都被擦除成它們的“原生”類型List猩系。
3.由于擦除,在使用泛型時(shí)中燥,這些類型參數(shù)都會被當(dāng)作Object進(jìn)行處理寇甸。

一.擦除的問題

1.泛型不能用于顯式地引用運(yùn)行時(shí)類型的操作之中,例如轉(zhuǎn)型疗涉、instanceof操作和new表達(dá)式拿霉。
2.為了關(guān)閉由于沒有使用泛型的警告,Java提供了一個(gè)注解:

@SuppressWarnings("unchecked")

這個(gè)注解被放置在可以產(chǎn)生這類警告的方法之上咱扣,而不是整個(gè)類上绽淘。

二.邊界處的動作
public class ArrayMaker<T> {
    private Class<T> kind;
    private ArrayMaker(Class<T> kind) {
        this.kind = kind;
    }
    
    @SuppressWarnings("unchecked")
    T[] create(int size) {
        return (T[]) Array.newInstance(kind, size);
    }
    public static void main(String... args) {
        ArrayMaker<String> stringMaker = new ArrayMaker<String>(String.class);
        String[] stringArray = stringMaker.create(9);
        System.out.println(Arrays.toString(stringArray))
    }
}
/*
Output:
[null, null, null, null, null, null, null, null, null]
*/

上面這段代碼中,由于擦除闹伪,kind實(shí)際被儲存為Class沪铭,沒有任何參數(shù)壮池。因此Array,newInstance()中并沒有收到任何具體的類型信息,因此必須轉(zhuǎn)型杀怠。
注意椰憋,對于在泛型中創(chuàng)建數(shù)組,使用Array.newInstance()是推薦的方式赔退。
2.因?yàn)椴脸诜椒w中移除了類型信息橙依,所以在運(yùn)行時(shí)的問題就是邊界:即對象進(jìn)入和離開方法的地點(diǎn)。這些正是編譯器執(zhí)行類型檢查并插入轉(zhuǎn)型代碼的地點(diǎn)硕旗。
3.在泛型中所有的動作都發(fā)生在邊界處——對傳遞進(jìn)來的值進(jìn)行額外的編譯期檢查窗骑,并插入對傳遞出去的值的轉(zhuǎn)型。典型的情況就是在get()set()方法中使用泛型漆枚。

擦除的補(bǔ)償

1.擦除丟失了在泛型代碼中執(zhí)行某些操作的能力慧域。任何在運(yùn)行時(shí)需要知道的確切類型信息的操作都將無法工作

public class Erased<T> {
    private final int SIZE = 100;
    public static void f(Object arg) {
        if (arg instanceof T) {)                    //ERROR
        T var = new T();                             //ERROR
        T[] array = new T[SIZE];                //ERROR
        T[] array = (T) new Object[SIZE];  //ERROR
    }
}
一.創(chuàng)建類型實(shí)例

1.上面的程序中對創(chuàng)建一個(gè)new T()的嘗試無法實(shí)現(xiàn),部分原因是因?yàn)椴脸硕粒硪徊糠衷蚴且驗(yàn)榫幾g器不能驗(yàn)證T具有默認(rèn)(無參)構(gòu)造器。
2.對于上面這個(gè)問題Java的解決方法是使用工廠對象來創(chuàng)建新的實(shí)例辛藻,建議使用顯式工廠碘橘,并限制其類型,使得只能接受實(shí)現(xiàn)了這個(gè)工廠的類:

interface Factory<T> {
    T create();
}

class Foo<T> {
    private T x;
    public <F extends Factory<T>> Foo(F Factory) {
        x = factory.create();
    }
}

class IntegerFactory implements Factory<Integer> {
    public Integer create() {
        return new Integer(0);
    }  
}

class Widget {
    public static class WidgetFactory implements Factory<Widget> {
        public WidgetFactory create() {
            return new Widget();
        }
    }
}

public class FactoryConstraint {
    public static void main(String... args) {
        new Foo<Integer>(new IntegerFactory());
        new Foo<Widget>(new Widget.WidgetFactory());
    }
}
二.泛型數(shù)組

1.不能直接創(chuàng)建泛型數(shù)組吱肌,一般想要達(dá)到相同效果的方法是在任何想要?jiǎng)?chuàng)建泛型數(shù)組的地方都使用ArrayList痘拆。
2.如果一定要?jiǎng)?chuàng)建一個(gè)泛型數(shù)組,唯一的方式就是創(chuàng)建一個(gè)被擦除類型的新數(shù)組氮墨,然后對其轉(zhuǎn)型

class Generic<T> {} 

class ArrayOfGeneric {
    static Generic<Interger> gia;
    public static void main(String... args) {
        gia = (Generic<Interger>[]) new Generic[10];
    }
}

3.因?yàn)橛辛瞬脸龜?shù)組的運(yùn)行時(shí)類型就只能是Object[]纺蛆。如果我們立即將其轉(zhuǎn)型為T(),那么在編譯期該數(shù)組的實(shí)際類型就將丟失规揪。因此桥氏,最好時(shí)機(jī)在集合內(nèi)部使用Object[],然后當(dāng)你使用數(shù)組元素時(shí)猛铅,添加一個(gè)對T[]對轉(zhuǎn)型

class GenericArray<T> {
    private Object[] array;
    
    public GenericArray(int size) {
        array = new Object[size];
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    @SupressWarnings("unchecked")
    public T get(int index) {
        return (T) array[index];
    }
}

但如果試著將Object[]轉(zhuǎn)型為T[]是不行的字支,因?yàn)椋瑳]有任何方式可以推翻底層數(shù)組類型奸忽,它只能是Object[]堕伪。
4.下面是對上面代碼對一種改進(jìn),ArrayList中就使用了這種形式對轉(zhuǎn)型:

public class GenericArrayWithTypeToken<T> {
    private T[] array;

    @SupressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int size) {
        array = (T[]) Array.newInstance(size, type);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return (T) array[index];
    }
}

邊界

1.邊界使得我們可以在用于泛型的類型參數(shù)類型上設(shè)置限制條件栗菜。因?yàn)椴脸祟愋托畔⑶反疲钥梢杂脽o界泛型參數(shù)調(diào)用的方法只是那些可以用 Object調(diào)用的方法。但是疙筹,如果能夠?qū)⑦@個(gè)參數(shù)限制為某個(gè)類型子集富俄,那就可以用類型子集來調(diào)用方法禁炒。
2.為了執(zhí)行對泛型參數(shù)的限制,Java重用了extends關(guān)鍵字蛙酪。
3.對泛型進(jìn)行參數(shù)限制也有多繼承齐苛,并且也可以通過繼承消除冗余:

interface A{}

interface B {}

class C{}

class D extends C implements A, B {}

class E<T extends C & A & B> {}

class F {
    public F() {
        E<D> e = new E<D>();
    }
}

4.下面的程序展示了如何在繼承的每個(gè)層次上添加邊界限制:

class A {
   void set();
}

class B<T> {
   T item;
   public B(T item) {
       this.item = item;
   }
}

class C<T extends A> extends B<T> {
   T item;
   public C(T item) {
       this,item = item;
       item.set();
   }
}

上面的程序中B直接持有一個(gè)對象,因此這種行為被繼承到了C中桂塞,它也要求其參數(shù)與A一致凹蜂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市阁危,隨后出現(xiàn)的幾起案子玛痊,更是在濱河造成了極大的恐慌,老刑警劉巖狂打,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件擂煞,死亡現(xiàn)場離奇詭異,居然都是意外死亡趴乡,警方通過查閱死者的電腦和手機(jī)对省,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晾捏,“玉大人蒿涎,你說我怎么就攤上這事〉胄粒” “怎么了劳秋?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胖齐。 經(jīng)常有香客問我玻淑,道長,這世上最難降的妖魔是什么呀伙? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任补履,我火速辦了婚禮,結(jié)果婚禮上剿另,老公的妹妹穿的比我還像新娘干像。我一直安慰自己,他們只是感情好驰弄,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布麻汰。 她就那樣靜靜地躺著,像睡著了一般戚篙。 火紅的嫁衣襯著肌膚如雪五鲫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天岔擂,我揣著相機(jī)與錄音位喂,去河邊找鬼浪耘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛塑崖,可吹牛的內(nèi)容都是我干的七冲。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼规婆,長吁一口氣:“原來是場噩夢啊……” “哼澜躺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抒蚜,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤掘鄙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嗡髓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體操漠,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年饿这,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浊伙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡长捧,死狀恐怖吧黄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情唆姐,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布廓八,位于F島的核電站奉芦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剧蹂。R本人自食惡果不足惜声功,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宠叼。 院中可真熱鬧先巴,春花似錦、人聲如沸冒冬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽简烤。三九已至剂邮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間横侦,已是汗流浹背挥萌。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工绰姻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人引瀑。 一個(gè)月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓狂芋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親憨栽。 傳聞我的和親對象是個(gè)殘疾皇子帜矾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

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

  • 通配符 1.數(shù)組具有協(xié)變性:可以向?qū)С鲱愋偷臄?shù)組賦予基類型的數(shù)組引用: 上面的代碼不會出現(xiàn)編譯問題,因?yàn)锳pple...
    zpauly閱讀 710評論 0 0
  • 附上思維導(dǎo)圖徒像。這篇博客主要講了如下知識點(diǎn)黍特。 看完了《Thinking in Java》的第十五章泛型,著實(shí)被震了一...
    Happioo閱讀 735評論 0 1
  • 泛型是Java 1.5引入的新特性锯蛀。泛型的本質(zhì)是參數(shù)化類型灭衷,這種參數(shù)類型可以用在類、變量旁涤、接口和方法的創(chuàng)建中翔曲,分別...
    何時(shí)不晚閱讀 3,039評論 0 2
  • 2.簡單泛型 -********Java泛型的核心概念:告訴編譯器想使用什么類型, 然后編譯器幫你處理一切細(xì)節(jié) 2...
    CodingHou閱讀 393評論 0 0
  • 一、為什么要使用泛型 1.類型參數(shù)的好處 類型安全:泛型的主要目標(biāo)是提高 Java 程序的類型安全劈愚。通過知道使用泛...
    SeanMa閱讀 7,086評論 1 18