Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/three.js/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么可以修改最终对象?_Java_Final - Fatal编程技术网

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的简单解释:“如果最终变量包含对对象的引用,则对象的状态可能会因对对象的操作而改变,但变量始终引用同一对象。”