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就应该用通配符?来代替。否则应该使用泛化方法。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值