成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

JavaSE筆記——泛型-創(chuàng)新互聯(lián)

文章目錄
  • 前言
  • 一、簡(jiǎn)單泛型
    • 1.一個(gè)元組類(lèi)庫(kù)
    • 2.一個(gè)堆棧類(lèi)
  • 二、泛型接口
  • 三、泛型方法
    • 1.變長(zhǎng)參數(shù)和泛型方法
    • 2.一個(gè)泛型的 Supplier
    • 3.簡(jiǎn)化元組的使用
    • 4.一個(gè) Set 工具
  • 四、構(gòu)建復(fù)雜模型
  • 五、泛型擦除
    • 1.遷移兼容性
    • 2.擦除的問(wèn)題
    • 3.邊界處的動(dòng)作
  • 六、補(bǔ)償擦除
    • 1.創(chuàng)建類(lèi)型的實(shí)例
  • 七、邊界
  • 八、通配符
    • 1.超類(lèi)型通配符
    • 2.無(wú)界通配符
  • 總結(jié)

創(chuàng)新互聯(lián)-專(zhuān)業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性?xún)r(jià)比婁煩網(wǎng)站開(kāi)發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式婁煩網(wǎng)站制作公司更省心,省錢(qián),快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋婁煩地區(qū)。費(fèi)用合理售后完善,十多年實(shí)體公司更值得信賴(lài)。
前言

多態(tài)是一種面向?qū)ο笏枷氲姆夯瘷C(jī)制。你可以將方法的參數(shù)類(lèi)型設(shè)為基類(lèi),這樣的方法就可以接受任何派生類(lèi)作為參數(shù),包括暫時(shí)還不存在的類(lèi)。這樣的方法更通用,應(yīng)用范圍更廣。在類(lèi)內(nèi)部也是如此,在任何使用特定類(lèi)型的地方,基類(lèi)意味著更大的靈活性。除了 final 類(lèi)(或只提供私有構(gòu)造函數(shù)的類(lèi))任何類(lèi)型都可被擴(kuò)展,所以大部分時(shí)候這種靈活性是自帶的。

拘泥于單一的繼承體系太過(guò)局限,因?yàn)橹挥欣^承體系中的對(duì)象才能適用基類(lèi)作為參數(shù)的方法中。如果方法以接口而不是類(lèi)作為參數(shù),限制就寬松多了,只要實(shí)現(xiàn)了接口就可以。這給予調(diào)用方一種選項(xiàng),通過(guò)調(diào)整現(xiàn)有的類(lèi)來(lái)實(shí)現(xiàn)接口,滿(mǎn)足方法參數(shù)要求。接口可以突破繼承體系的限制。

即便是接口也還是有諸多限制。一旦指定了接口,它就要求你的代碼必須使用特定的接口。而我們希望編寫(xiě)更通用的代碼,能夠適用 “非特定的類(lèi)型”,而不是一個(gè)具體的接口或類(lèi)。

這就是泛型的概念,是 Java 5 的重大變化之一。泛型實(shí)現(xiàn)了參數(shù)化類(lèi)型,這樣你編寫(xiě)的組件(通常是集合)可以適用于多種類(lèi)型。“泛型” 這個(gè)術(shù)語(yǔ)的含義是 “適用于很多類(lèi)型”。編程語(yǔ)言中泛型出現(xiàn)的初衷是通過(guò)解耦類(lèi)或方法與所使用的類(lèi)型之間的約束,使得類(lèi)或方法具備最寬泛的表達(dá)力。


一、簡(jiǎn)單泛型

促成泛型出現(xiàn)的最主要的動(dòng)機(jī)之一是為了創(chuàng)建集合類(lèi),集合用于存放要使用到的對(duì)象。數(shù)組也是如此,不過(guò)集合比數(shù)組更加靈活,功能更豐富。幾乎所有程序在運(yùn)行過(guò)程中都會(huì)涉及到一組對(duì)象,因此集合是可復(fù)用性最高的類(lèi)庫(kù)之一。

我們希望先指定一個(gè)類(lèi)型占位符,稍后再?zèng)Q定具體使用什么類(lèi)型。要達(dá)到這個(gè)目的,需要使用類(lèi)型參數(shù),用尖括號(hào)括住,放在類(lèi)名后面。然后在使用這個(gè)類(lèi)時(shí),再用實(shí)際的類(lèi)型替換此類(lèi)型參數(shù)。在下面的例子中,T 就是類(lèi)型參數(shù):

public class GenericHolder{private T a;

    public GenericHolder() {}

    public T getA() {return a;
    }

    public void setA(T a) {this.a = a;
    }

    public static void main(String[] args) {GenericHoldergenericHolder = new GenericHolder<>();
        genericHolder.setA("aaaa");
        System.out.println(genericHolder.getA());
    }
}

創(chuàng)建 GenericHolder 對(duì)象時(shí),必須指明要持有的對(duì)象的類(lèi)型,將其置于尖括號(hào)內(nèi),就像 main() 中那樣使用。然后,你就只能在 GenericHolder 中存儲(chǔ)該類(lèi)型(或其子類(lèi),因?yàn)槎鄳B(tài)與泛型不沖突)的對(duì)象了。當(dāng)你調(diào)用 get() 取值時(shí),直接就是正確的類(lèi)型。這就是 Java 泛型的核心概念:你只需告訴編譯器要使用什么類(lèi)型,剩下的細(xì)節(jié)交給它來(lái)處理。

