标签:
public class ListErr {
public static void main(String[] args) {
//创建一个只想保存字符串的List集合
List strList = new ArrayList();
strList.add("Hello World");
strList.add("Good Morning");
strList.add("你好");
//"不小心"把一个Integer对象"丢进"了集合
strList.add(5);
for(int i = 0; i < strList.size(); i++){
//因为List里取出的全部是Object,所以必须强制类型转换
//最后一个元素将出现ClassCastException异常
// java.lang.ClassCastException:java.lang.Integer cannot be cast to java.lang.String
String str = (String) strList.get(i);
}
}
}
上面的程序创建了一个List集合,并且只是希望该List对象保存字符串对象,但是我们没有办法添加任何的限制,所以程序中“不小心”丢进了一个Integer对象,这将导致程序发生java.lang.ClassCastException异常。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常,因此导致此类错误编码过程中不易发现。通过上面程序,可以使我们了解到Java集合的两个问题:
public class GenericList {
public static void main(String[] args) {
//创建一个只想保存字符串的List集合
List<String> strList = new ArrayList<String>();
strList.add("Hello World");
strList.add("Good Morning");
strList.add("你好");
//下面代码将引起编译错误
strList.add(5);//01
for(int i = 0; i < strList.size(); i++){
//下面代码无须强制类型转换
String str = strList.get(i);//02
}
}
}上面程序将在01处引起编译错误,因为strList集合只能添加String对象,所以不能将Integer对象"丢进"该集合。而且程序在02处不需要进行强制类型转换,因为strList对象可以“记住”它的所有集合元素都是String类型。public interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean addAll(int index, Collection<? extends E> c);
boolean removeAll(Collection<?> c);
}可以看到,在List接口中采用泛型化定义之后,<E>中的E表示类型形参,可以接收具体的类型实参,并且此接口定义中,凡是出现E的地方均表示相同的接受自外部的类型实参。public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
//省略
}通过上面的介绍可以发现,我们可以为任何类增加泛型声明(并不是只有集合类才可以使用泛型声明,虽然泛型是集合类的重要使用场所),public class Apple<T> {
private T data;
public Apple(T data){
this.data = data;
}
public void setData(T data){
this.data = data;
}
public T getData(){
return this.data;
}
public static void main(String[] args) {
//因为传给T形参的是String类型,所以构造函数只接受String类型
Apple<String> a1 = new Apple<String>("苹果");
System.out.println(a1.getData());
//因为传给T形参的是Double实际类型,所以构造器的参数只能是Double或者double
Apple<Double> a2 = new Apple<Double>(1.23);
System.out.println(a2.getData());
}
}
上面程序定义了一个带泛型声明的Apple<T>类,实际使用Apple<T>类时会为T形参传入实际类型,这样就可以生成如Apple<String>、Apple<Double>等等形式的多个逻辑子类(物理上并不存在)。//定义类继承Apple类,Apple类不能跟类型形参
public class A extends Apple<T>{}正确的写法应该是:public class A extends Apple<String>{}如果从Apple<String>类派生子类,则在Apple类中所有使用T类型形参的地方都将被替换成String类型,即它的子类将会继承到String
getData()和void setData(String data)两个方法,如果子类需要重写父类的方法,必须注意这一点。此处不再赘述。//定义一个如此的方法
public void test(List<Object> ol){
for(int i = 0; i < ol.size(); i++){
System.out.println(ol.get(i));
}
}表面上看起来,上面方法声明没有问题,这个方法声明确实没有任何问题。问题是调用该方法传入实际参数值时可能不是我们所期望的,如下调用此方法://创建一个List<String>对象 List<String> strList = new ArrayList<String>(); //将strList作为参数来调用前面的test方法 test(strList);//01编译上面程序,将在01处发生如下编译错误:
The method test(List<Object>) in the type Apple<T> is not applicable for the arguments (List<String>)显然得到List<Object>不是List<String>的父类,那么它们的父类是谁呢?为了表示各种泛型的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将问号作为类型实参传给泛型类,例如List<?>。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。因此可以把List<?>在逻辑上认为是List<Object>、List<String>等所有List<具体类型实参>的父类(类型通配符一般是使用?代替具体的类型实参,注意是类型实参而不是类型形参)。
List<?> c = new ArrayList<String>(); //下面程序引起编译时错误 c.add(new Object());因为我们不知道上面程序中c的集合里元素的类型,所以不能向其中添加对象。根据前面的List<E>接口定义的代码可以发现:add方法有类型参数E作为集合的元素类型,所以我们传给add的参数必须是E类的对象或者其子类的对象。但因为在该例中不知道E是什么类型,所以程序无法将任何对象“丢进”该集合。唯一的例外是null,它是所有引用类型的实例。如何解决这个问题呢,下面介绍的类型通配符的上限和下限将解决这个问题。
public void test(List<?> c){
for(int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
}现在我们对类型实参进一步限制:只能是Number类及其子类。此时,需要用到类型通配符上限,将上面的代码修改为如下:public void test(List<? extends Number> c){
for(int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
} 同理,如果只能是Integer类本身及其父类呢?此时,需要用到类型通配符下限,示例代码为如下:public void test(List<? super Integer> c){
for(int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
} 同样可以使用类型通配符上下限设置类型形参。public class Apple<T extends Number> {
private T data;
public Apple(T data){
this.data = data;
}
public void setData(T data){
this.data = data;
}
public T getData(){
return this.data;
}
}public class TestErasure{
public static void main(String[] args) {
Apple<Integer> a = new Apple<Integer>(6); //01
//a的getSize方法返回Integer对象
Integer as = a.getData();
//把a对象赋给Apple变量,会丢失尖括号里的类型信息
Apple b = a; //02
//b只知道sizede类型是Number
Number size1 = b.getData();
//下面代码引起编译错误
Integer size2 = b.getData(); //03
}
} 从上面程序可知,当把a赋给一个不带泛型信息的b变量时,编译器就会丢失a对象的泛型信息,即使所有尖括号里的信息都被丢失。但因为Apple的类型形参的上限是Number类,所有编译器依然知道b的getSize方法返回Number类型,但具体是Number的哪个子类就不清楚了。这就是类型的擦除。标签:
原文地址:http://blog.csdn.net/jzpkzm/article/details/51718407