Groovy 为什么Collections.unmodifiableCollection允许您更改集合?

Groovy 为什么Collections.unmodifiableCollection允许您更改集合?,groovy,collections,unmodifiable,Groovy,Collections,Unmodifiable,假设我必须按照设置: Set<String> fruits = new HashSet<String>() fruits.add("Apple") fruits.add("Grapes") fruits.add("Orange") Set<String> unmodifiableFruits = Collections.unmodifiableSet(new HashSet<String>(fruits)) unmodifiableFruits.

假设我必须按照
设置

Set<String> fruits = new HashSet<String>()
fruits.add("Apple")
fruits.add("Grapes")
fruits.add("Orange")

Set<String> unmodifiableFruits = Collections.unmodifiableSet(new HashSet<String>(fruits))
unmodifiableFruits.add("Peach") // -- Throws UnsupportedOperationException

Set<String> fruitSet = Collections.unmodifiableCollection(fruits)
fruitSet.add("Peach")
println(fruitSet)
Set fruits=new HashSet()
水果。添加(“苹果”)
水果。添加(“葡萄”)
水果。添加(“橙色”)
Set unmodifiableFruits=Collections.unmodifiableSet(新HashSet(fruits))
不可修改的水果。添加(“Peach”)/--抛出UnsupportedOperationException
集合水果集合=集合。不可修改的集合(水果)
水果集。添加(“桃子”)
println(果集)
如果我要使用
Collections.unmodifiableSet()
当我尝试使用
add()
方法时,它会抛出一个异常,但
Collections.unmodifiableCollection()的情况并非如此。为什么?

根据,它应该抛出一个错误:

返回指定集合的不可修改视图。这种方法 允许模块为用户提供对内部数据库的“只读”访问 收藏。对返回的集合“读取”的查询操作 到指定集合,并尝试修改 返回的集合,无论是直接的还是通过其迭代器,都会导致 不支持操作异常。


所有代码都是使用Groovy 2.5.2编写的。简短回答:向这个集合添加
Peach
是可能的,因为Groovy会将
collection
动态转换为
Set
类型,所以
foultset
变量不是
Collections$UnmodifiableCollection
类型,而是
LinkedHashSet

看看这个简单的示例类:

class DynamicGroovyCastExample {

  static void main(String[] args) {
    Set<String> fruits = new HashSet<String>()
    fruits.add("Apple")
    fruits.add("Grapes")
    fruits.add("Orange")

    Set<String> fruitSet = Collections.unmodifiableCollection(fruits)
    println(fruitSet)
    fruitSet.add("Peach")
    println(fruitSet)
  }
}
有趣的一行如下:

Set fruitSet = (Set)ScriptBytecodeAdapter.castToType(var1[4].call(Collections.class, fruits), Set.class);
Groovy发现您已经将
水果集
的类型指定为
集合
,并且因为右侧表达式返回一个
集合
,所以它尝试将其强制转换为所需的类型。现在,如果我们跟踪接下来发生的事情,我们将发现
ScriptBytecodeAdapter.castToType()
转到:

private static Object continueCastOnCollection(Object object, Class type) {
    int modifiers = type.getModifiers();
    Collection answer;
    if (object instanceof Collection && type.isAssignableFrom(LinkedHashSet.class) &&
            (type == LinkedHashSet.class || Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers))) {
        return new LinkedHashSet((Collection)object);
    }

// .....
}
资料来源:

这就是为什么
foultset
LinkedHashSet
而不是
Collections$unmodifabelecollection
的原因

当然,它对于
集合非常有效。unmodifiableSet(fruits)
,因为在这种情况下不需要强制转换-
Collections$unmodifiableSet
实现
Set
,因此不涉及动态强制转换

如何防止类似情况? 如果您不需要任何Groovy动态特性,请使用静态编译来避免Groovy的动态特性出现问题。如果我们仅通过在类上添加
@CompileStatic
注释来修改此示例,它将不会编译,并且我们将收到早期警告:

其次,始终使用有效类型。如果该方法返回
集合
,请将其分配给
集合
。您可以在运行时处理动态强制转换,但必须注意它可能带来的后果


希望能有所帮助。

确实如此。@sp00m-在
Groovy
中,它确实可以编译。我怀疑Groovy正在从
unmodifiableCollection
返回的类型(
java.util.Collections$unmodifiableCollection
就我的测试而言)隐式转换到
Set
。很可能它正在创建一个新的、可修改的集合(
java.util.LinkedHashSet
是在我的测试中分配给
水果集的类型)。@ernest_k-这有点误导。文档中明确说明返回指定集合的不可修改视图。事实似乎并非如此。@ernest_k-在这种情况下,
不可修改集
是更好的选择?因此,最好使用
集合。不可修改集
?既然我知道我的集合是一个集合,那么调用它是最安全的方法,对吗?@Sveta-correct。静态编译不允许您将
集合
分配给
集合
,因此尽可能明确始终是一个好的选择。通知文档的编写者
集合是否是一个好主意。不可修改的集合
页面无法准确描述该方法的功能?
Set
页面来自Groovy,但它链接到Java的
集合。对于任何读者来说,这可能会让人困惑。
collection.unmodifiableCollection()
的文档是可以的,它会返回所描述的内容。您面临的问题是在不兼容的类型之间转换Groovy“特性”。右侧表达式
Collections.unmodifiableCollection()
返回不可修改的集合,然后Groovy与左侧的类型进行比较,找到
Set
,因为
collection
无法强制转换为
Set
它应用了
ScriptBytecodeAdapter.castToType()
从给定的
集合创建
集合
。这只是由从类型
Collection
Set
的动态转换所支持的异常用法。问题是Groovy的
Set
文档链接到Java关于
集合的文档。unmodifiableCollection()
可能会让读者产生这样的想法
Collections.unmodifiableCollection()
在Groovy和Java中的函数是相同的,但正如您所演示的,它不是。在Java中,
Collections.unmodifiableCollection()
在尝试调用
add()
时抛出异常,就像文档中解释的那样。在Groovy中,它不会这样做,正如您所演示的,它是从给定集合创建的
Set
Set fruitSet = (Set)ScriptBytecodeAdapter.castToType(var1[4].call(Collections.class, fruits), Set.class);
private static Object continueCastOnCollection(Object object, Class type) {
    int modifiers = type.getModifiers();
    Collection answer;
    if (object instanceof Collection && type.isAssignableFrom(LinkedHashSet.class) &&
            (type == LinkedHashSet.class || Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers))) {
        return new LinkedHashSet((Collection)object);
    }

// .....
}