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

时间:2018-08-30 18:52:42

标签: groovy collections unmodifiable

假设我必须关注sed -i '/volume:/d' '' ./docker-compose.yaml

set

如果我要使用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) ,则在尝试使用Collections.unmodifiableSet()方法时会引发异常,但add()并非如此。为什么?

根据documentation,它应该引发错误:

  

返回指定集合的​​不可修改视图。这个方法   允许模块为用户提供对内部的“只读”访问权限   集合。查询操作对“返回的集合”的读取   到指定的集合,然后尝试修改   返回的集合,无论是直接的还是通过其迭代器的,都会导致   UnsupportedOperationException。

所有代码都是使用Groovy 2.5.2编写的

1 个答案:

答案 0 :(得分:1)

简短答案:可以向此集合添加Peach,因为Groovy确实将Collection的类型从动态转换为Set类型,因此fruitSet变量的类型不是{{ 1}},但Collections$UnmodifiableCollection

看看这个简单的示例类:

LinkedHashSet

在像Java这样的静态编译语言中,以下行将引发编译错误:

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<String> fruitSet = Collections.unmodifiableCollection(fruits) 强制转换为Collection(由于Set扩展了Set,它的作用方向相反)。现在,由于Groovy在设计上是一种动态语言,因此如果左侧的类型无法访问右侧返回的类型,它将尝试转换为左侧的类型。如果编译此代码,请执行一个Collection文件,然后对其进行反编译,您将看到类似以下的内容:

.class

有趣的一行如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class DynamicGroovyCastExample implements GroovyObject {
    public DynamicGroovyCastExample() {
        CallSite[] var1 = $getCallSiteArray();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        Set fruits = (Set)ScriptBytecodeAdapter.castToType(var1[0].callConstructor(HashSet.class), Set.class);
        var1[1].call(fruits, "Apple");
        var1[2].call(fruits, "Grapes");
        var1[3].call(fruits, "Orange");
        Set fruitSet = (Set)ScriptBytecodeAdapter.castToType(var1[4].call(Collections.class, fruits), Set.class);
        var1[5].callStatic(DynamicGroovyCastExample.class, fruitSet);
        var1[6].call(fruitSet, "Peach");
        var1[7].callStatic(DynamicGroovyCastExample.class, fruitSet);
    }
}

Groovy看到您已将Set fruitSet = (Set)ScriptBytecodeAdapter.castToType(var1[4].call(Collections.class, fruits), Set.class); 的类型指定为fruitSet,并且由于右侧表达式返回了Set<String>,因此它试图将其强制转换为所需的类型。现在,如果我们跟踪接下来会发生什么,我们将发现Collection转到:

ScriptBytecodeAdapter.castToType()
  

来源:src/main/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java#L253

这就是为什么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); } // ..... } fruitSet而不是LinkedHashSet的原因。

enter image description here

当然,它对于Collections$UnmodifableCollection来说也很好,因为在这种情况下不需要强制转换-Collections.unmodifiableSet(fruits)实现Collections$UnmodifiableSet,因此不涉及动态强制转换。

如何预防类似情况?

如果不需要任何Groovy动态功能,请使用静态编译来避免Groovy的动态性质出现问题。如果仅通过在类上添加Set注释来修改此示例,则该示例将无法编译,因此我们会提早发出警告:

enter image description here

第二,始终使用有效类型。如果该方法返回@CompileStatic,则将其分配给Collection。您可以在运行时中进行动态强制转换,但是您必须意识到它可能带来的后果。

希望有帮助。