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
标签:范型,Java,ArrayList,Object,泛型,new,type,class 来源: https://blog.csdn.net/weixin_30826761/article/details/95744050