【问题标题】:Java static final values replaced in code when compiling?编译时在代码中替换Java静态最终值?
【发布时间】:2011-03-02 20:58:44
【问题描述】:

在java中,说我有以下

==fileA.java==
class A
{  
    public static final int SIZE = 100;
}  

然后在另一个文件中我使用这个值

==fileB.java==  
import A;
class b
{
      Object[] temp = new Object[A.SIZE];
}

当它被编译时,SIZE 会被值 100 替换,所以如果我要替换 FileA.jar 而不是 FileB.jar,对象数组会得到新值还是被硬编码为100,因为这是最初构建时的值?

【问题讨论】:

  • 你的意思是new Object[A.SIZE];
  • 你应该在这里得到一个编译器错误。

标签: java static compilation final


【解决方案1】:

是的,Java 编译器确实将示例中的 SIZE 等静态常量值替换为其文字值。

因此,如果您稍后更改类A 中的SIZE 但不重新编译类b,您仍会在类b 中看到旧值。您可以轻松地对此进行测试:

文件 A.java

public class A {
    public static final int VALUE = 200;
}

文件 B.java

public class B {
    public static void main(String[] args) {
        System.out.println(A.VALUE);
    }
}

编译 A.java 和 B.java。现在运行:java B

更改 A.java 中的值。重新编译 A.java,而不是 B.java。再次运行,您将看到打印的旧值。

【讨论】:

  • 这就是我认为它会做的事情,但下面也有几个答案相反的说法.. 有人有任何文件支持这两个方向吗?
  • 有什么方法可以避免编译器这样做吗?
  • @jesper:我在这个问题上看到的最好的答案之一。帮了我很多!
【解决方案2】:

你可以通过这样做来防止常量被编译成B

class A
{  
    public static final int SIZE;

    static 
    {
        SIZE = 100;
    }
}  

【讨论】:

  • 谢谢:这在一个奇怪的场景中帮助了我:我正在使用 ikvm 将 java 编译为 .NET,并且我的静态最终字段在 C# 中呈现为“const”(表现出相同的内联行为) .我想让 ikvm 生成“静态只读”,它是恒定的,但不在依赖程序集中内联。我将您的解决方案应用于我的 java 代码,并且 ikvm 在 SIZE = 100; 中优雅地生成了 public static readonly;这在 C# 中没问题!
  • 嗨@MeBigFatGuy 你能解释一下当我们定义一个像 final static final String str = "some big string" 与 static { str = "some big string";为什么静态常量会被编译成引用它们的类?任何帮助都会很好
  • 当您定义一个公共静态最终字符串时,无论您在何处引用该字段,编译器都会注入一个 LDC 操作码,该操作码引用该类的常量池,其中发出 LDC。因此,如果您在一个类中定义一个字段,但在 100 个类中引用该变量,那么其他 100 个类中的每一个都将在每个类的常量池中获得该字符串的副本,而其他类将不会引用原始类获取字符串的值。如果您修改和编译原始类而不编译其他 100 个类,这可能会导致问题。
  • 如果有问题的字符串真的很大,你会有一堆非常大的类文件。任何类引用该字段都会很大。如果你真的在处理大量字符串,你可能应该将它们放在类路径上的属性文件中。
【解决方案3】:

哇——你每天都能学到新东西!

取自 Java 规范...

注意:如果是原始类型或字符串 被定义为一个常数和值 在编译时已知,编译器 到处替换常量名 在代码中及其值。这是 称为编译时常量。如果 外部常数的值 世界变化(例如,如果它是 立法规定 pi 实际上应该是 3.975),您将需要重新编译任何使用此常量的类以获得 当前值。

【讨论】:

  • 真的吗?替换 jar 意味着替换已编译的类。因此,该声明无法验证您的“未替换”声明。快速反编译类文件将揭示正确答案。
【解决方案4】:

另一种证明行为的方法是查看生成的字节码。当常数为“小”时(大概

public B();
  Code:
   0:   aload_0
   1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   bipush  42
   7:   anewarray       #3; //class java/lang/Object
   10:  putfield        #12; //Field temp:[Ljava/lang/Object;
   13:  return

}

(我使用 42 而不是 100,所以它更突出)。在这种情况下,它显然被替换为字节码。但是,假设常数“更大”。然后你会得到如下所示的字节码:

public B();
  Code:
   0:   aload_0
   1:   invokespecial   #10; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   ldc     #12; //int 86753098
   7:   anewarray       #3; //class java/lang/Object
   10:  putfield        #13; //Field temp:[Ljava/lang/Object;
   13:  return

当它更大时,使用操作码“ldc”,根据JVM documentation“一个无符号字节,必须是当前类的运行时常量池的有效索引”。

在任何一种情况下,常量都嵌入到 B 中。我想,由于在操作码中您只能访问当前类运行时常量池,因此将常量写入类文件的决定与实现无关(但我不知道事实)。

【讨论】:

  • 您可以使用 JDK 附带的 javap 工具从 *.class 文件中获取该字节码。
【解决方案5】:

这里的重要概念是 static final 字段使用编译时常量进行初始化,如 JLS 中所定义。使用非常量初始化器(或非static 或非final)并且不会被复制:

public static final int SIZE = null!=null?0: 100;

null 不是*编译时常量。)

【讨论】:

【解决方案6】:

其实我前段时间就遇到过这种怪事。

这将直接将“100”编译到类b中。如果只是重新编译 A 类,则不会更新 B 类中的值。

最重要的是,编译器可能不会注意到重新编译 b 类(当时我正在编译单个目录,而 B 类在单独的目录中,编译 a 的目录并没有触发 B 的编译)

【讨论】:

    【解决方案7】:

    作为优化,编译器将内联该 final 变量。

    所以在编译时它会是这样的。

    class b
    {
          Object[] temp = new Object[100];
    }
    

    【讨论】:

      【解决方案8】:

      需要注意的一点是:静态终值在编译时就知道了 如果在编译时该值未知,编译器不会将代码中各处的常量名称替换为其值。

        public class TestA {
            // public static final int value = 200;
            public static final int value = getValue();
            public static int getValue() {
              return 100;
            }
        }
      
      public class TestB {
          public static void main(String[] args) {
              System.out.println(TestA.value);
          }
      }
      

      首先编译TestA和TestB,运行TestB

      然后将TestA.getValue()改为返回200,编译TestA,运行TestB,TestB会得到新的值 enter image description here

      【讨论】:

        【解决方案9】:

        有一个例外:-

        如果静态最终字段在编译时为 null,则它不会被 null (实际上是它的值)替换

        A.java

        class A{
             public static final String constantString = null;
        }
        

        B.java

        class B{
             public static void main(String... aa){
                 System.out.println(A.constantString);
             }
        }
        

        编译 A.java 和 B.java 并运行 java B

        输出将为 null


        现在用以下代码更新 A.java 并只编译这个类。

        class A{
             public static final String constantString = "Omg! picking updated value without re-compilation";
        }
        

        现在运行 java B

        输出将是 天哪!无需重新编译即可选择更新的值

        【讨论】:

          【解决方案10】:

          Java 确实优化了这些类型的值,但前提是它们在同一个类中。在这种情况下,由于您正在考虑的用例,JVM 会查看 A.SIZE 而不是对其进行优化。

          【讨论】:

          • 什么样的用例会导致它在编译时替换而不是替换值?
          • 错了,如果是编译时常量,不管在哪个类中定义或使用,都会在编译时替换掉。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-01-05
          • 2012-05-16
          • 2011-06-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-19
          相关资源
          最近更新 更多