Java集合API:为什么Unmodifiable [List | Set | Map]不是公开可见的类?

时间:2010-11-28 11:56:39

标签: java api collections unmodifiable

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

这些类是公开的,一个有两个优点:

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

那么,是否有任何优势使这些类公开?

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

7 个答案:

答案 0 :(得分:7)

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

Java的类型系统不可能编写一个似乎 mutators 实际上是不可变的的类型。想象一下,如果我们开始设计一些解决方案:

interface Immutable //marker for immutability

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

但是ImmutableMapMap的子类,因此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没有

答案 1 :(得分:4)

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

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

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

答案 2 :(得分:3)

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

private static Class UNMODIFIABLE_LIST_CLASS = 
    Collections.unmodifiableList( new ArrayList() ).getClass();
...
if( UNMODIFIABLE_LIST_CLASS == listToTest.getClass() ){
    ...
} 

答案 3 :(得分:3)

原因的答案非常简单:当时,在1998年,高效的设计有点侧面。人们想到这一点并不是一个优先事项。但是没有真正的,深刻的思考它。

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

它们是显式不可变的,使用该库时的一个好习惯不是返回List,而是返回一个ImmutableList。所以你会知道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
    ...
  }
  ...
}

答案 4 :(得分:1)

  
      
  • 能够指示更具体的返回值(例如UnmodifiableList),因此API用户不会想到修改该集合;
  •   

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

  
      
  • 在运行时检查Listinstanceof UnmodifiableList
  • 的能力   

这样的需求表明实际的问题存在于其他地方。这是代码设计中的一个缺陷。问问自己,您是否曾经需要检查ListArrayList还是LinkedList的实例?无论是ArrayListLinkedList还是UnmodifiableList,显然都是在代码写入时间内做出的决定,而不是在代码运行时。如果您遇到问题,因为您正在尝试修改UnmodifiableList(API开发人员可能已经非常好地证明了这些问题),那么它就是您自己的错误,而不是运行时故障。< / p>

总而言之,没有任何意义。 Collections#unmodifiableXXX()synchronizedXXX()checkedXXX()以任何方式执行都不代表具体实现。它们都只是decorators,无论底层的具体实现如何,都可以应用它们。

答案 5 :(得分:0)

假设UnmodifiableList 是公共类。我怀疑它会让程序员陷入虚假的安全感。请注意,UnmodifiableList可修改 List视图。这意味着UnmodifiableList的内容仍然可以通过对其基础List所做的任何更改来更改。一个天真的程序员可能无法理解这种细微差别,可能期望UnmodifiableList的实例是不可变的。

答案 6 :(得分:0)

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

public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
    return new UnmodifiableMap<K,V>(m);
}