Java泛型

java里同样也有范型的概念,我们可以自定义范型类、范型接口、范型内部类和范型方法等:

class MyGeneric<T> { public T value; }  
class MyOtherGeneric<A, B> { void f(A a, B b) {} }  
interface MyGenericInterface<T> { T getT(); }  
class NormalOuter {  
    class GenericInner<T> {}  
    public <T> void GenericMethod(T value) {}  
}

注意范型不能接受基本类型作为参数,但是可以使用包装类。比如不能使用MyGeneric<int>但可以使用MyGeneric<Integer>。
java中的范型使用了一种“擦除”的机制,即范型只在编译期存在,编译器去除了(不是替代)范型参数,而java虚拟机根本不知道范型的存在。带有不同的范型参数的范型类在运行时都是同一个类:

MyGeneric<Integer> mg1 = new MyGeneric<Integer>();  
MyGeneric<Double> mg2 = new MyGeneric<Double>();  
boolean b = mg1.getClass() == mg2.getClass(); // true;

因为范型类在运行时最终都会被擦除成普通的类,所以不能定义两个类名相同,而范型参数不同的类:

class MyGeneric<T> { public T value; }  
// class MyGeneric<A, B> { public T value; } // The type MyGeneric is already defined  
更悲惨的是,在运行时是无法确切知道范型参数的实际类型。接着上面的代码:
MyOtherGeneric<Integer, Double> mog = new MyOtherGeneric<Integer, Double>();   
TypeVariable<?>[] types = mog.getClass().getTypeParameters();  
System.out.println(Arrays.toString(types)); // [A, B] 

getTypeParameters这个方法只是返回范型参数的占位符,不是具体的某个类型。java的范型在定义时不是假定范型参数支持哪些方法,尽管C++是可以做到的。看下面的代码:

class MyGeneric<T> {   
    public void GuessT(T t) { t.f(); } // error: The method f() is undefined for the type T 
}

如果要让上面的代码编译通过,则需要使用到边界:

class SupportT { void f() {} }  
class MyGeneric<T extends SupportT> {   
    public void GuessT(T t) { t.f(); }  
}

java中之所以用擦除的方式来实现范型,而不是像C++一样根据不同的范型参数具现化不同的类,是由于java语言在发布初期并不支持范型,在发布很长时间后才开始引入范型,为了很原来的类库兼容才使用了擦除。擦除会把参数T换成Object。因为擦除,在范型类里是不能创建范型参数类型的对象,因为编译器不能保证T拥有默认构造器:

class ClassMaker<T> {  
    T create() {  
        // return new T(); // error: Cannot instantiate the type T   
    }  
}

要创建一个范型对象,可以利用Class类型:

class ClassMaker<T> {  
    ClassMaker(Class<T> c) { type = c; }  
    T create() {  
        T t = null;  
        try { t = type.newInstance(); }   
        catch (InstantiationException e) { e.printStackTrace(); }  
        catch (IllegalAccessException e) { e.printStackTrace(); }  
        return t;  
    }  
    private Class<T> type;  
}

同样,如果要创建范型数组的话:

class ClassMaker<T> {  
    ClassMaker(Class<T> c) { type = c; }  
    T[] createArray(int size) {  
        // return new T[size]; // error: Cannot create a generic array of T   
        // return (T[]) new Object[size]; // success!   
        return (T[])Array.newInstance(type, size); // success!   
    }  
    private Class<T> type;  
}

范型的边界(extends)可以加入多个限定,但只能是一个类和多个接口:

interface face1 {}  
interface face2 {}  
class class1 {}  
class Bound<T extends class1 & face1 & face2> {} 

在子类还能加入更多的限定:

interface face3 {}  
class BoundDerived<T extends class1 & face1 & face2 & face3> extends Bound<T> {}  
试试把一个子类的数组赋给父类数组的引用:
class Base1 {}  
class Derived1 extends Base1 {}  
Base1[] base = new Derived1[3];  
base[0] = new Base1(); // java.lang.ArrayStoreException  

父类数组的引用实际指向一个子类的数组,当给元素赋值时,传入一个父类对象对得到一个异常,因为它期望的是一个子类的类型。
如果你希望在编译期时就发现这样的错误,而不是等到异常发生时才发现,可以使用范型:

 ArrayList<Base1> alb = new ArrayList<Derived1>(); 
// Type mismatch: cannot convert from ArrayList<Derived1> to ArrayList<Base1>  

这样你在编译时就会发现这个错误。有时候你可能就是希望一个合法的向上转型,这时可以使用通配符

ArrayList<? extends Base1> aleb = new ArrayList<Derived1>(); // success!   
// aleb.add(new Base1()); // error: The method add(capture#3-of ? extends Base1) //
//in the type ArrayList<capture#3-of ? extends Base1> is not applicable
//for the arguments (Object)   
// aleb.add(new Derived1()); error   
// aleb.add(new Object()); // error   
aleb.add(null); // success   
Base1 b = aleb.get(0); // success 

可以发现带有通配符参数的范型类,它的所有带范型参数的方法调用都不能通过编译,比如上例中的ArrayList.add(T)。甚至连Object类型都不能接受,只能接受null。不带范型参数的方法可以调用,如ArrayList.get。这是因为编译器不知道应该接受什么类型,所以干脆就什么类型都不接受。如果你希望你的范型类在参数是通配符的时候,它的某些方法仍然能被调用,则定义方法的参数类型为Object,而非范型类型T。

与通配符相对的是超类型通配符,即<? super T>

ArrayList<? super Derived1> alsb = new ArrayList<Base1>();  
alsb.add(new Derived1()); //success   
// alsb.add(new Base1()); // error: The method add(capture#4-of ? super Derived1)in the type 
//ArrayList<capture#4-of ? super Derived1> is not applicable for the arguments (Base1)   
Object d = alsb.get(0); // return an Object  

可以看到在接受参数时限制放宽了,因为编译器知道范型的下界,只要是Derived类或它的子类都是合法的。但是在返回时,它只能返回Object类型,因为它不能确定它的上界。

无界通配符,即<?>,与原生类型(非范型类)大体相似,但仍有少许不同:

ArrayList<?> al_any = new ArrayList<Base1>();  
// al_any.add(new Object()); 
// error: The method add(capture#5-of ?) in the type ArrayList<capture#5-of ?> 
//is not applicable for the arguments (Object)   
Object obj = al_any.get(0); // return an Object   
  
ArrayList al_raw = new ArrayList<Base1>();  
al_raw.add(new Object()); 
// warning: Type safety: The method add(Object) belongs to the raw type ArrayList. 
//References to generic type ArrayList<E> should be parameterized   
Object obj2 = al_raw.get(0);  

转载于:https://www.cnblogs.com/cnforest/archive/2012/04/26/2471376.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值