1.一個(gè)元組類(lèi)庫(kù)

有時(shí)一個(gè)方法需要能返回多個(gè)對(duì)象。而 return 語(yǔ)句只能返回單個(gè)對(duì)象,解決方法就是創(chuàng)建一個(gè)對(duì)象,用它打包想要返回的多個(gè)對(duì)象。當(dāng)然,可以在每次需要的時(shí)候,專(zhuān)門(mén)創(chuàng)建一個(gè)類(lèi)來(lái)完成這樣的工作。但是有了泛型,我們就可以一勞永逸。同時(shí),還獲得了編譯時(shí)的類(lèi)型安全。

這個(gè)概念稱(chēng)為元組,它是將一組對(duì)象直接打包存儲(chǔ)于單一對(duì)象中。可以從該對(duì)象讀取其中的元素,但不允許向其中存儲(chǔ)新對(duì)象(這個(gè)概念也稱(chēng)為 數(shù)據(jù)傳輸對(duì)象或 信使)。

通常,元組可以具有任意長(zhǎng)度,元組中的對(duì)象可以是不同類(lèi)型的。不過(guò),我們希望能夠?yàn)槊總€(gè)對(duì)象指明類(lèi)型,并且從元組中讀取出來(lái)時(shí),能夠得到正確的類(lèi)型。要處理不同長(zhǎng)度的問(wèn)題,我們需要?jiǎng)?chuàng)建多個(gè)不同的元組。下面是一個(gè)可以存儲(chǔ)兩個(gè)對(duì)象的元組:

public class Tuple2{public final A a1;
    public final B a2;

    public Tuple2(A a, B b) {a1 = a;
        a2 = b;
    }

    public String rep() {return a1 + ", " + a2;
    }

    @Override
    public String toString() {return "(" + rep() + ")";
    }
    
}

public class TupleTest {static Tuple2f() {return new Tuple2<>("hi", 47);
    }

    public static void main(String[] args) {Tuple2ttsi = f();
        System.out.println(ttsi);
    }
}

構(gòu)造函數(shù)傳入要存儲(chǔ)的對(duì)象。這個(gè)元組隱式地保持了其中元素的次序。

初次閱讀上面的代碼時(shí),你可能認(rèn)為這違反了 Java 編程的封裝原則。a1 和 a2 應(yīng)該聲明為 private,然后提供 getFirst() 和 getSecond() 取值方法才對(duì)呀?考慮下這樣做能提供的 “安全性” 是什么:元組的使用程序可以讀取 a1 和 a2 然后對(duì)它們執(zhí)行任何操作,但無(wú)法對(duì) a1 和 a2 重新賦值。例子中的 final 可以實(shí)現(xiàn)同樣的效果,并且更為簡(jiǎn)潔明了。

2.一個(gè)堆棧類(lèi)

用鏈?zhǔn)浇Y(jié)構(gòu)實(shí)現(xiàn)的堆棧:

public class LinkedStack{private static class Node{U item;
        Nodenext;

        Node() {item = null;
            next = null;
        }

        Node(U item, Nodenext) {this.item = item;
            this.next = next;
        }

        boolean end() {return item == null && next == null;
        }
    }

    // 棧頂
    private Nodetop = new Node<>();

    public void push(T item) {top = new Node<>(item, top);
    }

    public T pop() {T result = top.item;
        if (!top.end()) {top = top.next;
        }
        return result;
    }

    public static void main(String[] args) {LinkedStacklss = new LinkedStack<>();
        for (String s : "Phasers on stun!".split(" ")) {lss.push(s);
        }
        String s;
        while ((s = lss.pop()) != null) {System.out.println(s);
        }
    }
}

在這里插入圖片描述
內(nèi)部類(lèi) Node 也是一個(gè)泛型,它擁有自己的類(lèi)型參數(shù)。這個(gè)例子使用了一個(gè) 末端標(biāo)識(shí) (end sentinel) 來(lái)判斷棧何時(shí)為空。這個(gè)末端標(biāo)識(shí)是在構(gòu)造 LinkedStack 時(shí)創(chuàng)建的。然后,每次調(diào)用 push() 就會(huì)創(chuàng)建一個(gè) Node 對(duì)象,并將其鏈接到前一個(gè) Node 對(duì)象。當(dāng)你調(diào)用 pop() 方法時(shí),總是返回 top.item,然后丟棄當(dāng)前 top 所指向的 Node,并將 top 指向下一個(gè) Node,除非到達(dá)末端標(biāo)識(shí),這時(shí)就不能再移動(dòng) top 了。如果已經(jīng)到達(dá)末端,程序還繼續(xù)調(diào)用 pop() 方法,它只能得到 null,說(shuō)明棧已經(jīng)空了。

二、泛型接口

