帶你入門Java的泛型

泛型

1、簡單泛型

泛型的主要目的之一就是用來指定容器要持有什麼類型的對象,而且由編譯器來保證類型的正確性。

泛型暫時不指定類型,在使用時決定具體使用什麼類型。通過<T>來實現,T就是類型參數。

(1)元組

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

(2)堆棧

class LinkedStack<T>{
    private class Node {
        T item;
        Node next;
        Node() { item = null; next = null; }
        Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
        boolean end() { return item == null && next == null; }
    }
​
    private Node top = 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;
    }
}
(3)RandomList
class RandomList<T>{
    private ArrayList<T> storage = new ArrayList<>();
    private Random rand = new Random(47);
    public void add(T item){
        storage.add(item);
    }
    public T select(){
        return storage.get(rand.nextInt(storage.size()));
    }
}

2、泛型接口

泛型也可以應用於接口,例如生成器,這是一種專門負責創建對象的類。

import net.mindview.util.Generator;
import java.util.Iterator;
​
class Fibonacci implements Generator<Integer> {
    private int count = 0;
    public Integer next(){
        return fib(count++);
    }
    private int fib(int n){
        if(n<2) return 1;
        return fib(n-2) + fib(n-1);
    }
}
​
class IterableFibonacci implements Iterable<Integer> {
    private Fibonacci fib = new Fibonacci();
    private int n;
    public IterableFibonacci(int count){
        n = count;
    }
​
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            @Override
            public boolean hasNext() {
                return n>0;
            }
​
            @Override
            public Integer next() {
                n--;
                return fib.next();
            }
            public void remove() { // Not implemented
                throw new UnsupportedOperationException();
            }
        };
    }
}

3、泛型方法

  泛型方法使得該方法能夠獨立於類而產生變化。使用泛型方法的時候,通常不必指明參數類型,因為編譯器會為我們找出具體的類型,這稱為類型參數推斷。

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

(1)類型推斷

  使用泛型有時候需要向程序中加入更多的代碼。如下所示:

 Map<Person,List<? extends Pet>> petPerson = 
     new HashMap<Person,List<? extends Pet>>();

在泛型方法中可以通過類型推斷來簡化一部分工作。如下所示:

class New{
    public static <K,V> Map<K,V> map(){
        return new HashMap<K,V>();
    }
​
    public static void main(String[] args) {
        Map<Person,List<? extends Pet>> petPerson = New.map();
    }
}

類型推斷隻對賦值操作有效,其他時候並不起作用。如果將一個泛型方法的結果作為參數,傳遞給另一個方法時,另一個方法需要顯式的類型說明。如下所示:

public class ExplicitTypeSpecification{
    static void f(Map<Person,List<? extends Pet>> petPerson){}
    public static void main(String[] args) {
        f(New.<Person,List<? extends Pet>>map());
    }
}

(2)通用的Generator

import net.mindview.util.Generator;
​
public class BasicGenerator<T> implements Generator<T>{
    private Class<T> type;
    public BasicGenerator(Class<T> type){
        this.type = type;
    }
    public T next(){
        try {
            return type.newInstance();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    public static <T> Generator<T> create(Class<T> type){
        return new BasicGenerator<T>(type);
    }
}

(3)Set實用工具實現數學方法

public class Sets{
    @SuppressWarnings("unchecked")
    protected static <T> Set<T> copy(Set<T> s) {
        if(s instanceof EnumSet)
            return ((EnumSet)s).clone();
        return new HashSet<T>(s);
    }
​
    //並集
    public static <T> Set<T> union(Set<T> a, Set<T> b) {
        Set<T> result = copy(a);
        result.addAll(b);
        return result;
    }
    //交集
    public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
        Set<T> result = copy(a);
        result.retainAll(b);
        return result;
    }
    //差集
    public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
        Set<T> result = copy(superset);
        result.removeAll(subset);
        return result;
    }
    //包含除瞭交集以外的所有元素
    public static <T> Set<T> complement(Set<T> a, Set<T> b) {
        return difference(union(a, b), intersection(a, b));
    }
}

4、擦除

