23

Collections.unmodifiableList(...)返回静态内部类 的新实例UnmodifiableList。其他不可修改的集合类的构造方式相同。

如果这些课程是公开的,其中一个有两个优点:

  • 能够指示更具体的返回值(例如UnmodifiableList),因此 API 用户不会想到修改该集合;
  • 能够在运行时检查 aList是否为instanceof UnmodifiableList.

那么,不公开这些课程有什么好处吗?

编辑:没有提出绝对令人信服的论据,所以我选择了最受好评的答案。

4

7 回答 7

9

我个人完全同意你的看法。问题的核心在于 Java 的泛型不是协变的,这反过来又是因为 Java 的集合是可变的。

Java 的类型系统不可能编码一个看起来有mutators实际上是 immutable的类型。想象一下,如果我们要开始设计一些解决方案:

interface Immutable //marker for immutability

interface ImmutableMap<K, V> extends Map<K, V>, Immutable

但是 thenImmutableMap是 的子类Map,因此Map可以从ImmutableMap任何返回这种不可变 Map 的方法中赋值:

public ImmutableMap<K, V> foo();

可以分配给 Map,因此可以在编译时进行变异:

Map<K, V> m = foo();
m.put(k, v); //oh dear

因此,您可以看到添加这种类型实际上并没有阻止我们做任何坏事。我认为出于这个原因,做出了一个判断,它没有提供足够的东西。


scala这样的语言具有声明站点差异注释。也就是说,您可以将类型指定为协变(因此是不可变的),因为 Scala 的 Map 是(实际上它的V参数是协变的)。因此,您的 API 可以声明其返回类型是可变的还是不可变的。

另外,Scala 允许您声明交集类型,因此您甚至不需要将ImmutableXYZ接口创建为单独的实体,您可以指定一个方法来返回:

def foo : XYZ with Immutable

但是 scala 有一个适当的类型系统,而 Java 没有

于 2010-11-28T19:48:20.803 回答
5

我认为这两个优点都存在,但没有那么有用。主要问题保持不变: UnmodifiableList 仍然是一个 List,因此所有的 setter 都可用,并且底层集合仍然是可修改的。将类 UnmodifiableList 设为 public 会增加不可修改的错觉。

更好的方法是让编译器提供帮助,但为此集合类层次结构必须进行很多更改。例如,Scala 的集合 API 在这方面要先进得多。

一个缺点是在 API 中引入了至少三个额外的类/接口。由于它们没有那么有用,我认为将它们排除在 API 之外是一个不错的选择。

于 2010-11-28T17:58:54.320 回答
3

如果检查列表是否使用 Collections.unmodifiableList 创建对您很重要,那么您可以创建一个实例并请求该类。现在您可以将此类与任何列表的类进行比较。

private static Class UNMODIFIABLE_LIST_CLASS = 
    Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
    ...
} 
于 2010-11-28T19:29:13.580 回答
3

原因的答案很简单:在 1998 年的时候,高效的设计有点偏激。人们认为这显然不是优先事项。但是没有真正的,深刻的思考。

如果要使用这样的机制,请使用 Guava 的 ImmutableList/Set/Map/...

它们是明确的不可变的,使用该库时的一个好习惯不是返回一个列表,而是一个不可变的列表。所以你会知道 List/Set/Map/... 是不可变的。

例子:

private final ImmutableList constants = ...;
public final ImmutableList<String> getConstants() {
  return constants;
}

关于 UnmodifiableXxx 的设计本身,可以做到以下几点:

public static final class UnmodifiableXxx implements Xxx { // don't allow extend
  // static if inside Collections
  UnmodifiableXxx (Xxx backend) { // don't allow direct instanciation
    ...
  }
  ...
}
于 2011-01-03T10:14:05.953 回答
1

假设UnmodifiableList 一个公共课。我怀疑这会让程序员产生一种错误的安全感。记住,UnmodifiableList是一个可修改的视图。这意味着 an 的内容仍然可以通过对其底层. 一个天真的程序员可能不理解这种细微差别,并且可能期望 的实例是不可变的。 ListUnmodifiableListListUnmodifiableList

于 2010-11-28T12:22:58.300 回答
1
  • 能够指示更具体的返回值(例如UnmodifiableList),因此 API 用户不会想到修改该集合;

在适当的 API 中,这应该已经记录在返回不可修改列表的方法的 javadoc 中。

  • 能够在运行时检查 aList是否为instanceof UnmodifiableList.

这种需要表明实际问题出在其他地方。这是代码设计中的缺陷。问问自己,您是否曾经需要检查 aList是否是ArrayListor的实例LinkedList?无论是ArrayList还是LinkedList显然UnmodifiableList是在代码编写期间而不是在代码运行期间做出的决定。如果您因为尝试修改某个问题而遇到问题UnmodifiableList(API 开发人员可能有很好的理由应该已经记录在案),那么这是您自己的错,而不是运行时错误。

总而言之,这没有任何意义。和do 无论如何都不代表具体的Collections#unmodifiableXXX()实现synchronizedXXX()checkedXXX()无论底层具体实现如何,它们都只是可以应用的装饰器。

于 2010-11-28T19:57:32.697 回答
0

我认为答案是因为方法表单正确地知道所使用的泛型并且不需要额外的编程来传递这些信息,而类表单需要更多的混乱。for 的方法形式unmodifiableMap有两个浮动泛型参数,它映射到返回类型的泛型参数和传递参数的泛型参数。

public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
    return new UnmodifiableMap<K,V>(m);
}
于 2010-11-28T18:02:33.090 回答