泛型也可以應(yīng)用于接口。例如 生成器,這是一種專(zhuān)門(mén)負(fù)責(zé)創(chuàng)建對(duì)象的類(lèi)。實(shí)際上,這是 工廠(chǎng)方法設(shè)計(jì)模式的一種應(yīng)用。不過(guò),當(dāng)使用生成器創(chuàng)建新的對(duì)象時(shí),它不需要任何參數(shù),而工廠(chǎng)方法一般需要參數(shù)。生成器無(wú)需額外的信息就知道如何創(chuàng)建新對(duì)象。

一般而言,一個(gè)生成器只定義一個(gè)方法,用于創(chuàng)建對(duì)象。例如 java.util.function 類(lèi)庫(kù)中的 Supplier 就是一個(gè)生成器,調(diào)用其 get() 獲取對(duì)象。get() 是泛型方法,返回值為類(lèi)型參數(shù) T。

public class CoffeeSupplier implements Supplier, Iterable{private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class};
    private static Random rand = new Random(47);

    public CoffeeSupplier() {}

    private int size = 0;

    public CoffeeSupplier(int sz) {size = sz;
    }

    @Override
    public Coffee get() {try {return (Coffee) types[rand.nextInt(types.length)].newInstance();
        } catch (InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);
        }
    }

    @Override
    public Iteratoriterator() {return new CoffeeIterator();
    }

    class CoffeeIterator implements Iterator{int count = size;

        @Override
        public boolean hasNext() {return count >0;
        }

        @Override
        public Coffee next() {count--;
            return CoffeeSupplier.this.get();
        }

        @Override
        public void remove() {throw new UnsupportedOperationException();
        }

    }

    public static void main(String[] args) {Stream.generate(new CoffeeSupplier()).limit(5).forEach(System.out::println);
        for (Coffee c : new CoffeeSupplier(5)) {System.out.println(c);
        }

    }
}

在這里插入圖片描述
參數(shù)化的 Supplier 接口確保 get() 返回值是參數(shù)的類(lèi)型。CoffeeSupplier 同時(shí)還實(shí)現(xiàn)了 Iterable 接口,所以能用于 for-in 語(yǔ)句。不過(guò),它還需要知道何時(shí)終止循環(huán),這正是第二個(gè)構(gòu)造函數(shù)的作用。

三、泛型方法

到目前為止,我們已經(jīng)研究了參數(shù)化整個(gè)類(lèi)。其實(shí)還可以參數(shù)化類(lèi)中的方法。類(lèi)本身可能是泛型的,也可能不是,不過(guò)這與它的方法是否是泛型的并沒(méi)有什么關(guān)系。

泛型方法獨(dú)立于類(lèi)而改變方法。作為準(zhǔn)則,請(qǐng) “盡可能” 使用泛型方法。通常將單個(gè)方法泛型化要比將整個(gè)類(lèi)泛型化更清晰易懂。

如果方法是 static 的,則無(wú)法訪(fǎng)問(wèn)該類(lèi)的泛型類(lèi)型參數(shù),因此,如果使用了泛型類(lèi)型參數(shù),則它必須是泛型方法。

要定義泛型方法,請(qǐng)將泛型參數(shù)列表放置在返回值之前,如下所示:

public class GenericMethods {publicvoid f(T x) {System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(1.0);
        gm.f(1.0F);
        gm.f('c');
        gm.f(gm);
    }
}

在這里插入圖片描述
盡管可以同時(shí)對(duì)類(lèi)及其方法進(jìn)行參數(shù)化,但這里未將 GenericMethods 類(lèi)參數(shù)化。只有方法 f() 具有類(lèi)型參數(shù),該參數(shù)由方法返回類(lèi)型之前的參數(shù)列表指示。

對(duì)于泛型類(lèi),必須在實(shí)例化該類(lèi)時(shí)指定類(lèi)型參數(shù)。使用泛型方法時(shí),通常不需要指定參數(shù)類(lèi)型,因?yàn)榫幾g器會(huì)找出這些類(lèi)型。這稱(chēng)為 類(lèi)型參數(shù)推斷。因此,對(duì) f() 的調(diào)用看起來(lái)像普通的方法調(diào)用,并且 f() 看起來(lái)像被重載了無(wú)數(shù)次一樣。它甚至?xí)邮蹽enericMethods 類(lèi)型的參數(shù)。

1.變長(zhǎng)參數(shù)和泛型方法

泛型方法和變長(zhǎng)參數(shù)列表可以很好地共存:

public class GenericVarargs {@SafeVarargs
    public staticListmakeList(T... args) {Listresult = new ArrayList<>();
        for (T item : args) {result.add(item);
        }
        return result;
    }

    public static void main(String[] args) {Listls = makeList("A");
        System.out.println(ls);
        ls = makeList("A", "B", "C");
        System.out.println(ls);
        ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
        System.out.println(ls);
    }

}

在這里插入圖片描述
此處顯示的 makeList() 方法產(chǎn)生的功能與標(biāo)準(zhǔn)庫(kù)的 java.util.Arrays.asList() 方法相同。

@SafeVarargs 注解保證我們不會(huì)對(duì)變長(zhǎng)參數(shù)列表進(jìn)行任何修改,這是正確的,因?yàn)槲覀冎粡闹凶x取。如果沒(méi)有此注解,編譯器將無(wú)法知道這些并會(huì)發(fā)出警告。

2.一個(gè)泛型的 Supplier