Java泛型是使用擦除來實現的,這意味著當你在使用泛型時,任何具體的類型信息都被擦除瞭,你唯一知道的就是你在使用一個對象。因此List<String>和List<Integer>在運行時事實上是相同的類型,都被擦除成它們的“原生”類型List。

(1)遷移兼容性

泛型類型隻有在靜態類型檢查期間才出現,在此之後,程序中的所有泛型類型都將被擦除,替換為他們的非泛型上界。擦除的核心動機是它使得泛化的客戶端可以用非泛化的類庫來使用,反之亦然,這經常被稱為“遷移兼容性”。

(2)擦除的問題

泛型的所有關於參數的類型信息都丟失瞭,所以不能用於顯式地引用運行時類型的操作之中,例如轉型、instanceof操作和new表達式。

5、擦除的補償

(1)由於擦除原因,無法通過instanceof比較類型。如果引入類型標簽,就可以轉而使用動態的isInstance()。

public class ClassTypeCapture<T>{
    Class<T> kind;
    public ClassTypeCapture(Class<T> kind){
        this.kind = kind;
    }
    public boolean f(Object arg){
        return kind.isInstance(arg);
    }
}

(2)創建類型實例

通過工廠對象來創建實例。如果使用類型標簽,就可以使用newInstance()來創建這個類型的新對象。

