Java中使用"final"关键字声明类成员变量的作用

4

这更像是一个理论问题而不是解决方案问题,所以请听我说。

在C/C++和PHP中,你可以声明常量。通常有几种方法可以做到这一点(例如#DEFINE或'const type'...),其最终效果是在编译期间进行替换,使得所有这些命名常量变成文字。这有助于避免访问内存位置查找数据的需要,因为数据被硬编码了,但又不会出现硬编码的缺点——如果需要重新使用该值,则需要调用该值,并且需要在需要更改该值时更改所有实例。

但是Java的final声明略微晦涩;因为我可以创建一个具有未设置的final变量的类并在构造函数中初始化它们,这意味着就我所知道的,它们不会作为文字预编译。除了保证它们不能在构造后逻辑上更改之外,final声明是否提供任何效率上的好处?

参考文章是可以的,只要你注意解释final真正的功能以及除了防止构造后更改值之外的其他任何好处。

作为一个推论,是否可能以任何方式在Java中声明编译级别的常量,而不仅仅是使用文字(这本来就是一个坏主意?)


1
请参见:https://dev59.com/J2855IYBdhLWcg3wilCD - nolegs
4个回答

5

Java中确实有常量表达式。请参见此处的java语言规范。

A compile-time constant expression is an expression denoting a value of primitive type or a String that does not complete abruptly and is composed using only the following:

Literals of primitive type and literals of type String (§3.10.5)
Casts to primitive types and casts to type String
The unary operators +, -, ~, and ! (but not ++ or --)
The multiplicative operators *, /, and %
The additive operators + and -
The shift operators <<, >>, and >>>
The relational operators <, <=, >, and >= (but not instanceof)
The equality operators == and !=
The bitwise and logical operators &, ^, and |
The conditional-and operator && and the conditional-or operator ||
The ternary conditional operator ? :
Parenthesized expressions whose contained expression is a constant expression.
Simple names that refer to constant variables (§4.12.4).
Qualified names of the form TypeName . Identifier that refer to constant variables (§4.12.4).

但是在Java中,与C/C++不同的是,你还有一个JIT编译器,因此额外的内联是可能的。所以最重要的是,在看到真正的性能问题之前不要担心。


通过模拟/游戏,我必须从高效的方式开始,否则我将花费更多时间进行优化。在60fps下,效果可能是低效率的几何甚至指数倍。 - user1086498
1
使用Java中的JIT编译器,它确实是一些你无法通过猜测来确定的东西。你过早的优化可能会削弱JIT编译器并使结果变慢。不必等到最终产品完成,但你需要知道哪些函数需要执行多次,并在测试下运行它们并实际测量性能。@RiverC - Yishai
我认为测试中的性能测量有些无用,因为根据我的经验,某些方面最终在不同环境之间变化太大,而实际性能总是受到性能测试设备的影响而减慢。简而言之,它只适用于检查数量级 - 即哪些部分占用了总时间的大部分,而不是绝对指标,这些指标总是被未知数量所偏移。 - user1086498
长话短说,我希望实际的普遍常数尽可能地保持不变 - 因为最终我可能会不得不进行优化,为什么现在不这样做呢?我唯一能想到的理由是,正如你所说,我的过早优化会妨碍编译器本身的优化。如果编译器可以将静态final转换为实际的常量,那么它就是无关紧要的,我宁愿使用它们。 - user1086498
阅读更深入后,显然在有意义的范围内没有常量; 这取决于编译器。这令人失望。 - user1086498

4

Java有常量,Java编译器会在编译时将其替换成其值。例如,成员变量是finalString类型的有效常量,以这种方式被替换。(这是允许的,因为类String是不可变的)。其中一个后果是,如果你改变源代码中的字符串,但你没有重新编译使用这个字符串的类,那么这些类将看不到字符串的新值。

JLS 在下面的段落中解释了这一点:

4.12.4 final 变量

13.4.9 final 字段和常量


2

Final字段旨在使对象不可变

静态final字段是一种常量

编译器优化、数据流分析在某种程度上发生。如果你对此感兴趣,可以尝试使用javap命令查看JVM字节码。


我想做一些事情,这样我就不必依赖编译器优化,而是针对某些值立即执行最优操作。 - user1086498
“Final字段旨在创建不可变对象。” 从原则上讲没有问题,但我认为这种说法可能会误导人。仅仅因为一个字段是“final”的,并不能说明它的数据是不可变的。除非所引用的对象本身是不可变的,否则由“final”变量指示的引用类型仍然是可变的。 - scottb

1
最终声明对效率有任何好处吗?
实际上并没有。这是因为JIT通常可以确定该值在运行时不会更改,并将其视为final。(如果该值不是volatile且在另一个线程中更改,则会出现问题)
在Java 8中,您可以在闭包中使用局部变量,如果它们可以被定义为final,而不必将它们声明为final。

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