這是一個(gè)為任意具有無(wú)參構(gòu)造方法的類(lèi)生成 Supplier 的類(lèi)。為了減少鍵入,它還包括一個(gè)用于生成 BasicSupplier 的泛型方法:

public class BasicSupplierimplements Supplier{private Classtype;

    public BasicSupplier(Classtype) {this.type = type;
    }

    @Override
    public T get() {try {return type.newInstance();
        } catch (InstantiationException |
                IllegalAccessException e) {throw new RuntimeException(e);
        }
    }

    public staticSuppliercreate(Classtype) {return new BasicSupplier<>(type);
    }
}

此類(lèi)提供了產(chǎn)生以下對(duì)象的基本實(shí)現(xiàn):

  1. 是 public 的。因?yàn)?BasicSupplier 在單獨(dú)的包中,所以相關(guān)的類(lèi)必須具有 public權(quán)限,而不僅僅是包級(jí)訪(fǎng)問(wèn)權(quán)限。
  2. 具有無(wú)參構(gòu)造方法。要?jiǎng)?chuàng)建一個(gè)這樣的 BasicSupplier 對(duì)象,請(qǐng)調(diào)用 create() 方法,并將要生成類(lèi)型的類(lèi)型令牌傳遞給它。通用的 create() 方法提供了 BasicSupplier.create(MyType.class) 這種較簡(jiǎn)潔的語(yǔ)法來(lái)代替較笨拙的 new BasicSupplier(MyType.class)。
3.簡(jiǎn)化元組的使用
static Tuple2f() {return new Tuple2<>("hi", 47);
    }
4.一個(gè) Set 工具

對(duì)于泛型方法的另一個(gè)示例,請(qǐng)考慮由 Set 表示的數(shù)學(xué)關(guān)系。這些被方便地定義為可用于所有不同類(lèi)型的泛型方法:

public class Sets {public staticSetunion(Seta, Setb) {Setresult = new HashSet<>(a);
        result.addAll(b);
        return result;
    }

    public staticSetintersection(Seta, Setb) {Setresult = new HashSet<>(a);
        result.retainAll(b);
        return result;
    }
    
    public staticSetdifference(Setsuperset, Setsubset) {Setresult = new HashSet<>(superset);
        result.removeAll(subset);
        return result;
    }
    
    public staticSetcomplement(Seta, Setb) {return difference(union(a, b), intersection(a, b));
    }
}

前三個(gè)方法通過(guò)將第一個(gè)參數(shù)的引用復(fù)制到新的 HashSet 對(duì)象中來(lái)復(fù)制第一個(gè)參數(shù),因此不會(huì)直接修改參數(shù)集合。因此,返回值是一個(gè)新的 Set 對(duì)象。

這四種方法代表數(shù)學(xué)集合操作:union() 返回一個(gè)包含兩個(gè)參數(shù)并集的 Set ,intersection() 返回一個(gè)包含兩個(gè)參數(shù)集合交集的 Set ,difference() 從 superset中減去 subset 的元素,而 complement() 返回所有不在交集中的元素的 Set。

四、構(gòu)建復(fù)雜模型

泛型的一個(gè)重要好處是能夠簡(jiǎn)單安全地創(chuàng)建復(fù)雜模型。例如,我們可以輕松地創(chuàng)建一個(gè)元組列表:

public class TupleListextends ArrayList>{public static void main(String[] args) {TupleListtl = new TupleList<>();
        tl.add(TupleTest.f());
        tl.add(TupleTest.f());
        tl.forEach(System.out::println);
    }

}

在這里插入圖片描述
這將產(chǎn)生一個(gè)功能強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),而無(wú)需太多代碼。

五、泛型擦除
public class LostInformation {public static void main(String[] args) {Listlist = new ArrayList<>();
        Mapmap = new HashMap<>();
        Quarkquark = new Quark<>();
        System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
    }

    static class Frob {}
    static class Fnorkle {}
    static class Quark{}
}

在這里插入圖片描述
根據(jù) JDK 文檔,Class.getTypeParameters() “返回一個(gè) TypeVariable 對(duì)象數(shù)組,表示泛型聲明中聲明的類(lèi)型參數(shù)…” 這暗示你可以發(fā)現(xiàn)這些參數(shù)類(lèi)型。但是正如上例中輸出所示,你只能看到用作參數(shù)占位符的標(biāo)識(shí)符,這并非有用的信息。

殘酷的現(xiàn)實(shí)是:在泛型代碼內(nèi)部,無(wú)法獲取任何有關(guān)泛型參數(shù)類(lèi)型的信息。因此,你可以知道如類(lèi)型參數(shù)標(biāo)識(shí)符和泛型邊界這些信息,但無(wú)法得知實(shí)際的類(lèi)型參數(shù)從而用來(lái)創(chuàng)建特定的實(shí)例。在使用 Java 泛型工作時(shí),它是必須處理的最基本的問(wèn)題。

Java 泛型是使用擦除實(shí)現(xiàn)的。這意味著當(dāng)你在使用泛型時(shí),任何具體的類(lèi)型信息都被擦除了,你唯一知道的就是你在使用一個(gè)對(duì)象。因此,List和 List在運(yùn)行時(shí)實(shí)際上是相同的類(lèi)型。它們都被擦除成原生類(lèi)型 List。

