为什么 Collections.unmodifiableCollection 允许您更改集合?
Why does Collections.unmodifiableCollection allow you to change the collection?
假设我必须遵循 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()
方法时它会抛出异常,但 Collections.unmodifiableCollection()
不是这种情况。为什么?
根据 documentation 它应该抛出一个错误:
Returns an unmodifiable view of the specified collection. This method
allows modules to provide users with "read-only" access to internal
collections. Query operations on the returned collection "read
through" to the specified collection, and attempts to modify the
returned collection, whether direct or via its iterator, result in an
UnsupportedOperationException.
所有代码都是使用Groovy2.5.2
编写的
简短回答:可以将 Peach
添加到此集合中,因为 Groovy 会从 Collection
动态转换为 Set
类型,因此 fruitSet
变量不是 Collections$UnmodifiableCollection
类型,而是 LinkedHashSet
.
类型
看看这个简单的示例 class:
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
扩展了 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
的类型指定为 Set<String>
并且因为右侧表达式 returns 和 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);
}
// .....
}
Source: src/main/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java#L253
这就是为什么 fruitSet
是 LinkedHashSet
而不是 Collections$UnmodifableCollection
。
当然它对 Collections.unmodifiableSet(fruits)
工作得很好,因为在这种情况下不需要转换 - Collections$UnmodifiableSet
实现 Set
所以不涉及动态转换。
如何防止类似情况发生?
如果您不需要任何 Groovy 动态特性,请使用静态编译来避免 Groovy 的动态特性出现问题。如果我们只是通过在 class 上添加 @CompileStatic
注释来修改此示例,它将无法编译,我们会提前警告:
其次,始终使用有效类型。如果方法returnsCollection
,赋值给Collection
。您可以在运行时使用动态转换,但您必须了解它可能产生的后果。
希望对您有所帮助。
假设我必须遵循 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()
方法时它会抛出异常,但 Collections.unmodifiableCollection()
不是这种情况。为什么?
根据 documentation 它应该抛出一个错误:
Returns an unmodifiable view of the specified collection. This method allows modules to provide users with "read-only" access to internal collections. Query operations on the returned collection "read through" to the specified collection, and attempts to modify the returned collection, whether direct or via its iterator, result in an UnsupportedOperationException.
所有代码都是使用Groovy2.5.2
编写的简短回答:可以将 Peach
添加到此集合中,因为 Groovy 会从 Collection
动态转换为 Set
类型,因此 fruitSet
变量不是 Collections$UnmodifiableCollection
类型,而是 LinkedHashSet
.
看看这个简单的示例 class:
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
扩展了 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
的类型指定为 Set<String>
并且因为右侧表达式 returns 和 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);
}
// .....
}
Source: src/main/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java#L253
这就是为什么 fruitSet
是 LinkedHashSet
而不是 Collections$UnmodifableCollection
。
当然它对 Collections.unmodifiableSet(fruits)
工作得很好,因为在这种情况下不需要转换 - Collections$UnmodifiableSet
实现 Set
所以不涉及动态转换。
如何防止类似情况发生?
如果您不需要任何 Groovy 动态特性,请使用静态编译来避免 Groovy 的动态特性出现问题。如果我们只是通过在 class 上添加 @CompileStatic
注释来修改此示例,它将无法编译,我们会提前警告:
其次,始终使用有效类型。如果方法returnsCollection
,赋值给Collection
。您可以在运行时使用动态转换,但您必须了解它可能产生的后果。
希望对您有所帮助。