Java 为什么可以修改最终对象?
我在正在编写的代码库中遇到了以下代码:Java 为什么可以修改最终对象?,java,final,Java,Final,我在正在编写的代码库中遇到了以下代码: public final class ConfigurationService { private static final ConfigurationService INSTANCE = new ConfigurationService(); private List providers; private ConfigurationService() { providers = new ArrayList();
public final class ConfigurationService {
private static final ConfigurationService INSTANCE = new ConfigurationService();
private List providers;
private ConfigurationService() {
providers = new ArrayList();
}
public static void addProvider(ConfigurationProvider provider) {
INSTANCE.providers.add(provider);
}
...
实例
声明为最终
。为什么可以将对象添加到实例
?这不应该使final的使用无效吗。(事实并非如此)
我假设答案与指针和内存有关,但我想确定。
final
只是使对象引用不可更改。通过这样做,它指向的对象不是不变的<代码>实例决不能引用另一个对象,但它引用的对象可能会更改状态。Final和immutable不是一回事。Final表示无法重新分配引用,因此您不能说
INSTANCE = ...
不可变意味着不能修改对象本身。这方面的一个例子是
java.lang.String
类。不能修改字符串的值。导致误解的关键在于问题的标题。最终的不是对象,而是变量。变量的值不能更改,但其中的数据可以更改
始终记住,当您声明引用类型变量时,该变量的值是一个引用,而不是一个对象。final只意味着引用无法更改。如果实例声明为final,则无法将其重新分配给另一个引用。对象的内部状态仍然是可变的
final ConfigurationService INSTANCE = new ConfigurationService();
ConfigurationService anotherInstance = new ConfigurationService();
INSTANCE = anotherInstance;
将抛出编译错误
一旦分配了final
变量,它总是包含相同的值。如果final
变量包含对对象的引用,则对象的状态可以通过对该对象的操作来更改,但该变量将始终引用同一对象。这也适用于数组,因为数组是对象;如果final
变量包含对数组的引用,则可以通过对数组的操作更改数组的组件,但该变量将始终引用同一数组
这里有一个指南。是最终的并不是不变的
final!=不可变的
final
关键字用于确保引用未被更改(即,它拥有的引用不能替换为新的引用)
但是,如果属性是self,并且是可修改的,则可以执行您刚才描述的操作
比如说
class SomeHighLevelClass {
public final MutableObject someFinalObject = new MutableObject();
}
如果我们实例化这个类,我们将无法为属性someFinalObject
分配其他值,因为它是final
所以这是不可能的:
....
SomeHighLevelClass someObject = new SomeHighLevelClass();
MutableObject impostor = new MutableObject();
someObject.someFinal = impostor; // not allowed because someFinal is .. well final
但如果对象本身是可变的,则如下所示:
class MutableObject {
private int n = 0;
public void incrementNumber() {
n++;
}
public String toString(){
return ""+n;
}
}
public final class ConfigurationService {
private static final ConfigurationService INSTANCE = new ConfigurationService();
private List providers;
private ConfigurationService() {
providers = new ArrayList();
}
// Avoid modifications
//public static void addProvider(ConfigurationProvider provider) {
// INSTANCE.providers.add(provider);
//}
// No mutators allowed anymore :)
....
然后,可以更改该可变对象包含的值
SomeHighLevelClass someObject = new SomeHighLevelClass();
someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();
someObject.someFinal.incrementNumber();
System.out.println( someObject.someFinal ); // prints 3
这与您的帖子具有相同的效果:
public static void addProvider(ConfigurationProvider provider) {
INSTANCE.providers.add(provider);
}
在这里,您不是在更改实例的值,而是在修改其内部状态(通过providers.add方法)
如果要防止类定义发生如下更改:
class MutableObject {
private int n = 0;
public void incrementNumber() {
n++;
}
public String toString(){
return ""+n;
}
}
public final class ConfigurationService {
private static final ConfigurationService INSTANCE = new ConfigurationService();
private List providers;
private ConfigurationService() {
providers = new ArrayList();
}
// Avoid modifications
//public static void addProvider(ConfigurationProvider provider) {
// INSTANCE.providers.add(provider);
//}
// No mutators allowed anymore :)
....
但是,这可能没有多大意义:)
顺便说一句,出于同样的原因,您也必须这样做。Java没有在语言中内置不变性的概念。没有办法将方法标记为变体。因此,该语言无法强制执行对象不变性。+1,有关更多详细信息,请查看,第4.5.4节。假设我反序列化了一个
ConfigurationService
对象,并尝试执行一个实例=deserializedConfigurationService
是不允许的?您永远不能将实例
分配给另一个对象。其他物体从哪里来并不重要。(注意,每个ClassLoader
都有一个实例
加载了这个类。理论上,你可以在一个JVM中多次加载这个类,每个JVM都是独立的。但这是一个不同的技术点。)@AkhilGite你对我答案的编辑弄错了;它实际上颠倒了句子的意思,这是正确的。引用是不可变的。对象保持可变。它不是“一成不变的”。@SeanOwen Sry对于我所做的编辑,你的陈述是完全正确的,谢谢。这种误解经常出现,不一定是一个问题。通常作为回答或注释。JLS的简单解释:“如果最终变量包含对对象的引用,则对象的状态可能会因对对象的操作而改变,但变量始终引用同一对象。”