1.遷移兼容性

如果 Java 1.0 就含有泛型的話(huà),那么這個(gè)特性就不會(huì)使用擦除來(lái)實(shí)現(xiàn)——它會(huì)使用具體化,保持參數(shù)類(lèi)型為第一類(lèi)實(shí)體,因此你就能在類(lèi)型參數(shù)上執(zhí)行基于類(lèi)型的語(yǔ)言操作和反射操作。泛型在 Java 中仍然是有用的,只是不如它們本來(lái)設(shè)想的那么有用,而原因就是擦除。

在基于擦除的實(shí)現(xiàn)中,泛型類(lèi)型被當(dāng)作第二類(lèi)類(lèi)型處理,即不能在某些重要的上下文使用泛型類(lèi)型。泛型類(lèi)型只有在靜態(tài)類(lèi)型檢測(cè)期間才出現(xiàn),在此之后,程序中的所有泛型類(lèi)型都將被擦除,替換為它們的非泛型上界。例如,List 這樣的類(lèi)型注解會(huì)被擦除為 List,普通的類(lèi)型變量在未指定邊界的情況下會(huì)被擦除為 Object。

擦除的核心動(dòng)機(jī)是你可以在泛化的客戶(hù)端上使用非泛型的類(lèi)庫(kù),反之亦然。這經(jīng)常被稱(chēng)為 “遷移兼容性”。在理想情況下,所有事物將在指定的某天被泛化。在現(xiàn)實(shí)中,即使程序員只編寫(xiě)泛型代碼,他們也必須處理 Java 5 之前編寫(xiě)的非泛型類(lèi)庫(kù)。這些類(lèi)庫(kù)的作者可能從沒(méi)想過(guò)要泛化他們的代碼,或許他們可能剛剛開(kāi)始接觸泛型。

因此 Java 泛型不僅必須支持向后兼容性——現(xiàn)有的代碼和類(lèi)文件仍然合法,繼續(xù)保持之前的含義——而且還必須支持遷移兼容性,使得類(lèi)庫(kù)能按照它們自己的步調(diào)變?yōu)榉盒?,?dāng)某個(gè)類(lèi)庫(kù)變?yōu)榉盒蜁r(shí),不會(huì)破壞依賴(lài)于它的代碼和應(yīng)用。在確定了這個(gè)目標(biāo)后,Java 設(shè)計(jì)者們和從事此問(wèn)題相關(guān)工作的各個(gè)團(tuán)隊(duì)決策認(rèn)為擦除是唯一可行的解決方案。擦除使得這種向泛型的遷移成為可能,允許非泛型的代碼和泛型代碼共存。

2.擦除的問(wèn)題

因此,擦除主要的正當(dāng)理由是從非泛化代碼到泛化代碼的轉(zhuǎn)變過(guò)程,以及在不破壞現(xiàn)有類(lèi)庫(kù)的情況下將泛型融入到語(yǔ)言中。擦除允許你繼續(xù)使用現(xiàn)有的非泛型客戶(hù)端代碼,直至客戶(hù)端準(zhǔn)備好用泛型重寫(xiě)這些代碼。這是一個(gè)崇高的動(dòng)機(jī),因?yàn)樗粫?huì)驟然破壞所有現(xiàn)有的代碼。

擦除的代價(jià)是顯著的。泛型不能用于顯式地引用運(yùn)行時(shí)類(lèi)型的操作中,例如轉(zhuǎn)型、instanceof 操作和 new 表達(dá)式。因?yàn)樗嘘P(guān)于參數(shù)的類(lèi)型信息都丟失了,當(dāng)你在編寫(xiě)泛型代碼時(shí),必須時(shí)刻提醒自己,你只是看起來(lái)?yè)碛杏嘘P(guān)參數(shù)的類(lèi)型信息而已??紤]如下的代碼段:

class Foo{T var;
}

Foof = new Foo<>();

class Foo 中的代碼應(yīng)該知道現(xiàn)在工作于 Cat 之上。泛型語(yǔ)法也在強(qiáng)烈暗示整個(gè)類(lèi)中所有 T 出現(xiàn)的地方都被替換,當(dāng)你在編寫(xiě)這個(gè)類(lèi)的代碼時(shí),必須提醒自己:“不,這只是一個(gè) Object“。

另外,擦除和遷移兼容性意味著,使用泛型并不是強(qiáng)制的:

public class GenericBase{private T element;
    public void set(T arg) {element = arg;
    }
    public T get() {return element;
    }
}
class Derived1extends GenericBase{}

class Derived2 extends GenericBase {}
3.邊界處的動(dòng)作

因?yàn)椴脸?,發(fā)現(xiàn)泛型最令人困惑的方面是可以表示沒(méi)有任何意義的事物。例如:

public class ArrayMaker{private Classkind;
    public ArrayMaker(Classkind) {this.kind = kind;
    }
    @SuppressWarnings("unchecked")
    T[] create(int size) {return (T[]) Array.newInstance(kind, size);
    }
    public static void main(String[] args) {ArrayMakerstringMaker = new ArrayMaker<>(String.class);
        String[] stringArray = stringMaker.create(9);
        System.out.println(Arrays.toString(stringArray));
    }

}