class ClassAsFactory<T>{
    T x;
    public ClassAsFactory(Class<T> kind){
        try{
            x = kind.newInstance();
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
}

如果類沒有默認的構造器,上面的案例會創建失敗。為瞭解決這個問題,可以通過顯示的工廠來實現。

interface FactoryI<T>{
    T create();
}
class Foo2<T>{
    private T x;
    public <F extends FactoryI<T>> Foo2(F factory){
        x = factory.create();
    }
}
class IntegerFactory implements FactoryI<Integer>{
    public Integer create(){
        return new Integer(6);
    }
}

另一種方式是模板方法設計模式。

abstract class GenericWithCreate<T>{
    final T element;
    GenericWithCreate(){ element = create(); }
    abstract T create();
}
​
class X{}
​
class Creator extends GenericWithCreate<X>{
    X create(){ return new X(); }
}

(3)泛型數組

無法通過 T[] array = new T[sz] 來創建泛型數組,一般的解決方法是在需要泛型數組的地方都使用ArrayList。

在創建泛型數組時,有以下三種情況:

①創建時強制轉型

public class GenericArray<T>{
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(int sz){
        array = (T[])new Object[sz];
    }
    public T[] rep(){ return array; }
​
    public static void main(String[] args) {
        GenericArray<Integer> gai = new GenericArray<Integer>(10);
        Integer[] ia = gai.rep();//引起ClassCastException
        Object[] oa = gai.rep();
    }
}

②方法返回時強制轉型

class GenericArray2<T>{
    private Object[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(int sz){
        array = new Object[sz];
    }
    public T[] rep(){ return (T[])array; }
    public static void main(String[] args) {
        GenericArray<Integer> gai = new GenericArray<Integer>(10);
        Integer[] ia = gai.rep();//引起ClassCastException
        Object[] oa = gai.rep();
    }
}

③使用Array.newInstance()

以上兩種方法都無法創建具體類型的數組,無法推翻底層的數組類型,隻能是Object[]。通過傳入類型標記Class<T>,可以從擦除中恢復。

class GenericArray3<T>{
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(Class<T> type,int sz){
        array = (T[]) Array.newInstance(type,sz);
    }
    public T[] rep(){ return array; }
    public static void main(String[] args) {
        GenericArray<Integer> gai = new GenericArray<Integer>(Integer.class,10);
        Integer[] ia = gai.rep();//可以正常運行
        Object[] oa = gai.rep();
    }
}

6、邊界

邊界使得你可以在用於泛型的參數類型上設置限制條件,可以按照自己的邊界類型來調用方法。

public class Test {
    public static void main(String[] args) {
        Man m = new Man();
        m.hear();
        m.smell();
    }
}
​
interface SuperPower{}
interface SuperHearing extends SuperPower{
    void hearSubtleNoises();
}
interface SuperSmell extends SuperPower{
    void trackBySmell();
}
​
class SuperHero<POWER extends SuperPower>{
    POWER power;
    SuperHero(POWER power){ this.power = power; }
    POWER getPower(){ return power; }
}
​
class CaineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER>{
    CaineHero(POWER power){ super(power); }
    void hear(){ power.hearSubtleNoises(); }
    void smell(){ power.trackBySmell(); }
}
​
class SuperHearSmell implements SuperHearing,SuperSmell{
​
    @Override
    public void hearSubtleNoises() {
        System.out.println("hearSubtleNoises");
    }
​
    @Override
    public void trackBySmell() {
        System.out.println("trackBySmell");
    }
}
​
class Man extends CaineHero<SuperHearSmell>{
    Man(){ super(new SuperHearSmell()); }
}

7、通配符

(1)List<? extends Fruit>協變

表示具有任何從Fruit繼承的類型的列表。List<? extends Fruit>可以合法地指向一個List<Apple>。一旦執行這種類型的向上轉型,就將丟失掉向其中傳遞任何對象的能力,甚至是傳遞Object也不行。

List<? extends Fruit> flist =
    Arrays.asList(new Apple());
//Compile Error:can't add any type of object
//add()的參數是<? extends Fruit>,編譯器不知道需要Fruit的哪個
//具體的子類型,因此不接受任何類型的Fruit
//flist.add(new Apple());
//flist.add(new Fruit());
//flist.add(new Object());
flist.add(null);//Legal but uninteresting
Apple a = (Apple)flist.get(0);//No warning
Fruit f = flist.get(0);//No warning
flist.contains(new Apple());//參數是Object
flist.indexOf(new Apple());//參數是Object

(2)List<? super Fruit>逆變

超類型通配符可以安全地傳遞一個類型對象到泛型類型中。List<? super Fruit>意味著向其中添加Fruit或Fruit的子類型是安全的。

List<? super Fruit> flist = new ArrayList<Fruit>();
        flist.add(new Apple());
        flist.add(new Fruit());
//Error:Incompatible Type
//Fruit f = flist.get(0);
Object f = flist.get(0);//OK,but type information has been lost

(3)無界通配符List<?>

List實際上表示“持有任何Object類型的原生List”,List<?>表示“具有某種特定類型的非原生List,隻是我們不知道那種類型是什麼”,List<? extends Object>表示“類型是Object的導出類”。

無界通配符的一個重要應用:處理多個泛型參數時,允許一個參數可以是任何類型,同時為其他參數確定某種特定類型。

Map<String,?> map = new HashMap<String,Integer>;
map = new HashMap<String,String>;

原生Holder與Holder<?>是大致相同的事物,但存在不同。它們會揭示相同的問題,但是後者將這些問題作為錯誤而不是警告報告。

static void rawArgs(Holder holder,Object arg){
    //holder.set(arg);
    //Warning:Unchecked call to set(T) as member
    //of the raw type Holder
    //holder.set(new Wildcards());//Same Warning
    //Can't do this:don't have any 'T'
    //T t = holder.get();
    //OK,but type infomation has been lost
    Object obj = holder.get();
}
//Similar to rawArgs(),but errors instead of warnings
static void unboundedArg(Holder<?> holder,Object arg){
    //holder.set(arg);
    //Error:set(capture of ?) in Holder<capture of ?>
    //cannot be applied to (Object)
    //holder.set(new Wildcards());//Same Error
    //Can't do this:don't have any 'T'
    //T t = holder.get();
    //OK,but type infomation has been lost
    Object obj = holder.get();
}

(4)捕獲轉換

未指定的通配符類型被捕獲,並被轉換為確切類型。在f2()中調用f1(),參數類型在調用f2()的過程中被捕獲,因此它可以在對f1()的調用中被使用。不能從f2()中返回T,因為T對於f2()來說是未知的。

static <T> void f1(Holder<T> holder){
    T t = holder.get();
     System.out.println(t.getClass().getSimpleName());
}
​
static <T> void f2(Holder<T> holder){
    f1(holder);
}

8、問題

(1)任何基本類型都不能作為類型參數

(2)實現參數化接口

一個類不能實現同一個泛型接口的兩種變體。將泛型參數移除掉後,這段代碼就可以正常編譯瞭。

interface Payable<T>{}
​
class Employee implements Payable<Employee>{}
​
//Compile Error:cannot be inherited with different type arguments
class Hourly extends Employee implements Payable<Hourly>{}

(3)轉型和警告

使用帶有泛型類型參數的轉型或instanceof不會有任何效果。

由於擦除原因,編譯器無法知道這個轉型是否安全,並且pop()方法實際上並沒有執行任何轉型。如果沒有@SuppressWarnings註解,編譯器將對pop()產生“Unchecked cast”警告。

private int index = 0;
private Object[] storage;
@SuppressWarnings("unchecked")
public T pop(){ return (T)storage[--index]; }

(4)重載

由於擦除的原因,重載方法將產生相同的類型簽名,導致程序不能編譯。

public class UseList<W,T>{
     void f(List<T> v){}
     void f(List<W> v){}
 }

(5)基類劫持瞭接口

一旦為Comparable確定瞭ComparablePet參數,那麼其他任何實現類都不能與ComparablePet之外的任何對象比較。在前面的“實現參數化接口”章節裡面的第一個例子,就體現瞭基類劫持接口。

public class ComparablePet
implements Comparable<ComparablePet> {
  public int compareTo(ComparablePet arg) {
      return 0;
  }
}
​
class Cat extends ComparablePet implements Comparable<Cat>{
  // Error: Comparable cannot be inherited with
  // different arguments: <Cat> and <Pet>
  public int compareTo(Cat arg) { return 0; }
} ///:~
​
class Hamster extends ComparablePet
    implements Comparable<ComparablePet>{
    public int compareTo(ComparablePet arg) {
        return 0;
    }
}

9、自限定

class Subtype extends BasicHolder<Subtype> {}這樣用,就構成自限定瞭。從定義上來說,它繼承的父類的類型參數是它自己。

從使用上來說,Subtype對象本身的類型是Subtype,且Subtype對象繼承而來的成員(element)、方法的形參(set方法)、方法的返回值(get方法)也是Subtype瞭(這就是自限定的重要作用)。這樣Subtype對象就隻允許和Subtype對象(而不是別的類型的對象)交互瞭。

class BasicHolder<T> {
    T element;
    void set(T arg) { element = arg; }
    T get() { return element; }
    void f() {
        System.out.println(element.getClass().getSimpleName());
    }
}
​
class Subtype extends BasicHolder<Subtype> {}
​
public class CRGWithBasicHolder {
    public static void main(String[] args) {
        Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype();
        st1.set(st2);
        st2.set(st3);
        Subtype st4 = st1.get().get();
        st1.f();
    }
} /* Output:
Subtype
*/

10、異常

由於擦除原因,將泛型應用於異常是非常受限的。但是,類型參數可能會在一個方法的throws子句中用到,這使得你可以編寫隨檢查型異常的類型而發生變化的泛型代碼。

interface
Processor<T,E extends Exception> {
    void process(List<T> resultCollector) throws E;
}
​
class
ProcessRunner<T,E extends Exception>
        extends ArrayList<Processor<T,E>> {
    List<T> processAll() throws E {
        List<T> resultCollector = new ArrayList<T>();
        for(Processor<T,E> processor : this)
            processor.process(resultCollector);
        return resultCollector;
    }
}
​
class Failure extends Exception {}
​
class Processor1 implements
        Processor<String,Failure> {
    static int count = 3;
    public void process(List<String> resultCollector)
            throws Failure1_1, Failure1_2 {
        if(count-- > 1)
            resultCollector.add("Hep!");
        else
            resultCollector.add("Ho!");
        if(count < 0)
                throw new Failure1();
    }
}
​
public class Test {
    public static void main(String[] args) {
        ProcessRunner<String,Failure> runner =
                new ProcessRunner<String,Failure>();
        for(int i = 0; i < 3; i++)
            runner.add(new Processor1());
        try {
            System.out.println(runner.processAll());
        } catch(Failure e) {
            System.out.println(e);
        }
    }
}

總結

本篇文章就到這裡瞭,希望能給您帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: