Java表达式的陷阱——泛型引起的错误

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Never_Blue/article/details/70652659

4、泛型引起的错误

        泛型是Java5新增的知识点,它允许在使用Java类、调用方法时传入一个类型参数,这样就可以让Java类、调用方法动态地改变类型。

4、1 原始类型变量的赋值

       在严格的泛型程序中,使用带泛型声明的类时应该总是为止指定类型实参,但为了与之前版本Java代码保持一致,Java也允许使用带泛型声明的类时不指定类型参数。如果使用带泛型声明的类时没有传入类型参数,那么这个类型参数默认是声明该参数时指定的第一个上限类型,这个类型参数也被称为raw type(原始类型)。
import java.util.ArrayList;
import java.util.List;

public class RawTypeTest {
	public static void main(String[] args) {
		List list = new ArrayList();
		list.add("Java对象1");
		list.add("Java对象2");
		list.add("Java对象3");
		List<Integer> intList = list;
		for ( int i = 0 ; i < intList.size() ; i ++ ) {
			System.out.println(intList.get(i));
		}
	}
}
输出结果为:
Java对象1
Java对象2
Java对象3
       上面程序中先定义了一个不带泛型信息的List集合,其中所有的集合元素都是String类型。然后将List集合赋给List<Integer>变量,但是可以正常输出intList集合的三个字符串元素。
       通过上面介绍可以看出,当程序把一个原始类型的变量赋给一个带泛型信息的变量时,只要它们的类型保持兼容(例如:将List变量赋给List<integer>,无论List集合里实际包含什么类型的元素),系统都不会有任何问题。不过需要指出的是,当把一个原始类型的变量(如List变量)赋给带泛型信息的变量(如List<Integer>)时会有一个潜在的问题:JVM会把集合里盛装的所有元素都当做Integer来处理。上面程序遍历List<Integer>集合时,只是简单地输出每个集合元素,并未涉及集合元素的类型,因此程序并没有出现异常;否则,程序要么在运行时出现ClassCastException异常,要么在编译时提示编译错误。
import java.util.ArrayList;
import java.util.List;

public class RawTypeTest2 {
	public static void main(String[] args) {
		List list = new ArrayList();
		list.add("Java对象1");
		list.add("Java对象2");
		list.add("Java对象3");
		List<Integer> intList = list;
		for ( int i = 0 ; i < intList.size() ; i ++ ) {
			Integer i = intList.get(i);
			System.out.println(i);
		}
	}
}
       上面程序遍历intList集合时,尝试将每个集合元素都赋给Integer变量。由于intList集合的类型本身就是List<Integer>类型,因此编译器会将每个集合元素都当成Integer处理。尝试运行上面程序,将看到如下运行时异常。
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	Duplicate local variable i
       很明显,intList所引用的集合里包含的集合元素的类型是String,而不是Integer,因此程序在运行代码时将会引发ClassCastException异常。

import java.util.ArrayList;
import java.util.List;

public class RawTypeTest3 {
	public static void main(String[] args) {
		List list = new ArrayList();
		list.add("Java对象1");
		list.add("Java对象2");
		list.add("Java对象3");
		List<Integer> intList = list;
		for ( int i = 0 ; i < intList.size() ; i ++ ) {
			String str = intList.get(i);
			System.out.println(intList.get(i));
		}
	}
}
       尝试编译上面程序,将发现编译器直接提示如下编译信息。对于程序中intList集合而言,它的类型是List<Integer>类型,因此编译器会认为该集合的每个元素都是Integer类型,而上面程序尝试将该集合元素赋给一个String类型的变量,因此会提示编译错误。
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	Type mismatch: cannot convert from Integer to String
总结
  1. 当程序把一个原始类型的变量赋给一个带泛型信息的变量时,总是可以通过编译,只是会提示一些警告信息。
  2. 当程序试图访问带有泛型声明的集合的集合元素时,编译器总是把集合元素当成泛型类型处理,并不关心集合里元素的实际类型。
  3. 当程序试图访问带有泛型声明的集合的集合元素时,JVM会遍历每个集合元素自动执行强制类型转化,如果集合元素的实际类型与集合所带的泛型信息不匹配,运行时将引发ClassCastException异常。

4、2 原始类型的擦除

       当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有尖括号里的类型信息都将被抛弃。比如,将一个List<String>类型的对象转型为List,则该List对集合元素的类型检查变成了类型变量的上限(即Object)。
class Tree<T extends Number> {
	T size;
	
	public Tree(T size) {
		this.size = size;
	}

	public T getSize() {
		return size;
	}

	public void setSize(T size) {
		this.size = size;
	}
}

public class ErasureTest {
	public static void main(String[] args) {
		Tree<Integer> a = new Tree<Integer>(6);  //①
		Integer as = a.getSize();
		Tree b = a;
		Number size1 = b.getSize();
		//Integer size2 = b.getSize();  报错:Type mismatch: cannot convert from Number to Integer  
	}
}
       上面程序里定义了一个带泛型声明的Tree类,其类型形参的上限是Number,这个类型形参用来定义Tree类的size属性。在①行代码创建了一个Tree<Integer>对象,所以调用a的getSize()方法时返回Integer类型的值。当把a赋给一个不带泛型信息的b变量时,编译器就会丢失a对象的泛型信息,但因为Tree类型形参的上限是Number类,所以编译器依然知道b的getSize()方法返回Number类型,但具体是Number的哪个子类就不清楚了。
       从上面程序可以看出,当把一个带泛型信息的Java对象赋给不带泛型信息的变量时,Java程序会发生擦除,这种擦除不仅会擦除使用该Java类时传入的类型实参,而且会擦除所有泛型信息,也就是擦除所有尖括号里的信息。
import java.util.ArrayList;
import java.util.List;

class AppleTree<T extends Number> {
	T size;
	
	public AppleTree() {}
	
	public AppleTree(T size) {
		this.size = size;
	}

	public T getSize() {
		return size;
	}

	public void setSize(T size) {
		this.size = size;
	}
	
	public List<String> getApple() {
		List<String> list = new ArrayList<>();
		for ( int i = 0 ; i < 3 ; i ++ ) {
			list.add(new AppleTree<Integer>(10 * i).toString());
		}
		return list;
	}
	
	public String toString() {
		return "AppleTree[size = " + size + "]";
	}
}

public class ErasureTest2 {
	public static void main(String[] args) {
		AppleTree<Integer> a = new AppleTree<Integer>(6);
		for ( String appleTree : a.getApple() ) {
			System.out.println(appleTree);
		}
		AppleTree b = a;
		for ( String appleTree : b.getApple() ) {
			System.out.println(appleTree);
		}
	}
}
       上面程序的main方法中先创建了一个AppleTree<Integer>对象,程序调用该方法的getApple()方法的返回值肯定是List<String>类型的值。将a变量赋给AppleTree类型的b变量时会发生擦除,该AppleTree<Integer>对象将丢失所有的泛型信息,包括 getApple()方法的返回值类型List<Stirng>里的尖括号信息,因此最后会提示“Type mismatch: cannot convert from element type Object to String”。


猜你喜欢

转载自blog.csdn.net/Never_Blue/article/details/70652659