Java 泛型

    泛型是編程語言內置的特性,它使得你的代碼更可靠。泛型通過在編譯時探測到更多的Bug來增強代碼的穩定性。在Java中,Collections框架使用泛型最多,而許多人也是通過學習Collections,才學習泛型的。其實其他地方也可以用泛型,我們可以自己從零開始來寫一個使用泛型的例子。

public class Box {
        private Object object;
        public void add(Object object) {
            this.object = object;
        }
        public Object get() {
            return object;
        }
    }

public class BoxDemo {
    public static void main(String[] args) {
        // ONLY place Integer objects into this box!
        Box integerBox = new Box();
        // Imagine this is one part of a large application
        // modified by one programmer.
         integerBox.add("10"); // note how the type is now String
        // ... and this is another, perhaps written
        // by a different programmer
        Integer someInteger = (Integer)integerBox.get();
        System.out.println(someInteger);
    }
}

    本來integerBox在註釋例說明應加入Integer對象,但粗心的程序員可能加入的是其他對象,這樣編譯雖然通過,但在運行時會拋出java.lang.ClassCastException異常,這種不易察覺的Bug可以通過泛型[Generics]來解決,它會使得編譯器在編譯時就瞭解必要信息,從而能在編譯時發現錯誤,而不是要等到運行時,程序崩潰才知道有錯誤。 

    泛類型[Generic Types]

    我們用泛型來更新Box類,如下:

    T is a formal type parameter of the Box class

/**

* Generic version of the Box class.

*/

public class Box<T> {

    private T t; // T stands for "Type" 
     public void add(T t) {
        this.t = t;
    }
    public T get() {
        return t;
    }
}
"Box<Integer> integerBox;"是一個通用類型調用,將一個類型實參Integer傳遞給泛化的Box類,

這樣便得到了一個具體的類。這裏泛化的Box類稱爲a parameterized type,即一種參數化類型。

可以實例化integerBox = new Box<Integer>();這樣調用get方法後直接返回Integer對象引用,

不需要轉型。如示例代碼:

public class BoxDemo1 {
    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.add(new Integer(10));
        Integer someInteger = integerBox.get(); // no cast!  無需轉型
        System.out.println(someInteger);
    }
}
    如果你加入一個與Integer不相容的類型對象,如String對象,編譯時將會得到一個警告。本例中改爲

integerBox.add("10");將會有compile-time error.

實際上,編譯器將所有泛化信息都擦除,在系統上只剩下Box.class,而不會有T.classT.java,因爲T

僅僅是類型變量,而不是實際的類型,這些與類型擦除(Type Erasure)有關。

不想譯了,英文意思很清楚。

Also note that a generic type may have multiple type parameters, but each parameter must

be unique within its declaring class or interface. A declaration of Box<T,T>, for example, would

generate an error on the second occurrence of T, but Box<T,U>, however, would be allowed. 

 

最常用的類型參數名:

The most commonly used type parameter names are:

· E - Element (used extensively by the Java Collections Framework)

· K - Key

· N - Number

· T - Type

· V - Value

· S,U,V etc. - 2nd, 3rd, 4th types

You'll see these names used throughout the Java SE API and the rest of this tutorial. 

1. 定義簡單的泛型

    從java.util包中截取一段ListIterator接口的定義,如下:

public interface List<E> {

     void add(E x);

     Iterator<E> iterator();

}

public interface Iterator<E> {

     E next();

     bolean hasNext();

}

 

尖括號中的元素是ListIterator接口的形式化類型參數,我們可以這樣調用List的泛型聲明,"List<Integer>"這裏聲明中的所有E將被實際的類型實參Integer代替。

     你可以認爲List<E>是泛化版List,而List<Integer>則是具體版本的List。但是要注意,List<E>不會擴展出很多具體的類,如List<Integer>List<Double>等等,在源代碼、字節碼、磁盤及內存中都不會有List<E>具體版本代碼的存在。

     編譯器將泛型聲明編譯一次,並且只保存爲單個class文件,與普通的類和接口一樣,對應一個class文件。這就是類型擦除。

     我們還可以定義泛化的方法和構造器,例如下面定義了一個泛化方法

public class Box<T> {
    private T t;         
     public void add(T t) {
        this.t = t;
    }
    public T get() {
        return t;
    }
    public <U> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }
    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.add(new Integer(10));
        integerBox.inspect("some text");
    }
}

output

T: java.lang.Integer

U: java.lang.String

2.泛型及其子類型

先看一小段代嗎,是否合法?

    List<String> ls = new ArrayList<String>(); // 1

List<Object> lo = ls; // 2 

1行是合法的,關鍵是第2行,就是說:一個List<String> is a List<Object>?即List<String>是不是List<Object>的子類?再增加兩行代碼,即可知道結果。

lo.add(new Object()); // 3

String s = ls.get(0); // 4: Attempts to assign an Object to a String!

執行第3行後,ls並不一定全是包含String,我們可以加入任意的Object,這樣在第4行取出對象時,會出現讓人驚訝的結果。

所以List<String>不是List<Object>的子類。

總之,如果FooBar的子類型(子類或子接口)G是泛型聲明,則G<Foo>並不是G<Bar>的子類型[subtype],這裏subtype包括subclasssubinterface

下面我們來學習更加靈活可行的泛類型。

先介紹通配符[Wildcards]----

Collection<?> collection of unknown,也就是它的元素類型可以是任何類型。符號?就是通配符。

