52

Java 语言文档说:

如果原始类型或字符串被定义为常量并且值在编译时已知,则编译器将代码中任何地方的常量名称替换为其值。这称为编译时常量。

我的理解是如果我们有一段代码:

private final int x = 10;

然后,编译器会将x代码中出现的每一个 替换为 literal 10


但是假设常量在运行时被初始化:

private final int x = getX(); // here getX() returns an integer value at run-time.

与编译时常数相比,是否会有任何性能下降(无论它可以忽略不计)?


另一个问题是以下代码行是否:

private int y = 10; // here y is not final

被编译器以与编译时常量相同的方式处理?


最后,我从答案中了解到的是:

  1. final static表示编译时常数
  2. 只是final意味着它是一个常数,但在运行时初始化
  3. 只是static意味着在运行时初始化
  4. withoutfinal是一个变量,不会被视为常量。

我的理解正确吗?

4

8 回答 8

63

编译时间常数必须是:

  • 宣布最终
  • 原始或字符串
  • 在声明中初始化
  • 用常量表达式初始化

所以private final int x = getX();不是恒定的。

对于第二个问题private int y = 10;不是恒定的(在这种情况下是非最终的),因此优化器无法确定该值将来不会改变。所以它不能像常数值那样优化它。答案是:不,它与编译时间常数的处理方式不同。

于 2012-01-31T16:21:11.197 回答
6

JLSfinal变量和常量进行了以下区分:

final变量

可以声明一个变量final。一个final变量只能分配一次。如果一个final变量被赋值,这是一个编译时错误,除非它在赋值之前被确定为未赋值(§16(Definite Assignment))。

final分配变量后,它始终包含相同的值。如果final变量持有对对象的引用,则对象的状态可能会通过对对象的操作而改变,但变量将始终引用同一个对象。这也适用于数组,因为数组是对象;如果final变量持有对数组的引用,则数组的组成部分可能会通过对数组的操作而改变,但变量将始终引用同一个数组。

空白finalfinal其声明缺少初始化程序的变量。

常数

常量变量final原始类型或 String使用常量表达式(第 15.28 节)初始化的类型的变量。

从这个定义中,我们可以看出一个常数必须是:

编译时常量呢?

JLS包含短语compile-time constant。但是,程序员经常交替使用术语编译时常量常量

如果final变量不符合上述被视为常数的标准,则在技术上应将其称为final变量。

于 2018-04-19T23:51:20.467 回答
3

根据 JLS,没有要求“常量变量”应该是静态的。

所以“常量变量”可能是静态的或非静态的(实例变量)。

但是 JLS 对变量作为“常量变量”施加了一些其他要求(除了只是最终变量):

  • 只是字符串或原始的
  • 仅内联初始化,因为它是最终的,并且不允许空白最终
  • 用 "constant expression" = "compile-time constant expression" 初始化(见下面的 JLS 引用)

4.12.4。最终变量(JLS)

常量变量是使用常量表达式(第 15.28 节)初始化的原始类型或 String 类型的最终变量。

15.28。常量表达式

编译时常量表达式是表示原始类型值或字符串的表达式,它不会突然完成并且仅使用以下内容组成:

原始类型的文字和字符串类型的文字(§3.10.1、§3.10.2、§3.10.3、§3.10.4、§3.10.5)

转换为原始类型并转换为 String 类型(第 15.16 节)

一元运算符 +、-、~ 和 ! (但不是 ++ 或 --)(§15.15.3、§15.15.4、§15.15.5、§15.15.6)

乘法运算符 *、/ 和 %(第 15.17 节)

加法运算符 + 和 - (§15.18)

移位运算符 <<、>> 和 >>>(第 15.19 节)

关系运算符 <、<=、> 和 >=(但不是 instanceof)(第 15.20 节)

等式运算符 == 和 != (§15.21)

位和逻辑运算符 &、^ 和 | (§15.22)

条件与运算符 && 和条件或运算符 || (§15.23, §15.24)

三元条件运算符 ? : (§15.25)

带括号的表达式(第 15.8.5 节),其包含的表达式是常量表达式。

引用常量变量(第 4.12.4 节)的简单名称(第 6.5.6.1 节)。

TypeName 形式的限定名称(§6.5.6.2)。引用常量变量的标识符(第 4.12.4 节)。

于 2018-01-26T11:31:29.853 回答
1

某些机器上可能会有非常小的性能下降,因为这将涉及至少一个方法调用(除了这不是编译时常量这一事实),但正如你所说,它可以忽略不计,所以为什么要打扰?private final int x = getX();

至于第二个问题:y不是最终的,因此不是编译时间常数,因为它可能会在运行时发生变化。

于 2012-01-31T16:21:21.220 回答
1

final关键字意味着一个变量将被初始化一次且仅一次。还需要声明一个真正的常量static。因此,编译器不会将您的任何示例视为常量。然而,final 关键字告诉您(和编译器)您的变量将只初始化一次(在构造函数中或字面上)。如果您需要在编译时分配它们的值,您的字段必须是静态的。

性能并没有真正受到影响,但请记住,原始类型是不可变的,一旦您创建了一个,它将将该值保存在内存中,直到垃圾收集器将其删除。因此,如果您有一个变量y = 1;,然后将其更改为y = 2;在内存中,JVM 将同时具有这两个值,但您的变量将“指向”后者。

私人int y = 10;// 这里 y 不是最终的

被编译器以与编译时间常数相同的方式处理?

不,这是一个实例变量,在运行时创建、初始化和使用。

于 2012-01-31T17:02:57.643 回答
1

请记住,在以下代码中, x 不是编译时间常数:

public static void main(String[] args) {
     final int x;
     x= 5;
}
于 2020-07-08T10:43:36.783 回答
0

private final int x = getX(); 将在第一次声明您的对象时调用。性能“下降”将取决于getX()但这不是造成瓶颈的那种事情。

于 2012-01-31T16:21:59.713 回答
0

简单地说,编译器在编译时将引用替换为指定的实际值,而不是使用引用参数。

public static void main(String[] args) {
final int x = 5;
}

IE。在编译时,编译器直接使用初始化值 5 进行编译,而不是使用参考变量“x”;

请检查此说明

于 2020-01-07T09:52:04.267 回答