6

阅读 JAVA 13 SE 规范,我在第 5 章第 5.1.7 节中找到。拳击转换有以下保证:

如果被装箱的值 p 是对 boolean、char、short、int 或 long 类型的常量表达式(第 15.28 节)求值的结果,并且结果为 true、false,则为 '\u0000' 到 ' 范围内的字符\u007f' 或 -128 到 127 范围内的整数(含),则令 a 和 b 为 p 的任意两次装箱转换的结果。总是 a == b

我觉得奇怪的是,字节类型的值从那个措辞中被忽略了。

例如,在如下代码中:

Byte b1=(byte)4;
Byte b2=(byte)4;
System.out.println(b1==b2);

我们有一个字节类型的常量表达式,在装箱之后,b1 和 b2 的值可能是也可能不是同一个对象。

没有演员表,它的工作方式实际上是一样的:

Byte b1=4;

在这里,我们在赋值上下文中有一个 int 类型的常量表达式。所以,根据规范

如果变量是 Byte、Short 或 Character 类型,并且常量表达式的值可以分别以 byte、short 或 char 类型表示,则可以使用紧缩原语转换后跟装箱转换。

所以表达式会被转换成byte,那个byte类型的值会被装箱,所以不能保证这个值是interned。

我的问题是我对规范的解释是正确的,还是我遗漏了什么?我查看了规范是否需要使用方法 Byte.valueOf() 进行装箱(可以保证),但事实并非如此。

4

2 回答 2

5

你理解正确。相同的 5.1.7 部分的结尾(来自https://docs.oracle.com/javase/specs/jls/se13/html/jls-5.html)说:

如果需要分配包装类(Boolean、 Byte 、Character、Short、Integer、Long、Float 或 Double)之一的新实例并且可用的存储空间不足,则装箱转换可能会导致 OutOfMemoryError 。

Byte如果预期它是预先生成的,则不会存在。

另一件事,仍然来自同一段:

理想情况下,装箱原始值将始终产生相同的引用。在实践中,使用现有的实现技术这可能是不可行的。上面的规则是一个务实的妥协,要求某些共同的价值观总是被装进无法区分的对象中。实现可能会懒惰地或急切地缓存这些。对于其他值,该规则不允许对程序员部分的装箱值的身份进行任何假设。这允许(但不要求)共享部分或全部这些引用。


不是“证明”,但也许值得一提:Integer描述拳击承诺,13甚至7

 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.

文本是相同的,即使实现随着时间的推移发生了变化。

Byte没有这样的声明,尽管它也被缓存了。7、13。_ _ 缓存在两者中都有,但没有一个关于它的词(也没有关于拳击)。

于 2020-01-01T19:22:28.790 回答
5

TL;DR 这已通过JDK 14修复,现在包括byte.

我认为这是一个规范错误,是多次重写的结果。

注意JLS 6 对应的文本:

如果被装箱的值ptrue是, false, a byte, achar在 \u0000 到 \u007f 的范围内,或者是介于 -128 和 127 之间的一个int或数,则令r1r2p 的任意两次装箱转换的结果。r1 == r2总是如此。short

在这里,byte明确提到无条件地将其装箱到具有规范身份的对象。由于所有字节都在 -127..128 范围内,因此不需要添加这样的限制。

但请注意,long尚未提及。

然后,遇到JDK-7190924, 5.1.7:JLS 没有提到自动装箱长的缓存

在评论中,您可以看到它是如何发生的。

在他的第一条评论中,亚历克斯巴克利批评“字节是一种类型,而不是一个值”,没有考虑到“字节”可能意味着“字节范围内的所有值”,但因为他还假设“数字”最初意味着“文字” (而不是,例如“数值”),他专注于所有整数文字都是 int 或 long 的点。

他的初稿使用术语“整数文字”并完全删除了类型。它的一个稍微修改的版本进入了Java 8 JLS

如果被装箱的值是介于和(第 3.10.1 节)之间p的整数文字,或布尔文字或(第 3.10.3 节),或介于和(第 3.10.4 节)之间的字符文字,那么让和是 的任意两次拳击转换的结果。总是如此。int-128127truefalse'\u0000''\u007f'abpa == b

所以在 Java 8 中,类型根本不重要,但保证仅限于文字。

所以这意味着

Byte b1 = 4;

由于整数文字,确实评估为规范对象,其中

Byte b1 = (byte)4;

可能不是,因为(byte)4是常量表达式,但不是文字。

多年后,在他的下一条评论中,他考虑了确实可以输入的“常量表达式”,并重新表述了该短语,带回了“布尔、字符、短、整数或长”类型,添加了长,但被遗忘了关于“字节”。

这个结果短语就是您引用的内容,它在 Java 9 以来的规范中。

省略byte肯定不是故意的,因为没有合理的理由省略它,尤其是当它以前存在时,所以从字面上看,这将是一个突破性的变化。

但是,将缓存限制为编译时常量,当 JLS 6 为范围内的所有值指定缓存时,没有这种限制,这已经是一个重大变化(这在实践中并不重要,只要它是通过实现valueOf的,无法知道该值是否源自编译时常量)。

作为旁注,文档Byte.valueOf(byte)明确说:

...所有字节值都被缓存

只要从 Java 7 开始

于 2020-01-08T00:32:23.543 回答