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

来源:互联网 发布:卓智网络是培训公司吗 编辑:程序博客网 时间:2024/06/06 05:56

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”。


1 0
原创粉丝点击