【问题标题】: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,无论它是否是最终的。

标签: java arrays casting int byte


【解决方案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) 具有使用常量表达式进行赋值转换的特殊规则:

      另外,如果表达式是byteshortcharint类型的常量表达式(§15.28):

      • 如果变量的类型是byteshortchar,并且常量表达式的值可以用变量的类型表示,则可以使用缩小原语转换。

      如果我们按照上面的链接,我们会在常量表达式的定义中看到这些

      • 原始类型的文字和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 实际上,在您的示例中不会发生这种情况。但是编译器不知道;规范不允许它进行扣除。
      猜你喜欢
      • 2011-04-24
      • 2014-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多