这个问题主要不是关于字符串。出于学术上的好奇心,我想知道变量上的final
修饰符如何改变程序的行为。以下示例显示了它是可能的。
这些行打印true
final String x = "x";
System.out.println(x + x == "xx");
但这些行打印false
String x = "x";
System.out.println(x + x == "xx");
除String
实习外,如果从变量声明中删除修饰符final
,还有其他任何可能导致程序行为发生变化的事情吗?我假设程序在有或没有修饰符的情况下编译。
请不要投票将其作为Comparing strings with == which are declared final in Java的副本关闭。我理解String
的例子。
我在问是否有任何其他原因删除final
修饰符可以有所作为。请有人链接到答案或回答问题。谢谢。
答案 0 :(得分:5)
final
修饰符仅确保变量为definitely assigned,并禁止对该变量进行任何重新分配。
可以观察到的唯一特殊情况是expressly stated in the JLS:
基本类型或类型String的变量,是最终的并使用编译时常量表达式(第15.28节)初始化,称为常量变量。
变量是否是常量变量可能对类初始化(第12.4.1节),二进制兼容性(第13.1节,第13.4.9节)和明确赋值(第16节)有影响。
有相当数量的JLS阅读,并且要涵盖主要观点:按JLS §13.4.9 ,您不会遇到的任何不良影响删除 final
修饰符。
然而, by JLS 17.5 ,如果您依赖线程的保证只看到它可以观察到的对象中明确赋值的变量,那么删除final
变量将导致这些变量不再对另一个线程可见。
因此,如果我们首先查看class initialization,如果字段是静态的并且不是常量变量,那么有关于类初始化的规则:
类或接口类型T将在第一次出现以下任何一个之前立即初始化:
- T是一个类,创建了一个T实例。
- T是一个类,调用T声明的静态方法。
- 分配由T声明的静态字段。
- 使用由T声明的静态字段,该字段不是常量变量(§4.12.4)。
在JLS §13.1中,明确指出将字段更改为final
可能会导致binary compatibility:
对作为常量变量的字段(第4.12.4节)的引用在编译时被解析为表示的常量值。二进制文件中的代码中不应存在对此类字段的引用(包含该字段的类或接口除外,该字段将具有初始化它的代码)。这样的字段必须总是看似已经初始化(§12.4.2);绝不能遵守此类字段类型的默认初始值。有关讨论,请参见§13.4.9。
从13.4.9开始:
如果未声明为final的字段更改为声明 最后,它可以破坏与预先存在的二进制文件的兼容性 尝试为该字段分配新值。
删除关键字final或更改字段所在的值 初始化不会破坏与现有二进制文件的兼容性。
如果字段是常量变量(§4.12.4),则删除关键字 最终或更改其值不会破坏兼容性 预先存在的二进制文件通过使它们不运行,但它们不会 除非它们是,否则请查看该字段用法的任何新值 重新编译。即使使用本身不是a,也是如此 编译时常量表达式(§15.28)。
此结果是决定支持条件的副作用 汇编,如§14.21结尾所述。
因此,仅从这一点开始,请注意突然将字段更改为final
。 删除字段安全。
...但这仅适用于单线程世界。来自JLS 17.5:
声明为final的字段初始化一次,但从未更改过 正常情况。最终字段的详细语义是 与正常领域有些不同。特别是, 编译器有很大的自由来移动最终字段的读取 跨同步障碍和调用任意或未知 方法。相应地,允许编译器保持a的值 最终字段缓存在寄存器中,而不是从内存中重新加载 必须重新加载非最终字段的情况。
final字段还允许程序员实现线程安全的不可变 没有同步的对象。线程安全的不可变对象是 即使使用数据竞争传递,所有线程都认为它是不可变的 对线程之间的不可变对象的引用。这可以提供 安全保证不通过不正确的或不正当的类别滥用 恶意代码。必须正确使用final字段才能提供 保证不变性。
当一个对象被认为是完全初始化时 构造函数完成。一个只能看到对一个引用的线程 保证该对象已完全初始化后的对象 查看该对象最终的正确初始化值 字段。
因此,如果您的程序依赖于上述保证以使其正常运行,那么删除final
关键字将对线程产生影响。