简体   繁体   English

为什么Java中的List.of()不返回类型化的不可变列表?

[英]Why does List.of() in Java not return a typed immutable list?

The list returned by the method List.of(E... elements) in java does return an immutable list, but this is not visible at all by looking at the created list. 由java中的方法List.of(E... elements)返回的列表确实返回一个不可变列表,但通过查看创建的列表,这根本不可见。 The created list simply throws an Exception instead of not showing the possiblity to change the list at all. 创建的列表只会抛出一个Exception,而不是根本不显示更改列表的可能性。 My point is, that List.of(E... elements) should return a ImmutableList that extends List . 我的观点是, List.of(E... elements)应该返回一个extends ListImmutableList This way the user can decide if he cares to show this fact of immutability or not. 这样,用户可以决定他是否愿意表现出这种不变性的事实。 But i don't find anybody complaining or showing alternative solutions. 但我没有发现任何人抱怨或显示替代解决方案。 Even Guava and Apache Commons don't do this by default. 即使是Guava和Apache Commons也不会默认执行此操作。 Only Guava gives the possibilty to create it (albeit with a lot of code): 只有Guava可以创建它(尽管有很多代码):

List<String> list = new ArrayList<String>(Arrays.asList("one", "two", "three"));
ImmutableList<String> unmodifiableList = ImmutableList.<String>builder().addAll(list).build();

But even this class has a (deprecated) add and remove method. 但即使是这个类也有一个(不推荐的) addremove方法。

Can anyone tell me why nobody cares about this (seemingly fundamental) issue? 任何人都可以告诉我为什么没有人关心这个(看似根本)的问题?

I would say that since commonly collections tend to (or at least should) be treated as "immutable by default" (meaning you're rarely modifying collections that you didn't create), it's not very important to specify that "this is immutable". 我会说,因为通常集合倾向于(或者至少应该)被视为“默认不可变”(意味着你很少修改你没有创建的集合),所以指定“这是不可变的”并不是非常重要”。 It would be more useful to specify "you can safely modify this collection if you wish". 如果你愿意,指定“你可以安全地修改这个集合”会更有用。

Secondly, your suggested approach wouldn't work. 其次,您建议的方法不起作用。 You can't extend List and hide methods, so the only option would be to make it return an ImmutableList that's not a subtype of List . 你不能扩展List和隐藏方法,所以唯一的选择是让它返回一个不是 List的子类型的ImmutableList That would make it useless, as it would require a new ImmutableList interface, and any existing code wouldn't be able to use it. 这将使它无用,因为它需要一个新的ImmutableList接口,并且任何现有的代码都无法使用它。

So is this optimal design? 那么这个优化设计呢? No, not really, but for backwards compatibility that's not going to change. 不,不是真的,但对于不会改变的向后兼容性。

Removing add , remove , etc. from all the Collection types and creating subinterfaces MutableCollection , MutableList , MutableSet would double the number of Collection interfaces, which is a complexity cost to be considered. 从所有Collection类型中删除addremove等,并创建子接口MutableCollectionMutableListMutableSet会使Collection接口的数量增加一倍,这是一个需要考虑的复杂性成本。 Furthermore, Collections aren't cleanly separated into Mutable and Immutable: Arrays.asList supports set , but not add . 此外,Collections没有完全分为Mutable和Immutable: Arrays.asList支持set ,但不支持add

Ultimately there's a tradeoff to be made about how much to capture in the type system and how much to enforce at runtime. 最终需要权衡在类型系统中捕获多少以及在运行时强制执行多少。 Reasonable people can disagree as to where to draw the line. 合理的人可能不同意在哪里划线。

It's not that nobody cares; 并不是没有人关心; it's that this is a problem of considerable subtlety. 这是一个相当微妙的问题。

The original reason there isn't a family of "immutable" collection interfaces is because of a concern about interface proliferation . 没有一系列“不可变”集合接口的原因是由于对接口扩散的担忧。 There could potentially be interfaces not only for immutability, but synchronized and runtime type-checked collections, and also collections that can have elements set but not added or removed (eg, Arrays.asList ) or collections from which elements can be removed but not added (eg, Map.keySet ). 可能存在接口,不仅可用于不可变性,还有同步和运行时类型检查的集合,以及可以具有元素集但未添加或删除的集合(例如, Arrays.asList )或可从中删除元素但不添加的集合(例如, Map.keySet )。

But it could also be argued that immutability is so important that it should be special-cased, and that there be support in the type hierarchy for it even if there isn't support for all those other characteristics. 但也可以说,不可变性非常重要,应该是特殊的,并且即使不支持所有其他特征,也可以在类型层次结构中得到支持。 Fair enough. 很公平。

The initial suggestion is to have an ImmutableList interface extend List , as 最初的建议是让一个ImmutableList接口扩展List ,如

ImmutableList <: List <: Collection ImmutableList <:List <:Collection

(Where <: means "is a subtype of".) (其中<:表示“是”的子类型。)

This can certainly be done, but then ImmutableList would inherit all of the methods from List , including all the mutator methods. 这当然可以完成,但是ImmutableList将继承List所有方法,包括所有mutator方法。 Something would have to be done with them; 他们必须做点什么; a sub-interface can't "disinherit" methods from a super-interface. 子接口不能从超级接口“删除”方法。 The best that could be done is to specify that these methods throw an exception, provide default implementations that do so, and perhaps mark the methods as deprecated so that programmers get a warning at compile time. 可以做的最好的事情是指定这些方法抛出异常,提供执行此操作的默认实现,并且可能将方法标记为已弃用,以便程序员在编译时收到警告。

