自动装箱是否调用valueOf()方法?

52
我正在尝试确定以下陈述是否保证为真:
((Boolean)true) == Boolean.TRUE
((Boolean)true) == Boolean.valueOf(true)
((Integer)1) == Integer.valueOf(1)

我一直认为自动装箱等同于在相应类型上调用valueOf()。我看到的每一个讨论关于这个主题的都似乎支持我的假设。但是我在JLS中找到的只有以下内容(§5.1.7):

如果被包装的值p是int类型的整数字面量,介于-128和127之间(§3.10.1),或者是布尔字面量true或false(§3.10.3),或者是字符字面量,介于'\ u0000'和'\ u007f'之间(§3.10.4),则让a和b成为p的任意两个装箱转换的结果。始终有a == b。
这描述了类似于valueOf ()的行为。但似乎没有保证实际调用valueOf(),这意味着理论上可能存在一个为自动装箱值保留单独的专用缓存的实现。在这种情况下,缓存的自动装箱值和常规缓存的装箱值之间可能不存在标识相等性。

Oracle自动装箱教程中指出,li.add(i)会被编译成li.add(Integer.valueOf(i)),其中i是一个int类型。但我不确定该教程是否应被认为是权威来源。


*这个保证比valueOf()稍微弱一些,因为它只涉及字面值。


7
这不是408661的重复,我在我的问题中链接了它。我知道它通常编译为valueOf();我的问题是JLS是否在这方面做出了任何保证。 - shmosel
这种类型的问题很难(但并非不可能)给出一个明确的答案,因为它(严格来说)要求你从头到尾阅读整个JLS,并确保没有这样的保证。(我之前发布了一个类似的问题。)话虽如此,我搜索了整个JLS中的 valueOf ,没有任何与自动装箱相关的结果(只有关于 Enum.valueOf 等的内容)。在我看来,这就解决了问题。 - aioobe
1
这是一个想法:如果我编写了一个不使用valueOf的编译器,或者如果javac切换到另一种解决方案,那么任何新发出的字节码都将与旧字节码不兼容,因为旧字节码用于自动装箱的valueOf,而两个自动装箱的值必须(至少在某些情况下)是引用等效的。现在要将其与正式证明联系起来,就必须在JLS中找到某些声明分割编译的特定保证。虽然我怀疑JLS涵盖了这些主题。 - aioobe
1
如果两个实现都使用相同的缓存,那么它们是否实际上使用 valueOf() 对我来说并不重要,因为没有行为上的区别。 - shmosel
1
@Jean-FrançoisSavard,如果未来的假设功能“迫使我们实现比简单调用‘valueOf’更复杂的架构”,同时提供与调用valueOf的现有类兼容的值,该怎么办? 如果valueOf继续正常工作,我认为在编译好的类中调用它是没有理由不这样做的。任何更花哨的东西都可以被JVM注入,替换调用,在运行时执行。就像今天可能已经发生的一样。 - Holger
显示剩余9条评论
4个回答

30

我最初以为你的问题是 What code does the compiler generate for autoboxing? 的重复,但在你对 @ElliottFrisch 的评论之后,我意识到它是不同的:

我知道编译器会这样做。我正在努力弄清楚是否保证了这种行为。

对于其他读者,请假设“这样做”意味着使用 valueOf

请记住,Java有多个编译器。为了“合法”,它们必须遵循JLS中给出的合同。因此,只要所有这里的规则都得到遵守,就不能保证自动装箱是如何内部实现的。

但我没有看到任何不使用 valueOf 的理由,尤其是它使用缓存值并且是 Joseph D. Darcy 在这篇文章中推荐的方式。


只要编译器生成Java字节码,不使用valueOf将会破坏与其他使用valueOf的编译器生成的类的互操作性,特别是参考实现的整个JRE类,因为在某些情况下提供相同对象的保证必须跨类工作。这是在JLS和JVMS之间的巨大差距中被指定的事情之一,编译器必须遵守。 - Holger
@Holger 你能举个例子说明可能会受到影响的内容吗? - shmosel
2
@shmosel JLS 暗示当你有一个类 A,其中包含 static Integer foo() { return 42; },以及一个类 B,其中包含 static Integer bar() { return 42; } 时,A.foo() == B.bar(),并且它没有说“只有在使用相同的编译器时才是如此”。因此,当一个类使用 javac 编译,并使用 Integer.valueOf(int) 时,另一个类必须最终也到达由 Integer.valueOf(int) 返回的 Integer,无论用于 B 的编译器是哪个。它可以插入额外的缓存机制,但对象的来源必须相同,以确保保证引用相等性。 - Holger

