如果从数组中复制最终变量,为啥 Java 需要对最终变量进行显式强制转换?
Posted
技术标签:
【中文标题】如果从数组中复制最终变量,为啥 Java 需要对最终变量进行显式强制转换?【英文标题】:Why does Java require an explicit cast on a final variable if it was copied from an array?如果从数组中复制最终变量,为什么 Java 需要对最终变量进行显式强制转换? 【发布时间】:2017-08-18 23:17:17 【问题描述】:从下面的代码开始...
byte foo = 1;
byte fooFoo = foo + foo;
当我尝试编译此代码时,我会收到以下错误...
错误:(5, 27) java: 不兼容的类型: 从 int 到 byte 的可能有损转换
...但是如果foo
是最终...
final byte foo = 1;
final byte fooFoo = foo + foo;
文件将编译成功。
继续下面的代码...
final byte[] fooArray = new byte[1];
fooArray[0] = 1;
final byte foo = fooArray[0];
fooArray[0] = 127;
System.out.println("foo is: " + foo);
... 将打印
foo is: 1
... 这很好。该值被复制到最终变量中,并且不能再更改。使用数组中的值不会改变 foo
的值(正如预期的那样......)。
为什么以下需要强制转换?
final byte[] fooArray = new byte[1];
fooArray[0] = 1;
final byte foo = fooArray[0];
final byte fooFoo = foo + foo;
这与该问题中的第二个示例有何不同?为什么编译器会出现以下错误?
错误:(5, 27) java: 不兼容的类型: 从 int 到 byte 的可能有损转换
怎么会这样?
【问题讨论】:
只是关于第二个示例的注释,您在其中从数组初始化foo
并测试它不会改变。即使foo
不是final
,它的值也不会改变。赋值(在这种情况下是初始化)在它发生的那一刻复制值(1),就是这样。 fooArray[0]
到 127 的后续更改将不会自动传播到 foo
,无论它是否是最终的。
【参考方案1】:
这是因为
byte foo = 1;
byte fooFoo = foo + foo;
foo + foo = 2 将被回答,但 2 不是字节类型,因为 java 具有整数变量的默认数据类型,它的类型是 int。所以你需要强行告诉编译器 该答案必须是明确的字节类型。
class Example
public static void main(String args[])
byte b1 = 10;
byte b2 = 20;
byte b1b2 = (byte)(b1 + b2);
//~ b1 += 100; // (+=) operator automaticaly type casting that means narrow conversion
int tot = b1 + b2;
//~ this bellow statement prints the type of the variable
System.out.println(((Object)(b1 + b2)).getClass()); //this solve your problem
【讨论】:
【参考方案2】:这确实是编译器在与final
一起使用时在常量折叠中所做的,正如我们从字节码中看到的那样:
byte f = 1;
// because compiler still use variable 'f', so `f + f` will
// be promoted to int, so we need cast
byte ff = (byte) (f + f);
final byte s = 3;
// here compiler will directly compute the result and it know
// 3 + 3 = 6 is a byte, so no need cast
byte ss = s + s;
//----------------------
L0
LINENUMBER 12 L0
ICONST_1 // set variable to 1
ISTORE 1 // store variable 'f'
L1
LINENUMBER 13 L1
ILOAD 1 // use variable 'f'
ILOAD 1
IADD
I2B
ISTORE 2 // store 'ff'
L2
LINENUMBER 14 L2
ICONST_3 // set variable to 3
ISTORE 3 // store 's'
L3
LINENUMBER 15 L3
BIPUSH 6 // compiler just compute the result '6' and set directly
ISTORE 4 // store 'ss'
如果你把最后一个字节改成 127,它也会报错:
final byte s = 127;
byte ss = s + s;
在这种情况下,编译器计算结果并知道它超出了限制,所以它仍然会抱怨它们不兼容。
更多:
而here 是另一个关于字符串常量折叠的问题:
【讨论】:
我只有手机可以接听,所以我跳过了字节码部分。很高兴你自愿这样做:-) 也展示了动机:当编译为字节码时,byte
变量被扩大到int
以便存储它们并用它们进行计算,除非右侧的操作数是final
常量,在这种情况下,计算可以在编译时静态发生。【参考方案3】:
值 1 非常适合一个字节; 1+1也是如此;当变量为final时,编译器可以执行constant folding。 (换句话说:编译器在执行 + 操作时不使用 foo
;而是使用“原始”1 值)
但是当变量不是最终变量时,所有关于转换和提升的有趣规则都会生效(请参阅here;您想阅读第 5.12 节关于扩大原始转换的内容)。
第二部分:将数组设为 final 仍然允许您更改它的任何字段;又是这样;不可能不断折叠;这样“加宽”操作又开始了。
【讨论】:
【参考方案4】:JLS (§5.2) 具有使用常量表达式进行赋值转换的特殊规则:
另外,如果表达式是
如果变量的类型是byte
、short
、char
或int
类型的常量表达式(§15.28):byte
、short
或char
,并且常量表达式的值可以用变量的类型表示,则可以使用缩小原语转换。
如果我们按照上面的链接,我们会在常量表达式的定义中看到这些:
原始类型的文字和String
类型的文字 加法运算符+
和-
引用常量变量 (§4.12.4) 的简单名称 (§6.5.6.1)。
如果我们点击上面的第二个链接,我们会看到
原始类型或
String
类型的变量,即final
并使用编译时常量表达式(§15.28) 进行初始化,称为常量变量。
因此,foo + foo
只能分配给fooFoo
,如果foo
是一个常量变量。将其应用于您的案例:
byte foo = 1;
没有定义一个常量变量,因为它不是final
。
final byte foo = 1;
确实定义了一个常量变量,因为它是final
,并使用常量表达式(原始文字)进行了初始化。
final byte foo = fooArray[0];
没有定义常量变量,因为它没有用常量表达式初始化。
注意fooFoo
本身是否是final
并不重要。
【讨论】:
但是最后一个问题如何:“这怎么会发生?” (其中 this = 从 int 到 byte 的可能有损转换) @KorayTugay 实际上,在您的示例中不会发生这种情况。但是编译器不知道;规范不允许它进行扣除。以上是关于如果从数组中复制最终变量,为啥 Java 需要对最终变量进行显式强制转换?的主要内容,如果未能解决你的问题,请参考以下文章
为啥java中的serialVersionUID必须是静态的、最终的和long类型的? [复制]
为啥从类中访问类变量需要“自我”。在 Python 中? [复制]