为什么可以修改最终对象?


89

我在正在处理的代码库中遇到以下代码:

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);
    }

    ...

INSTANCE声明为final。为什么可以添加对象INSTANCE?那不应该使final的使用无效。(不是)。

我假设答案必须与指针和内存有关,但想确定。


这种误解经常出现,不一定是一个问题。常作为答案或评论。
罗宾

5
JLS的简单解释:“如果最终变量持有对对象的引用,则可以通过对该对象的操作来更改对象的状态,但该变量将始终引用同一对象。” JLS文档
realPK

Answers:


161

final只是使对象引用不可更改。通过这样做,它指向的对象是不可变的。INSTANCE永远不能引用另一个对象,但是它引用的对象可能会更改状态。


1
+1,有关更多详细信息,请检查java.sun.com/docs/books/jls/second_edition/html/…,第4.5.4节。
Abel Morelos

假设我反序列化了一个ConfigurationService对象,然后尝试执行INSTANCE =deserializedConfigurationService不允许吗?
diegoaguilar 2013年

您永远无法分配INSTANCE引用另一个对象。另一个对象来自哪里都没有关系。(注意,INSTANCE每个ClassLoader类都已加载了该类。理论上,您可以在一个JVM中多次加载该类,并且每个类都是单独的。但这是不同的技术要点。)
Sean Owen

@AkhilGite您对我的答案所做的编辑是错误的;它实际上颠倒了句子的意义,这是正确的。该引用是不可变的。该对象保持可变。它不是“成为不变的”。
肖恩·欧文

@SeanOwen对我所做的编辑表示抱歉,您的发言完全正确,谢谢。
AkhilGite

33

最终与不变是不同的。

final != immutable

所述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;
     }
}  

然后,可以更改该可变对象包含的值。

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);
}

在这里,您没有更改INSTANCE的值,而是在修改其内部状态(通过providers.add方法)

如果要防止应该像这样更改类定义:

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 :) 
....

但是,这可能没有多大意义:)

顺便说一下,出于相同的原因,您还必须基本上同步对其的访问


26

误解的关键在于您问题的标题。不是最终的对象,而是变量。变量的值不能更改,但是其中的数据可以更改。

始终记住,在声明引用类型变量时,该变量的值是引用,而不是对象。


11

final只是表示引用不能更改。如果INSTANCE被声明为final,则不能将其重新分配给另一个引用。对象的内部状态仍然可变。

final ConfigurationService INSTANCE = new ConfigurationService();
ConfigurationService anotherInstance = new ConfigurationService();
INSTANCE = anotherInstance;

会引发编译错误


7

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

资源

这是使对象不可变的指南。


4

最终和不可变不是同一回事。Final表示无法重新分配参考,因此您不能说

INSTANCE = ...

不可变意味着对象本身无法修改。类的一个例子java.lang.String。您不能修改字符串的值。


2

Java没有在语言中内置不变性的概念。无法将方法标记为变异器。因此,该语言无法强制实现对象不变性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.