在這里插入圖片描述
即使 kind 被存儲(chǔ)為 Class,擦除也意味著它實(shí)際被存儲(chǔ)為沒(méi)有任何參數(shù)的Class。因此,當(dāng)你在使用它時(shí),例如創(chuàng)建數(shù)組,Array.newInstance() 實(shí)際上并未擁有 kind 所蘊(yùn)含的類(lèi)型信息。所以它不會(huì)產(chǎn)生具體的結(jié)果,因而必須轉(zhuǎn)型,這會(huì)產(chǎn)生一條令你無(wú)法滿(mǎn)意的警告。

如果我們創(chuàng)建一個(gè)集合而不是數(shù)組,情況就不同了:

public class ListMaker{Listcreate() {return new ArrayList<>();
    }

    public static void main(String[] args) {ListMakerstringMaker = new ListMaker<>();
        ListstringList = stringMaker.create();
    }
}

編譯器不會(huì)給出任何警告,盡管我們知道(從擦除中)在 create() 內(nèi)部的 newArrayList<>() 中的 被移除了——在運(yùn)行時(shí),類(lèi)內(nèi)部沒(méi)有任何,因此這看起來(lái)毫無(wú)意義。但是如果你遵從這種思路,并將這個(gè)表達(dá)式改為 new ArrayList(),編譯器就會(huì)發(fā)出警告。

本例中這么做真的毫無(wú)意義嗎?如果在創(chuàng)建 List 的同時(shí)向其中放入一些對(duì)象呢,像這樣:

public class FilledListextends ArrayList{public FilledList(T t, int size) {for (int i = 0; i< size; i++) {this.add(t);
        }
    }

    public static void main(String[] args) {Listlist = new FilledList<>("Hello", 4);
        System.out.println(list);
    }
}

在這里插入圖片描述

即使編譯器無(wú)法得知 add() 中的 T 的任何信息,但它仍可以在編譯期確保你放入FilledList 中的對(duì)象是 T 類(lèi)型。因此,即使擦除移除了方法或類(lèi)中的實(shí)際類(lèi)型的信息,編譯器仍可以確保方法或類(lèi)中使用的類(lèi)型的內(nèi)部一致性。

因?yàn)椴脸瞥朔椒w中的類(lèi)型信息,所以在運(yùn)行時(shí)的問(wèn)題就是邊界:即對(duì)象進(jìn)入和離開(kāi)方法的地點(diǎn)。這些正是編譯器在編譯期執(zhí)行類(lèi)型檢查并插入轉(zhuǎn)型代碼的地點(diǎn)。

六、補(bǔ)償擦除

因?yàn)椴脸?,我們將失去?zhí)行泛型代碼中某些操作的能力。無(wú)法在運(yùn)行時(shí)知道確切類(lèi)型:

public class Erased{private final int SIZE = 100;

    public void f(Object arg) {// error: illegal generic type for instanceof
        if (arg instanceof T) {}
        // error: unexpected type
        T var = new T();
        // error: generic array creation
        T[] array = new T[SIZE];
        // warning: [unchecked] unchecked cast
        T[] array = (T[]) new Object[SIZE];
    }
}

有時(shí),我們可以對(duì)這些問(wèn)題進(jìn)行編程,但是有時(shí)必須通過(guò)引入類(lèi)型標(biāo)簽來(lái)補(bǔ)償擦除。這意味著為所需的類(lèi)型顯式傳遞一個(gè) Class 對(duì)象,以在類(lèi)型表達(dá)式中使用它。

public class ClassTypeCapture{Classkind;

    public ClassTypeCapture(Classkind) {this.kind = kind;
    }

    public boolean f(Object arg) {return kind.isInstance(arg);
    }

    public static void main(String[] args) {ClassTypeCapturectt1 = new ClassTypeCapture<>(Building.class);
        System.out.println(ctt1.f(new Building()));
        System.out.println(ctt1.f(new House()));
        ClassTypeCapturectt2 = new ClassTypeCapture<>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.println(ctt2.f(new House()));
    }
}

在這里插入圖片描述
編譯器來(lái)保證類(lèi)型標(biāo)簽與泛型參數(shù)相匹配。

1.創(chuàng)建類(lèi)型的實(shí)例

試圖在類(lèi)中 new T() 是行不通的,部分原因是由于擦除,部分原因是編譯器無(wú)法驗(yàn)證 T 是否具有默認(rèn)(無(wú)參)構(gòu)造函數(shù)。 Java 中的解決方案是傳入一個(gè)工廠(chǎng)對(duì)象,并使用該對(duì)象創(chuàng)建新實(shí)例。方便的工廠(chǎng)對(duì)象只是 Class 對(duì)象,因此,如果使用類(lèi)型標(biāo)記,則可以使用 newInstance() 創(chuàng)建該類(lèi)型的新對(duì)象:

public class ClassAsFactoryimplements Supplier{Classkind;

    ClassAsFactory(Classkind) {this.kind = kind;
    }

