字面值的算术运算是在编译时还是运行时计算的?

30

我有以下内容:

double timeInMinutes = (double) timeInMilliseconds / (1000 * 60);

操作 (1000 * 60) 是在编译时还是运行时完成的?换句话说,上面的代码片段和以下代码片段在运行时是否存在性能差异:

double timeInMinutes = (double) timeInMilliseconds / 60000;

编辑:我的问题与“Java编译器是否会预先计算文字的总和?”不同,因为我在算术运算中混合使用变量和字面值。这是一个小差别,但正如@TagirValeev在评论中指出的那样(文字上的算术运算在编译时还是运行时计算?),有些文字即使可以进行预编译,也有实例是没有被预编译的。


查找常量表达式。 - Sotirios Delimanolis
1
根据答案,相关链接:https://dev59.com/5UrYs4cB2Jgan1zngaoM - neverendingqs
这是编译器可以实现的最简单的优化之一。即使是旧的“未优化”的VB3-VB6也曾经这样做过(不确定早期版本是否也这样做)。 - RBarryYoung
2
可能是Java编译器会预先计算文字的总和吗?的重复问题。 - chue x
你误解了TagirVallev的观点:(double) time / 1000 / 60不是对字面量进行的操作,并且由于浮点数的原因,不能期望它会给出与(double) time / (1000 * 60)相同的结果。 - user1084944
3个回答

21
根据JLS §15.2 - 表达式形式,一些表达式的值可以在编译时确定。这些是常量表达式(§15.28)。
乘法运算符,如 *,/和%属于常量表达式,因此它将在编译时确定。
@SergeyMorozov比我更快地编写并获取了字节码证明(# 2 = Integer 60000 ),但这里是实际证明,上面是理论/官方声明:
尝试使用 1000 * 60 60000 在您的端口生成字节码,您将看到相同的字节码指令,因此将具有相同的运行时性能。 Java类:
public class Test {
    public static void main(String[] args) {
        int compileTest = 1000 * 60;
    }
}

字节码:
Classfile /E:/Test.class
  Last modified Oct 9, 2015; size 265 bytes
  MD5 checksum fd115be769ec6ef7995e4c84f7597d67
  Compiled from "Test.java"
public class Test
  SourceFile: "Test.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#13         //  java/lang/Object."<init>":()V
   #2 = Integer            60000
   #3 = Class              #14            //  Test
   #4 = Class              #15            //  java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               Test.java
  #13 = NameAndType        #5:#6          //  "<init>":()V
  #14 = Utf8               Test
  #15 = Utf8               java/lang/Object
{
  public Test();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // int 60000
         2: istore_1
         3: return
      LineNumberTable:
        line 3: 0
        line 4: 3
}

你的示例 int compileTest = 1000 * 60; 和问题有点不同;我在变量和字面量之间混合了算术运算。阅读所有答案,它可能不会改变结果,但只是想指出它略有不同。 - neverendingqs
@neverendingqs,我拿一个例子来回答你的问题“1000 * 60操作是在编译时还是运行时完成的?”..无论如何,关键是你可以生成字节码并在你的端口进行验证,除了理论上的部分.. - hagrawal7777
4
我认为OP的问题可以更好地表述为类似于“当常量表达式出现在较大的非常量表达式中时,它们仍然会被优化吗?” - user1084944
@Hurkyl 当我们回答他的问题时,他的问题很明确 -“1000 * 60操作是在编译时还是在运行时完成?”,这就是我们回答的内容。但是,正如你所说,它可能会变得更广泛,我认为OP已经编辑了他的问题。 - hagrawal7777
很高兴接受问题标题或正文的任何编辑。鉴于不同的因素,我不确定如何接受答案。 - neverendingqs

10

在编译时。这是一种最基本的编译器优化之一,称为常量折叠


8
值得注意的是,timeInMilliseconds / 1000 / 60将在运行时计算。 - Tagir Valeev
@TagirValeev 可能值得添加您自己的答案或编辑现有答案之一以指出这一点。 - neverendingqs
@TagirValeev决定尝试一下你说的,看起来它是在运行时计算的:http://pastebin.com/D2RF0ygF。不过`timeInMilliseconds / (1000 / 60)`还是可以的。 - neverendingqs

8
只需创建Test类。
public class Test {
    public static void main(String [] args) {
        long timeInMilliseconds = System.currentTimeMillis();
        double timeInMinutes = (double) timeInMilliseconds / (1000 * 60);
        System.out.println(timeInMinutes);
    }
}

您可以使用命令进行反编译:javap -v Test

您将看到反编译类的输出:

  public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
  stack=4, locals=5, args_size=1
     0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
     3: lstore_1
     4: lload_1
     5: l2d
     6: ldc2_w        #3                  // double 60000.0d
     9: ddiv
    10: dstore_3
    11: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
    14: dload_3
    15: invokevirtual #6                  // Method java/io/PrintStream.println:(D)V
    18: return
  LineNumberTable:
    line 3: 0
    line 4: 4
    line 5: 11
    line 6: 18

请看第6行:ldc2_w #3 // 表示双精度浮点数 60000.0d


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