2016-11-29 172 views
4

我一直在阅读Java泛型教程和Stackoverflow中涉及泛型的一些线程,但仍无法理解特定情况。 这就是:Java泛型 - 原始类型和参数化类型转换

public class Box<T> 
{ 
    private T t; 

    public T getT() 
    { 
     return t; 
    } 

    public void setT (T t) 
    { 
     this.t = t; 
    } 

    public static void main (String[] args) 
    { 
     Box<Integer> intBox = new Box<Integer>(); 
     Box rawBox = intBox; 
     rawBox.setT("NBA"); 
     System.out.println(rawBox.getT()); 
     System.out.println(intBox.getT()); 
     /*1*/ //System.out.println(intBox.getT().toString()); 
    } 
} 

事情是这样的,第一个打印我的理解,那就是

System.out.println(rawBox.getT()); 

版画NBA,因为rawBox是原料型箱的T和它“给”我们对象。

我不明白的是第二打印:

System.out.println(intBox.getT()); 

它打印NBA。 intBox是一个泛型类型(在这种情况下是整数Box),这意味着它的getter方法应该返回一个T类型的值(在这种情况下是Integer),所以我理解的是那里的String对象应该是转换为Integer(因为这是给予Box T的参数类型)并且应该在运行时引发ClassCastException,但它不会发生,为什么?

顺便说一句,评论数/ /增加了混乱,因为如果我取消注释它,它会导致ClassCastException异常在ruuntime(字符串不能转换为整数)被提出,我不t了解

感谢所有:)

回答

1

类型擦除是原因。在运行时,所有的Box实例都是相同的,它们都包含引用。泛型只对程序员有利,以传达类型信息并保持程序的输入。

2

有时很难猜测Java将在哪里插入checked casts。一般来说,它只会在必要时插入它们。理解你看到的行为的最好方法是检查字节码!

如果我们(和/*1*/取消注释编译之后)运行javap -c Box.class,我们看到:

public static void main(java.lang.String[]); 
    Code: 
     ... 
     20: invokevirtual #8     // Method getT:()Ljava/lang/Object; 
     23: invokevirtual #9     // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
     26: getstatic  #7     // Field java/lang/System.out:Ljava/io/PrintStream; 
     29: aload_1 
     30: invokevirtual #8     // Method getT:()Ljava/lang/Object; 
     33: invokevirtual #9     // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
     36: getstatic  #7     // Field java/lang/System.out:Ljava/io/PrintStream; 
     39: aload_1 
     40: invokevirtual #8     // Method getT:()Ljava/lang/Object; 
     43: checkcast  #10     // class java/lang/Integer 
     46: invokevirtual #11     // Method java/lang/Integer.toString:()Ljava/lang/String; 
     49: invokevirtual #12     // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
     52: return 

你可以在这里看到的唯一一次JVM实际上需要强制转换为整数,是#43(checkcast)。这是它可以invokevirtual Integer.toString()

println(#33)呼叫不需要造型,因为println需要一个Object,而不是一个Integer(也许你以为你打电话println(int),但你不是)。所以JVM永远不需要检查它是一个整数,因为它不需要。

如果不是调用println(Object),而是调用接受Integer的方法,则应该看到ClassCastException

例如,这样的:

... 
    print(intBox.getT()); 
} 

private static void print(Integer integer) { 
    System.out.println(integer); 
} 

将进行转换:

26: aload_1 
    27: invokevirtual #8     // Method getT:()Ljava/lang/Object; 
    30: checkcast  #10     // class java/lang/Integer 
    33: invokestatic #11     // Method print:(Ljava/lang/Integer;)V 
+0

首先,很好的回答, 我明白你对println方法的接受,它接受Object类型的参数。 这仍然不解释为什么不调用println方法,下面的语句: intBox.getT();不会引发ClassCastException:String不能转换为Integer。 该声明编译并运行良好。 如果intBox是一个参数化类型(整数),它的getter方法应该返回整数,并且从字符串到整数 的转换应该导致异常从getter方法本身内引发,并且不会发生。 – Michael1

+0

@ Michael1:它将始终由泛型方法的*调用方*来执行强制类型的强制转换。回想一下,没有为每个实例化类型生成一个单独的'.class'文件。换句话说,'Box.class'中没有地方可以将一个强制类型添加到'Integer'中,因为它需要适用于所有类型。它只强制执行参数的*擦除*(在这种情况下,擦除是'对象')。然后调用者被要求在必要时进行演员表演。 –

+0

从[JLS 15.5](https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.5):*有些情况下,静态已知类型可能不是在运行时准确。这种情况可能出现在导致编译时未经检查的警告的程序中。这些警告是针对不能被静态保证为安全的操作给出的,并且**由于涉及不可确定类型**而不能立即进行动态检查。因此,稍后在程序执行过程中动态检查可能会导致运行时类型错误。 –

0

这就是为什么编译器警告您不要使用非通用类引用。在运行时,它无法知道intbox是什么类型,因为它的泛型没有保存在字节码中。