    @Override
    public T get() {try {return kind.newInstance();
        } catch (InstantiationException |
                IllegalAccessException e) {throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {ClassAsFactoryfii = new ClassAsFactory<>(Building.class);
        System.out.println(fii.get());

//        ClassAsFactoryfi = new ClassAsFactory<>(Integer.class);
//        System.out.println(fi.get());
    }
}

在這里插入圖片描述

對(duì)于 ClassAsFactory會(huì)失敗,這是因?yàn)?Integer 沒(méi)有無(wú)參構(gòu)造函數(shù)。由于錯(cuò)誤不是在編譯時(shí)捕獲的,因此語(yǔ)言創(chuàng)建者不贊成這種方法。他們建議使用顯式工廠(chǎng)(Supplier)并約束類(lèi)型,以便只有實(shí)現(xiàn)該工廠(chǎng)的類(lèi)可以這樣創(chuàng)建對(duì)象。這是創(chuàng)建工廠(chǎng)的兩種不同方法:

public class IntegerFactory implements Supplier{private int i = 0;

    @Override
    public Integer get() {return ++i;
    }
}

public class Widget {private int id;

    Widget(int n) {id = n;
    }

    @Override
    public String toString() {return "Widget " + id;
    }

    public static class Factory implements Supplier{private int i = 0;

        @Override
        public Widget get() {return new Widget(++i);
        }
    }
}

public class Fudge {private static int count = 1;
    private int n = count++;

    @Override
    public String toString() {return "Fudge " + n;
    }
}

public class Foo2{private Listx = new ArrayList<>();

    Foo2(Supplierfactory) {Suppliers.fill(x, factory, 5);
    }

    @Override
    public String toString() {return x.toString();
    }
}

public class Suppliers {// Create a collection and fill it:
    public static>C
    create(Supplierfactory, Suppliergen, int n) {return Stream.generate(gen)
                .limit(n)
                .collect(factory, C::add, C::addAll);
    }

    // Fill an existing collection:
    public static>C fill(C coll, Suppliergen, int n) {Stream.generate(gen)
                .limit(n)
                .forEach(coll::add);
        return coll;
    }

    // Use an unbound method reference to
    // produce a more general method:

    public staticH fill(H holder, BiConsumeradder, Suppliergen, int n) {Stream.generate(gen)
                .limit(n)
                .forEach(a ->adder.accept(holder, a));
        return holder;
    }

}

public class FactoryConstraint {public static void main(String[] args) {System.out.println(new Foo2<>(new IntegerFactory()));
        System.out.println(new Foo2<>(new Widget.Factory()));
        System.out.println(new Foo2<>(Fudge::new));
    }
}

IntegerFactory 本身就是通過(guò)實(shí)現(xiàn) Supplier的工廠(chǎng)。Widget 包含一個(gè)內(nèi)部類(lèi),它是一個(gè)工廠(chǎng)。還要注意,F(xiàn)udge 并沒(méi)有做任何類(lèi)似于工廠(chǎng)的操作,并且傳遞 Fudge::new 仍然會(huì)產(chǎn)生工廠(chǎng)行為,因?yàn)榫幾g器將對(duì)函數(shù)方法 Fudge::new 的調(diào)用轉(zhuǎn)換為對(duì) get() 的調(diào)用。

另一種方法是模板方法設(shè)計(jì)模式。在以下示例中,create() 是模板方法,在子類(lèi)中被重寫(xiě)以生成該類(lèi)型的對(duì)象:

public abstract class GenericWithCreate{final T element;

    GenericWithCreate() {element = create();
    }

    abstract T create();
}

public class X {}

public class XCreator extends GenericWithCreate{@Override
    X create() {return new X();
    }

    void f() {System.out.println(element.getClass().getSimpleName());
    }
}

public class CreatorGeneric {public static void main(String[] args) {XCreator xc = new XCreator();
        xc.f();
    }
}

在這里插入圖片描述
GenericWithCreate 包含 element 字段,并通過(guò)無(wú)參構(gòu)造函數(shù)強(qiáng)制其初始化,該構(gòu)造函數(shù)又調(diào)用抽象的 create() 方法。這種創(chuàng)建方式可以在子類(lèi)中定義,同時(shí)建立 T 的類(lèi)型。

七、邊界

邊界允許我們對(duì)泛型使用的參數(shù)類(lèi)型施加約束。盡管這可以強(qiáng)制執(zhí)行有關(guān)應(yīng)用了泛型類(lèi)型的規(guī)則,但潛在的更重要的效果是我們可以在綁定的類(lèi)型中調(diào)用方法

由于擦除會(huì)刪除類(lèi)型信息,因此唯一可用于無(wú)限制泛型參數(shù)的方法是那些 Object可用的方法。但是,如果將該參數(shù)限制為某類(lèi)型的子集,則可以調(diào)用該子集中的方法。為了應(yīng)用約束,Java 泛型使用了 extends 關(guān)鍵字。

重要的是要理解,當(dāng)用于限定泛型類(lèi)型時(shí),extends 的含義與通常的意義截然不同。此示例展示邊界的基礎(chǔ)應(yīng)用:

public class Coord {public int x, y, z;

    public Coord(int x, int y, int z) {this.x = x;
        this.y = y;
        this.z = z;
    }
}

public class Bounded extends Coord{public Bounded(int x, int y, int z) {super(x, y, z);
    }
}