20

在语言规范中未提及的情况下,不能保证自动装箱等效于调用静态valueOf方法。这是一种实现方面,不属于装箱转换规范的一部分。只要符合JLS中你提到的规定,理论上实现可以自由地使用另一种机制。

实际上,许多Sun JDK错误报告(例如JDK-4990346JDK-6628737)明确暗示,在Java 5中引入自动装箱时,意图是使编译器依赖valueOf,如JDK-6628737所述:

JDK 5 中引入了静态工厂方法 Integer.valueOf(int)、Long.valueOf(long) 等,用于 javac 实现自动装箱规范所需的缓存行为。

但这仅适用于 javac,不一定适用于所有编译器。


4

自动装箱 在 OpenJDK 中使用 valueOf() 来实现的...如果您使用的是这个实现,请继续阅读...否则,请跳到下面。

((Boolean)true) == Boolean.TRUE
((Boolean)true) == Boolean.valueOf(true)

Java文档说明Boolean.valueOf()始终返回Boolean.TRUEBoolean.FALSE,因此在这些情况下使用引用比较将会成功。
((Integer)1) == Integer.valueOf(1)

对于这个特定的例子,在默认设置下的OpenJDK实现中,它可能会工作,因为您选择了一个在启动时被缓存的小于128的值(尽管可以通过命令行参数覆盖)。如果它被频繁使用以被缓存,较大的值也可能起作用。除非您在整数缓存方面使用“安全”的假设,否则不要期望引用比较是相等的。
Long、Short、Character和Byte也偶然实现了这种缓存,但与Integer不同,它是不可调的。如果您比较autobox/valueOf()引用,则Byte始终有效,因为显然不能超出范围。Float和Double将始终创建新实例,这并不奇怪。
现在,以纯通用术语来说? 请参阅JLS的此部分 - 在-128到127范围内,必须为布尔值和任何int或char提供相等的引用。对于其他任何内容,没有任何保证。

-1
Oracle的自动装箱教程断言li.add(i)编译为li.add(Integer.valueOf(i)),其中i是int类型。但我不知道这个教程是否应该被视为权威来源。
我正在运行Oracle Java 1.7.0_72,看起来它确实使用了valueOf。以下是一些代码和它的字节码。字节码显示它正在使用valueOf。
public class AutoBoxing {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Integer x = 5;
        int i = x;
        System.out.println(x.toString());
    }

}





Compiled from "AutoBoxing.java"
public class testing.AutoBoxing {
  public testing.AutoBoxing();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5
       1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: astore_1
       5: aload_1
       6: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
       9: istore_2
      10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: aload_1
      14: invokevirtual #5                  // Method java/lang/Integer.toString:()Ljava/lang/String;
      17: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      20: return

但我不知道Open JDK使用的是什么。我会试一下。


3
这个答案如何回答这个问题? - Jean-François Savard
3
@Jean-FrançoisSavard,原始帖子中没有以问号结尾的句子。因此我猜最后一句话听起来像是一个问题。这篇帖子由几个断言和发现组成,并以“但我不知道这个教程是否应被认为是权威来源”结尾。所以对我来说,这看起来像是值得解决的问题。 - Jose Martinez
4
我不太明白。JLS没有提及具体实现方式,而Oracle Java提到了它是如何做到的。除了将这些事实重复回答给你之外,你究竟还在寻找什么?在我看来,你唯一可能要求的另一件事情是证明Oracle Java确实使用了valueOf()方法。 - Jose Martinez
1
@darijan 这样远远不够好。你的代码的正确性不应该依赖于其编译或执行环境,特别是在Java语言中。 - shmosel
1
是的,但它取决于JDK版本。这就是重点。您需要验证您感兴趣的任何JDK的实现。您特别提到了Oracle JDK(并没有提到特定的JDK版本)。您提到您不知道所阅读的文档是否是权威来源。您是否对特定的Oracle JDK版本或其他JDK(如Open JDK)感兴趣? - Jose Martinez
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接