Collection<?> c = new ArrayList<String>();

c.add(new Object()); // Compile time error

因爲c是包含任意類型的Collection?代表任意類型,但我們知道她就是一個Object,除了null,其他類型都是?的子類。

下面來看兩個方法的對比:

public void drawAll(List<Shape> shapes) {

    for (Shape s: shapes) {

        s.draw(this);

   }

} 

public void drawAll(List<? extends Shape> shapes) {

    ...

}

There is a small but very important difference here: we have replaced the type List<Shape> with List<? extends Shape>. Now drawAll() will accept lists of any subclass of Shape, so we can now call it on a List<Circle> if we want. 

List<? extends Shape> is an example of a bounded wildcard. The ? stands for an unknown type, just like the wildcards we saw earlier. 

However, in this case, we know that this unknown type is in fact a subtype of Shape. (Note: It could be Shape itself, or some subclass; 

it need not literally extend Shape.) We say that Shape is the upper bound of the wildcard.

? extends Shape是一種有上界的通配符,它表示一種未知的Shape子類。

此時List<Shape> List<Circle> List<Triangle>List<? extends Shape>subtype,因爲Shape Circle Triangle都是Shape的子類型[subtype] 

public static void addRegistry(Map<String, ? extends Person> registry) {......};方法addRegistry中的參數類型,僅僅使得該方法更加通用,使得該方法接收更多的類型,例如EmployeePersonsubtype,則可接收Map<String, Employee>類型的實參。 

定義泛化方法:

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {

    for (T o : a) {

        c.add(o); // Correct

    }

}

We can call this method with any kind of collection whose element type is a supertype of the element type of the array.

Object[] oa = new Object[100];

Collection<Object> co = new ArrayList<Object>();

fromArrayToCollection(oa, co); // T inferred to be Object

String[] sa = new String[100];

Collection<String> cs = new ArrayList<String>();

fromArrayToCollection(sa, cs); // T inferred to be String

fromArrayToCollection(sa, co); // T inferred to be Object

Integer[] ia = new Integer[100];

Float[] fa = new Float[100];

Number[] na = new Number[100];

Collection<Number> cn = new ArrayList<Number>();

fromArrayToCollection(ia, cn); // T inferred to be Number

fromArrayToCollection(fa, cn); // T inferred to be Number

fromArrayToCollection(na, cn); // T inferred to be Number

fromArrayToCollection(na, co); // T inferred to be Object 

fromArrayToCollection(na, cs); // compile-time error

Notice that we don't have to pass an actual type argument to a generic method. The compiler infers the type argument for us, based on the types of the actual arguments. It will generally infer the most specific type argument that will make the call type-correct. 

One question that arises is: when should I use generic methods, and when should I use wildcard types?

通過比較以下兩段代碼:

interface Collection<E> {

    public boolean containsAll(Collection<?> c);

    public boolean addAll(Collection<? extends E> c);

} 

我們可以使用泛化方法代替: 

interface Collection<E> {

    public <T> boolean containsAll(Collection<T> c);

    public <T extends E> boolean addAll(Collection<T> c);

    // Hey, type variables can have bounds too!

}

在上述代碼中,兩個方法中的類型參數T只使用了一次,並且返回類型不依賴於方法的任何參數的類型,沒有其他類型依賴於參數類型TT的唯一作用就是允許使用不同的實際類型來調用,在這種情況下,應該使用通配符?

Generic methods allow type parameters to be used to express dependencies among the types 

of one or more arguments to a method and/or its return type. If there isn't such a dependency,

 a generic method should not be used.

泛化方法是允許類型參數來表達參數與參數和返回值之間的依賴關係,如果參數的類型、返回值的類型之間沒有相互依賴關係,就不要使用泛化方法。

我們也可以以同時使用泛化方法和通配符?,例如Collections.copy()

class Collections {

    public static <T> void copy(List<T> dest, List<? extends T> src) {

    ...

}

Note the dependency between the types of the two parameters. Any object copied from the source list, src,

 must be assignable to the element type T of the destination list, dst. 

So the element type of src can be any subtype of T---we don't care which. The signature of copy expresses the dependency using a type parameter, but uses a wildcard

 for the element type of the second parameter. 

We could have written the signature for this method another way, without using wildcards at all:

class Collections {

    public static <T, S extends T> 

           void copy(List<T> dest, List<S> src) {

    ...

} 

This is fine, but while the first type parameter is used both in the type of dst and in the bound of the second type parameter, S, S itself is only used once, in the type of src--nothing else depends on it. This is a sign that we can replace S with a wildcard. Using wildcards is clearer and more concise than declaring explicit type parameters, 

and should therefore be preferred whenever possible. 

Wildcards also have the advantage that they can be used outside of method signatures, as the types of fields, local variables and arrays. Here is an example.

Returning to our shape drawing problem, suppose we want to keep a history of drawing requests. We can maintain the history in a static variable inside class Shape, and have drawAll() store its incoming argument into the history field.

static List<List<? extends Shape>> history = 

            new ArrayList<List<? extends Shape>>();

public void drawAll(List<? extends Shape> shapes) {

    history.addLast(shapes);

    for (Shape s: shapes) {

        s.draw(this);

    }

}

總之,若參數的類型、返回值的類型之間沒有依賴關係,且類型參數T只出現一次時,沒有其他類型依賴於T,則T就應該用通配符?來代替。否則應該使用泛化方法。

 

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