This works, but it doesn't help much. 这有效,但它没有多大帮助。 An implementation of such an interface cannot be guaranteed to be immutable at all. 根本不能保证这种接口的实现是不可变的。 A malicious or buggy implementation could override the mutator methods, or it could simply add more methods that mutate the state. 恶意或错误的实现可以覆盖mutator方法,或者它可以简单地添加更多改变状态的方法。 Any programs that used ImmutableList couldn't make any assumptions that the list was, in fact, immutable. 任何使用ImmutableList程序都无法做出任何假设,事实上该列表是不可变的。

A variation on this is to make ImmutableList be a class instead of an interface , to define its mutator methods to throw exceptions, to make them final, and to provide no public constructors, in order to restrict implementations. 对此的一种变体是使ImmutableList成为一个而不是一个接口 ,定义其mutator方法以抛出异常,使它们成为最终,并且不提供公共构造函数,以限制实现。 In fact, this is exactly what Guava's ImmutableList has done. 事实上,这正是Guava的ImmutableList所做的。 If you trust the Guava developers (I think they're pretty reputable) then if you have a Guava ImmutableList instance, you're assured that it is in fact immutable. 如果您信任Guava开发人员(我认为他们非常有信誉),那么如果您有一个Guava ImmutableList实例,那么您可以确信它实际上是不可变的。 For example, you could store it in a field with the knowledge that it won't change out from under you unexpectedly. 例如,您可以将其存储在一个字段中,并且知道它不会意外地从您下面更改。 But this also means that you can't add another ImmutableList implementation, at least not without modifying Guava. 但这也意味着你不能添加另一个ImmutableList实现,至少在没有修改Guava的情况下。

A problem that isn't solved by this approach is the "scrubbing" of immutability by upcasting. 这种方法无法解决的问题是通过向上转换“清除”不变性。 A lot of existing APIs define methods with parameters of type Collection or Iterable . 许多现有API使用CollectionIterable类型的参数定义方法。 If you were to pass an ImmutableList to such a method, it would lose the type information indicating that the list is immutable. 如果您要将ImmutableList传递给这样的方法,它将丢失指示列表是不可变的类型信息。 To benefit from this, you'd have to add immutable-flavored overloads everywhere. 要从中受益,您必须在任何地方添加不可变的重载。 Or, you could add instanceof checks everywhere. 或者,您可以在任何地方添加instanceof检查。 Both are pretty messy. 两者都很混乱。

(Note that the JDK's List.copyOf sidesteps this problem. Even though there are no immutable types , it checks the implementation before making a copy, and avoids making copies unnecessarily. Thus, callers can use List.copyOf to make defensive copies with impunity.) (请注意,JDK的List.copyOf回避了这个问题。即使没有不可变类型 ,它也会在复制之前检查实现,并避免不必要地复制。因​​此,调用者可以使用List.copyOf制作防御性副本而不受惩罚。 )

As an alternative, one might argue that we don't want ImmutableList to be a sub-interface of List , we want it to be a super-interface: 作为替代方案,有人可能会争辩说我们不希望ImmutableList成为List的子接口,我们希望它是一个超级接口:

List <: ImmutableList List <:ImmutableList

This way, instead of ImmutableList having to specify that all those mutator methods throw exceptions, they wouldn't be present in the interface at all. 这样,代替ImmutableList必须指定所有这些mutator方法抛出异常,它们根本不会出现在接口中。 This is nice, except that this model is completely wrong. 这很好,除了这个模型是完全错误的。 Since ArrayList is a List , that means ArrayList is also an ImmutableList , which is clearly nonsensical. 由于ArrayList是一个List ,这意味着ArrayList也是一个ImmutableList ,这显然是荒谬的。 The problem is that "immutable" implies a restriction on subtypes, which can't be done in an inheritance hierarchy. 问题是“不可变”意味着对子类型的限制,这不能在继承层次结构中完成。 Instead, it would need to be renamed to allow capabilities to be added as one goes down the hierarchy, for example, 相反,它需要重命名以允许在层次结构中添加功能,例如,

List <: ReadableList List <:ReadableList

which is more accurate. 哪个更准确。 However, ReadableList is altogether a different thing from an ImmutableList . 但是, ReadableListImmutableList完全不同。

Finally, there are a bunch of semantic issues that we haven't considered. 最后,我们还没有考虑过一堆语义问题。 One concerns immutability vs. unmodifiability . 一个涉及不变性不可修改性 Java has APIs that support unmodifiability, for example: Java具有支持不可修改性的API,例如:

List<String> alist = new ArrayList<>(...);
??? ulist = Collections.unmodifiableList(alist);

What should the type of ulist be? ulist的类型应该是什么? It's not immutable, since it will change if somebody changes the backing list alist . 这不是一成不变的,因为如果有人修改后台列表就会改变alist Now consider: 现在考虑:

???<String[]> arlist = List.of(new String[] { ... }, new String[] { ... });

What should the type be? 该类型应该是什么? It's certainly not immutable, as it contains arrays, and arrays are always mutable. 它肯定不是不可变的,因为它包含数组,并且数组总是可变的。 Thus it's not at all clear that it would be reasonable to say that List.of returns something immutable. 因此,说List.of返回不可变的东西是不合理的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM