【问题标题】:Why can final object be modified?为什么可以修改最终对象?
【发布时间】:2011-01-26 23:15:39
【问题描述】:

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

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可以添加对象?那不应该使最终的使用无效。 (它没有)。

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

【问题讨论】:

  • 这种误解经常出现,不一定是一个问题。通常作为答案或评论。
  • 来自 JLS 的简单解释:"如果最终变量持有对对象的引用,那么对象的状态可能会通过对对象的操作而改变,但变量将始终引用同一个对象。” JLS Documentation

标签: java final


【解决方案1】:

Java 没有内置于语言中的不变性概念。没有办法将方法标记为 mutator。因此语言无法强制对象不变性。

【讨论】:

    【解决方案2】:

    final 和 immutable 不是一回事。 final 意味着引用不能被重新分配,所以你不能说

    INSTANCE = ...
    

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • +1,更多详情请查看java.sun.com/docs/books/jls/second_edition/html/…,第 4.5.4 节。
      • 假设我反序列化了一个ConfigurationService 对象并尝试执行不允许的INSTANCE = deserializedConfigurationService
      • 您永远不能指定INSTANCE 来引用另一个对象。另一个对象来自哪里并不重要。 (注意,每个 ClassLoader 都有一个 INSTANCE 已加载此类。理论上,您可以在一个 JVM 中多次加载该类,并且每个都是独立的。但这是一个不同的技术点。)
      • @AkhilGite 你对我的回答的编辑弄错了;它实际上颠倒了句子的意思,这是正确的。引用是不可变的。对象保持可变。它不会“变得不可变”。
      • @SeanOwen 抱歉我所做的编辑,您的陈述完全正确,谢谢。
      【解决方案4】:

      最终不等于不可变。

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

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

      顺便说一句,你也必须synchronize access to it基本上是出于同样的原因。

      【讨论】:

        【解决方案5】:

        一旦分配了final 变量,它总是包含相同的值。如果final 变量持有对对象的引用,则对象的状态可能会通过对对象的操作而改变,但变量将始终引用同一个对象。这也适用于数组,因为数组是对象;如果final 变量持有对数组的引用,那么数组的组成部分可能会通过对数组的操作而改变,但变量将始终引用同一个数组。

        Source

        这是making an object immutable 的指南。

        【讨论】:

          【解决方案6】:

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

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

          会抛出编译错误

          【讨论】:

            【解决方案7】:

            误解的关键在于问题的标题。不是 object 是最终的,它是 variable。变量的值不能改变,但里面的数据可以。

            永远记住,当你声明一个引用类型变量时,该变量的值是一个引用,而不是一个对象。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-07-15
              • 2021-09-20
              • 2013-11-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多