简短的回答:添加Peach
到这个集合是可能的,因为 Groovy 会从类型动态Collection
转换Set
,所以fruitSet
变量不是类型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)
}
}
在像 Java 这样的静态编译语言中,以下行会引发编译错误:
Set<String> fruitSet = Collections.unmodifiableCollection(fruits)
这是因为Collection
不能转换为Set
(它以相反的方向工作,因为Set
extends Collection
)。现在,因为 Groovy 在设计上是一种动态语言,所以如果左侧的类型无法访问右侧返回的类型,它会尝试转换为左侧的类型。如果你编译这个代码做一个.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);
}
}
有趣的是以下一行:
Set fruitSet = (Set)ScriptBytecodeAdapter.castToType(var1[4].call(Collections.class, fruits), Set.class);
Groovy 看到您指定了fruitSet
as类型,Set<String>
并且因为右侧表达式返回 a Collection
,它会尝试将其强制转换为所需的类型。现在,如果我们跟踪接下来会发生什么,我们会发现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);
}
// .....
}
来源:src/main/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java#L253
这就是为什么fruitSet
是 aLinkedHashSet
而不是Collections$UnmodifableCollection
。
当然,它适用于Collections.unmodifiableSet(fruits)
,因为在这种情况下不需要强制转换 -Collections$UnmodifiableSet
实现Set
,因此不涉及动态强制转换。
如何预防类似情况?
如果您不需要任何 Groovy 动态特性,请使用静态编译来避免 Groovy 的动态特性出现问题。如果我们仅通过@CompileStatic
在类上添加注释来修改此示例,它将无法编译,并且我们会被提前警告:
其次,始终使用有效类型。如果方法返回Collection
,则将其分配给Collection
. 您可以在运行时使用动态转换,但您必须了解它可能产生的后果。
希望能帮助到你。