编译时常量和变量

问题描述 投票:32回答:7

Java语言文档说:

如果将基本类型或字符串定义为常量并且在编译时已知该值,则编译器会将代码中的常量名称替换为其值。这称为编译时常量。

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

private final int x = 10;

然后,编译器将用文字x替换代码中每次出现的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. 没有final是一个变量,不会被视为常数。

我的理解是否正确?

java performance constants final compile-time-constant
7个回答
48
投票

编译时间常数必须是:

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

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

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


2
投票

final关键字表示变量将初始化一次且仅一次。一个真正的常数也需要被宣布为static。因此,编译器都不会将您的示例视为常量。尽管如此,final关键字告诉您(以及编译器)您的变量将仅被初始化一次(在构造函数中或字面上)。如果需要在编译时分配它们的值,则字段必须是静态的。

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

private int y = 10; //这里你不是最终的

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

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


1
投票

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

至于第二个问题:y不是final,因此它不是编译时常量,因为它可能在运行时更改。


1
投票

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


1
投票

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

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

但是JLS将变量的一些其他要求强加为“常量变量”(除了最终):

  • 只是字符串或原始
  • 仅初始化内联,因为它是最终的,并且不允许空白最终
  • 用“常量表达式”=“编译时常量表达式”初始化(参见下面的JLS引用)

4.12.4. final Variables (JLS)

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

15.28. Constant Expressions

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

原始类型的文字和String类型的文字(§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节),其包含的表达式是一个常量表达式。

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

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


1
投票

JLSfinal变量和常量之间做出以下区分:

final variables

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

一旦分配了final变量,它总是包含相同的值。如果final变量保存对象的引用,则可以通过对象上的操作更改对象的状态,但该变量将始终引用同一对象。这也适用于数组,因为数组是对象;如果final变量持有对数组的引用,则数组的组件可能会被数组上的操作更改,但变量将始终引用相同的数组。

空白的final是一个final变量,其声明缺少初始化器。

constants

常量变量是原始类型或类型finalString变量,用常量表达式(§15.28)初始化。

根据这个定义,我们可以看出常量必须是:

  • 宣布final
  • 原始类型或类型String
  • 在其声明中初始化(不是空白的final
  • constant expression初始化

What about compile-time constants?

JLS不包含短语编译时常量。但是,程序员经常使用术语编译时常量和常量可互换。

如果final变量不符合上述标准被认为是常数,那么它在技术上应该被称为final变量。


-3
投票

每个原始Literal和String Literal都是一个编译时常量。

见:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.28

© www.soinside.com 2019 - 2024. All rights reserved.