关于 Java 泛型的一些有趣的例子

有以下的代码:

 1      try {
 2             ArrayList<String> lstA = new ArrayList<String>();
 3             ArrayList<Integer> lstB = new ArrayList<Integer>();
 4             
 5 //            ArrayList<Object> c = (ArrayList<Object>)lstA;
 6             
 7             Object d = lstA;
 8             ArrayList<Object> e = (ArrayList<Object>)d;
 9             System.out.println("e.toString=" + e.toString());
10             
11             List<String> f = lstA;
12             System.out.println("f.toString=" + f.toString());
13             
14             if (lstA.getClass() == lstB.getClass()) {
15                 System.out.println("lstA.class == lstB.class");
16             }else{
17                 System.out.println("lstA.class != lstB.class");
18             }
19             
20             String[] g = {"","2"};
21             Object[] h = (Object[])g;
22             System.out.println("h.toString=" + h.toString());
23         } catch (Exception ex) {
24             System.out.println("cast error!--" + ex.getMessage());
25         }

运行以上代码,估计会输出什么?会产生异常吗?

实际上,这些代码都能正常运行。

先看看被注释掉的第 5 行:

ArrayList<Object> c = (ArrayList<Object>)lstA;

这行会产生编译错误,所以被注释掉了。可是换一种方式却可以通过编译器的检查,也就是接下来的7-9行:

Object d = lstA;
ArrayList<Object> e = (ArrayList<Object>)d;
System.out.println("e.toString=" + e.toString());

这几行代码不光是骗过编译器,也能够正常运行。

继续看接下来的 11-12 行:

List<String> f = lstA;
System.out.println("f.toString=" + f.toString());

与前面 7-9 行的有所不同的是,前面是对“类型参数”做了转换,而此处是类型本身变了,即将 ArrayList 转为其超类接口 List ,这能行吗?

运行便知!OK,执行通过!

 

继续看看接下来的 14-18 行:

if (lstA.getClass() == lstB.getClass()) {
      System.out.println("lstA.class == lstB.class");
}else{
      System.out.println("lstA.class != lstB.class");
}

猜猜这里输出的结果是什么?

答案是:

"lstA.class == lstB.class"

为什么呢?难道 lstA 和 listB 是同一个类型,可是他们明明一个是 ArrayList<String>,一个是ArrayList<Integer> 。

原来这就是泛型的障眼法。

与 C# 的泛型不同,java 的泛型实际上更像是个语法上的东西,在运行时是没有“泛型”的存在的,运行时 lstA 和 lstB 的类型都是 ArrayList (或者说等同 ArrayList<Object>)。

 

我们再看看最后的 20-22 行:

String[] g = {"","2"};
Object[] h = (Object[])g;
System.out.println("h.toString=" + h.toString());

这和前面的 11 行类似,对于数组同样也行得通。当然这跟泛型没什么关系,这种称为“协变”,只是形式上与一些泛型的操作类似,所以放在一起比照。

以上全部代码在我本机运行的输出结果如下:

e.toString=[]
f.toString=[]
lstA.class == lstB.class
h.toString=[Ljava.lang.String;@e09713

 

 

再看看另外的一个例子:

 1     public static void main(String[] args) {
 2         try {
 3             testCaster1(SQLException.class);
 4         } catch (SQLException e) {
 5             System.out.println(String.format("SQLException=[%s]--%s", e.getClass().getName(), e.getMessage()));
 6         } catch (Exception e) {
 7             System.out.println(String.format("Exception=[%s]--%s", e.getClass().getName(), e.getMessage()));
 8         }
 9         
10         try {
11             testCaster2(SQLException.class);
12         } catch (SQLException e) {
13             System.out.println(String.format("SQLException=[%s]--%s", e.getClass().getName(), e.getMessage()));
14         } catch (Exception e) {
15             System.out.println(String.format("Exception=[%s]--%s", e.getClass().getName(), e.getMessage()));
16         }
17     }
18     
19     
20     private static <E extends Throwable> void testCaster1(Class<E> clazz) throws E{
21         try {
22             throw new SQLException("测试抛出的SQL错误。");
23         } catch (Throwable e) {
24             throw (E)e;
25         }
26     }
27     private static <E extends Throwable> void testCaster2(Class<E> clazz) throws E{
28         try {
29             throw new IOException("测试抛出的IO错误。");
30         } catch (Throwable e) {
31             throw (E)e;
32         }
33     }

两个泛型方法 testCaster1 和 testCaster2 的逻辑是一样,用泛型 E 定义了 throws 抛出的异常类型,同时在内部捕捉 Throwable 并将其转换为声明的泛型参数返回。

等等!!

细心的你是不是发现了什么不对劲的地方?...

是的,第 24 行和 31 行,明显异常类型 E 是泛型,调用者指定的具体类型是不确定的,而这两个方法一个抛出 SQLException,一个抛出 IOException,

那接下来的转换要引发 ClassCastException 了吧?

 

不过答案是否定的!它们运行得很好。

 

看 main 方法中的 2-8 行,调用 testCaster1 抛出的 SQLException,最终被 catch(SQLException e) 捕捉到。

再看接下来的 10-16 行,调用 testCaster2 抛出的 IOException,最终被 catch(Exception e) 捕捉到。

在 testCaster2 的内部抛出的 IOException 似乎并没有按照调用者指定的泛型参数 SQLException 做强制转换,因为并没有 ClassCastException 发生。

哈哈,这又是泛型的障眼法!

全部输出结果如下:

SQLException=[java.sql.SQLException]--测试抛出的SQL错误。
Exception=[java.io.IOException]--测试抛出的IO错误。

 

 

 

 

 

 

posted @ 2014-02-21 13:20  haiq  阅读(1536)  评论(0编辑  收藏  举报