如何使下面的 java class 不可变
How to make the below java class Immutable
除了以下内容之外,还有什么其他方法可以使下面的 class 不可变。
- 没有二传手
- 所有字段都是私有的和最终的
Class 被声明为 final,因此不能在 subclass
中覆盖方法
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;
}
}
需要在构造函数中复制listOfNumbers
,列表的getter需要return列表的副本。就目前而言,您正在 returning 一个可变列表,它违反了 class.
的不变性
或者,您可以使用不可变列表实现,例如来自 Guava.
的那个
您应该防御性地复制传递给构造函数的 listOfNumbers
和 return 它在 getter 中的不可变视图。
是的。您需要制作构造函数中提供的列表的防御副本和 getter 中的另一个列表。或者,使用 Google 番石榴的 ImmutableList
class 您可以将复制保存在 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.: 根据您可能想要强制执行的任何先决条件检查构造函数中的输入参数也是一种很好的做法。 (例如,列表不能为 null 也不能为空。)这些检查应该 always 在副本上完成,但 null 检查除外,这需要在创建之前进行副本。但这一点不是不变性,而是编写安全代码来维护其不变量,无论客户端试图破坏它们。
使用Collections.unmodifiableList
public List<Integer> getListOfNumbers() {
return Collections.unmodifiableList(listOfNumbers);
}
看看 Joshua Bloch 的书 Effective Java,特别是第 15 项。
他对如何使 class 不可变给出了惊人的解释。
由于其他人已经正确回答,您需要确保没有人可以修改 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.Whosebug.ImmutableClass to be IMMUTABLE
but: org.mutabilitydetector.Whosebug.ImmutableClass is actually NOT_IMMUTABLE
Reasons:
Attempts to wrap mutable collection type using a non-whitelisted unmodifiable wrapper method. [Field: listOfNumbers, Class: org.mutabilitydetector.Whosebug.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.Whosebug.Question_30240358.isImmutable(Question_30240358.java:14)
如果字段分配更改为:
,则此测试将通过
this.listOfNumbers = Collections.unmodifiableList(new ArrayList<Integer>(listOfNumbers));
该测试将捕获许多其他类型的引入可变性的问题。
除了以下内容之外,还有什么其他方法可以使下面的 class 不可变。
- 没有二传手
- 所有字段都是私有的和最终的
Class 被声明为 final,因此不能在 subclass
中覆盖方法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; } }
需要在构造函数中复制listOfNumbers
,列表的getter需要return列表的副本。就目前而言,您正在 returning 一个可变列表,它违反了 class.
或者,您可以使用不可变列表实现,例如来自 Guava.
的那个您应该防御性地复制传递给构造函数的 listOfNumbers
和 return 它在 getter 中的不可变视图。
是的。您需要制作构造函数中提供的列表的防御副本和 getter 中的另一个列表。或者,使用 Google 番石榴的 ImmutableList
class 您可以将复制保存在 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.: 根据您可能想要强制执行的任何先决条件检查构造函数中的输入参数也是一种很好的做法。 (例如,列表不能为 null 也不能为空。)这些检查应该 always 在副本上完成,但 null 检查除外,这需要在创建之前进行副本。但这一点不是不变性,而是编写安全代码来维护其不变量,无论客户端试图破坏它们。
使用Collections.unmodifiableList
public List<Integer> getListOfNumbers() {
return Collections.unmodifiableList(listOfNumbers);
}
看看 Joshua Bloch 的书 Effective Java,特别是第 15 项。
他对如何使 class 不可变给出了惊人的解释。
由于其他人已经正确回答,您需要确保没有人可以修改 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.Whosebug.ImmutableClass to be IMMUTABLE
but: org.mutabilitydetector.Whosebug.ImmutableClass is actually NOT_IMMUTABLE
Reasons:
Attempts to wrap mutable collection type using a non-whitelisted unmodifiable wrapper method. [Field: listOfNumbers, Class: org.mutabilitydetector.Whosebug.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.Whosebug.Question_30240358.isImmutable(Question_30240358.java:14)
如果字段分配更改为:
,则此测试将通过this.listOfNumbers = Collections.unmodifiableList(new ArrayList<Integer>(listOfNumbers));
该测试将捕获许多其他类型的引入可变性的问题。