如何使下面的java类成为不可变的

时间:2015-05-14 14:48:38

标签: java immutability

除了以下内容之外,还有什么方法可以使下面的类不可变。

  • No Setters
  • 所有字段均为私有且最终
  • 将类声明为final,因此无法在子类

    中重写方法
    public final class ImmutableClass {
    
        private final String name;
    
        private final List<Integer> listOfNumbers;
    
        public ImmutableClass(String name, List<Integer> listOfNumbers) {
            this.name = name;
            this.listOfNumbers = listOfNumbers;
        }
    
        public String getName() {
            return name;
        }
    
        public List<Integer> getListOfNumbers() {
            return listOfNumbers;
        }
    }
    

6 个答案:

答案 0 :(得分:6)

你应该防御 - 复制传递给构造函数的listOfNumbers并在getter中返回它的不可变视图。

答案 1 :(得分:6)

是。您需要制作构造函数中提供的列表的防御副本以及getter中的另一个列表。或者,使用Google Guava的ImmutableList课程,您可以将复制保存在getter中。

public ImmutableClass(String name, List<Integer> listOfNumbers) {
    this.name = name;
    // you need to null check your parameter here first
    this.listOfNumbers = ImmutableList.copyOf(listOfNumbers);
}

这确保了getter返回的对象不会被客户端篡改,即使它与您在字段中存储的对象实例相同。

如果你想变得非常迂腐,你仍然可以用相对较少的开销来写这样的getter:

public List<Integer> getListOfNumbers() {
    return ImmutableList.copyOf(listOfNumbers);
}

作为ImmutableList.copyOf() will try to avoid making a copy when it's safe to do so,这实际上不会创建新副本,因此没有太多意义。

P.s。:根据您可能要强制执行的任何先决条件,检查构造函数中的输入参数也是一种好习惯。 (例如,列表不能为空且不能为空。)这些检查应该在副本上完成始终,除了空检查,这需要在创建副本之前进行。但问题的关键不在于不变性,而是编写保护其不变量的安全代码,但客户端试图破坏它们。

答案 2 :(得分:2)

listOfNumbers需要在构造函数中复制,列表的getter需要返回列表的副本。目前,你正在返回一个违反班级不变性的可变列表。

或者,您可以使用不可变列表实现,例如来自Guava的那个。

答案 3 :(得分:2)

使用Collections.unmodifiableList

public List<Integer> getListOfNumbers() {
    return Collections.unmodifiableList(listOfNumbers);
}

答案 4 :(得分:2)

看看Joshua Bloch的书Effective Java,特别是第15项。

他给出了关于如何使类不可变的惊人解释。

答案 5 :(得分:1)

正如其他人已正确回答,您需要确保没有人可以修改listOfNumbers字段。

但是,您可以使用我编写的名为Mutability Detector的自动化工具获得相同的答案,当您想要测试其他想要变为不可变的类时,它可能会派上用场。

鉴于您的ExampleClass以及以下单元测试:

import org.junit.Test;
import static org.mutabilitydetector.unittesting.MutabilityAssert.assertImmutable;

public class Question_30240358 {

    @Test
    public void isImmutable() {
        assertImmutable(ImmutableClass.class);
    }
}

结果是单元测试失败,并显示以下消息:

Expected: org.mutabilitydetector.stackoverflow.ImmutableClass to be IMMUTABLE
     but: org.mutabilitydetector.stackoverflow.ImmutableClass is actually NOT_IMMUTABLE
    Reasons:
        Attempts to wrap mutable collection type using a non-whitelisted unmodifiable wrapper method. [Field: listOfNumbers, Class: org.mutabilitydetector.stackoverflow.ImmutableClass]
    Allowed reasons:
        None.
        at org.mutabilitydetector.unittesting.internal.AssertionReporter.assertThat(AssertionReporter.java:48)
        at org.mutabilitydetector.unittesting.MutabilityAsserter.assertImmutable(MutabilityAsserter.java:108)
        at org.mutabilitydetector.unittesting.MutabilityAssert.assertImmutable(MutabilityAssert.java:672)
        at org.mutabilitydetector.stackoverflow.Question_30240358.isImmutable(Question_30240358.java:14)

如果字段分配更改为:

,则此测试将通过
this.listOfNumbers = Collections.unmodifiableList(new ArrayList<Integer>(listOfNumbers));

该测试将捕获许多其他引入可变性的问题。