public class Solid{T item;

    public Solid(T t) {item = t;
    }

    int getX() {return item.x;
    }

    int getY() {return item.y;
    }

    int getZ() {return item.z;
    }

    public static void main(String[] args) {Solidsolid = new Solid<>(new Bounded(1, 2, 3));
        System.out.println(solid.getX());
    }
}
八、通配符

你想在兩個(gè)類(lèi)型間建立某種向上轉(zhuǎn)型關(guān)系,通配符可以產(chǎn)生這種關(guān)系。

public class GenericsAndCovariance {public static void main(String[] args) {Listflist = new ArrayList<>();
//        flist.add(new Apple());
//        flist.add(new Fruit());
//        flist.add(new Object());
        flist.add(null);
        Fruit f = flist.get(0);
    }
}

flist 的類(lèi)型現(xiàn)在是 List,你可以讀作 “一個(gè)具有任何從 Fruit繼承的類(lèi)型的列表”。然而,這實(shí)際上并不意味著這個(gè) List 將持有任何類(lèi)型的 Fruit。通配符引用的是明確的類(lèi)型,因此它意味著 “某種 flist 引用沒(méi)有指定的具體類(lèi)型”。因此這個(gè)被賦值的 List 必須持有諸如 Fruit 或 Apple 這樣的指定類(lèi)型,但是為了向上轉(zhuǎn)型為 Fruit,這個(gè)類(lèi)型是什么沒(méi)人在意。

List 必須持有一種具體的 Fruit 或 Fruit 的子類(lèi)型,但是如果你不關(guān)心具體的類(lèi)型是什么,那么你能對(duì)這樣的 List 做什么呢?如果不知道 List 中持有的對(duì)象是什么類(lèi)型,你怎能保證安全地向其中添加對(duì)象呢?就像在 CovariantArrays.java 中向上轉(zhuǎn)型一樣,你不能,除非編譯器而不是運(yùn)行時(shí)系統(tǒng)可以阻止這種操作的發(fā)生。你很快就會(huì)發(fā)現(xiàn)這個(gè)問(wèn)題。

你可能認(rèn)為事情開(kāi)始變得有點(diǎn)走極端了,因?yàn)楝F(xiàn)在你甚至不能向剛剛聲明過(guò)將持有Apple 對(duì)象的 List 中放入一個(gè) Apple 對(duì)象。是的,但編譯器并不知道這一點(diǎn)。List可能合法地指向一個(gè) List。一旦執(zhí)行這種類(lèi)型的向上轉(zhuǎn)型,你就丟失了向其中傳遞任何對(duì)象的能力,甚至傳遞 Object 也不行。

1.超類(lèi)型通配符

這里,可以聲明通配符是由某個(gè)特定類(lèi)的任何基類(lèi)來(lái)界定的,方法是指定<?super MyClass>,或者甚至使用類(lèi)型參數(shù):<?super T>(盡管你不能對(duì)泛型參數(shù)給出一個(gè)超類(lèi)型邊界;即不能聲明MyClass>)。這使得你可以安全地傳遞一個(gè)類(lèi)型對(duì)象到泛型類(lèi)型中。因此,有了超類(lèi)型通配符,就可以向 Collection 寫(xiě)入了:

public class SuperTypeWildcards {static void writeTo(Listapples) {apples.add(new Apple());
        apples.add(new Orange());
    }
}
2.無(wú)界通配符

無(wú)界通配符看起來(lái)意味著 “任何事物”,因此使用無(wú)界通配符好像等價(jià)于使用原生類(lèi)型。事實(shí)上,編譯器初看起來(lái)是支持這種判斷的:

public class UnboundedWildcards1 {static List list1;
    static Listlist2;
    static void assign1(List list) {list1 = list;
        list2 = list;
    }

    public static void main(String[] args) {assign1(new ArrayList());
    }
}

總結(jié)

普通的類(lèi)和方法只能使用特定的類(lèi)型:基本數(shù)據(jù)類(lèi)型或類(lèi)類(lèi)型。如果編寫(xiě)的代碼需要應(yīng)用于多種類(lèi)型,這種嚴(yán)苛的限制對(duì)代碼的束縛就會(huì)很大,泛型使我們能編寫(xiě)更通用的代碼。

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧

本文題目:JavaSE筆記——泛型-創(chuàng)新互聯(lián)
網(wǎng)址分享:http://jinyejixie.com/article42/pejhc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開(kāi)發(fā)、關(guān)鍵詞優(yōu)化、做網(wǎng)站網(wǎng)站建設(shè)、域名注冊(cè)搜索引擎優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

慈利县| 香港| 基隆市| 西林县| 平南县| 松溪县| 米泉市| 手游| 朔州市| 西乌珠穆沁旗| 邵阳县| 灯塔市| 通榆县| 桂阳县| 镇沅| 仪陇县| 张家界市| 云龙县| 南通市| 特克斯县| 北海市| 洮南市| 孟连| 汝阳县| 墨竹工卡县| 苍梧县| 高台县| 馆陶县| 福州市| 外汇| 都昌县| 图木舒克市| 峡江县| 双峰县| 伊川县| 石首市| 泸溪县| 梅州市| 龙岩市| 龙江县| 和静县|