这真的比自动装箱要宽吗?
我在参考Java规范的缺点回答另一个问题时看到了这一点:
There are more shortcomings and this is a subtle topic. Check this out: public class methodOverloading{ public static void hello(Integer x){ System.out.println("Integer"); } public static void hello(long x){ System.out.println("long"); } public static void main(String[] args){ int i = 5; hello(i); } } Here "long" would be printed (haven't checked it myself), because the compiler chooses widening over auto-boxing. Be careful when using auto-boxing or don't use it at all!
我们确定这实际上是扩展而不是自动装箱的示例,还是完全是其他东西?
在我的初始扫描中,我同意这样的说法,即基于将" i"声明为原始而不是对象,输出将为" long"。但是,如果我们更改了
hello(long x)
到
hello(Long x)
输出将显示" Integer"
这到底是怎么回事?我对Java的编译器/字节码解释器一无所知...
解决方案
回答
是的,在测试中尝试一下。我们将看到" long"字样。之所以要扩展,是因为Java在选择将int自动装箱为Integer之前会选择将int扩展为很长的时间,因此选择了调用hello(long)方法。
编辑:被引用的原始帖子。
进一步的编辑:第二个选项将打印Integer的原因是因为没有"扩大"到更大的原语作为选项,因此必须将其装箱,因此Integer是唯一的选项。此外,java只会自动装箱到原始类型,因此,如果我们离开hello(Long)并删除了hello(Integer),它将给出编译器错误。
回答
在第一种情况下,我们正在发生扩大的转换。在已编译的类上运行" javap"实用程序(包含在JDK中)时,可以看到以下内容:
public static void main(java.lang.String[]); Code: 0: iconst_ 5 1: istore_ 1 2: iload_ 1 3: i2l 4: invokestatic #6; //Method hello:(J)V 7: return }
显然,我们会看到I2L,它是扩展的Integer-To-Long字节码指令的助记符。请参阅此处的参考。
在另一种情况下,将" long x"替换为对象" Long x"签名,我们将在main方法中获得以下代码:
public static void main(java.lang.String[]); Code: 0: iconst_ 5 1: istore_ 1 2: iload_ 1 3: invokestatic #6; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 6: invokestatic #7; //Method hello:(Ljava/lang/Integer;)V 9: return }
因此,我们会看到编译器创建了指令Integer.valueOf(int),以将原语放入包装器中。
回答
此示例的另一件有趣的事是方法重载。类型扩展和方法重载的结合只能起作用,因为编译器必须决定选择哪种方法。考虑以下示例:
public static void hello(Collection x){ System.out.println("Collection"); } public static void hello(List x){ System.out.println("List"); } public static void main(String[] args){ Collection col = new ArrayList(); hello(col); }
它不使用List的运行时类型,而是使用Collection的编译时类型,因此打印" Collection"。
我鼓励我们阅读"有效的Java",这使我对JLS的一些极端案例大开眼界。