使用 Class 作为 HashMap 的键会导致不良影响吗?
Will the use of Class as key for a HashMap cause undesireable effects?
考虑以下几点:
Map<Class<?>, Object> myMap = new HashMap<Class<?>, Object>();
Foo fooObject = New Foo();
myMap.put(fooObject.getClass(), fooObject)
注意java.lang.Class本身并没有实现hashCode()方法,而是隐式继承自java.lang.Object。我在 JDK 1.8.
中验证了这一点
java.lang.Class
用作 java.util.HashMap
的密钥安全吗?
myMap.get(Foo.class)
总是 return 我输入的值 myMap.put(fooObject.getClass(), fooObject)
吗?考虑软件具有各种类加载器和序列化机制。还会是同样的结果吗?如果不是...还有什么替代方案?
在我的脑海中,是否有任何理由不使用字符串 class 名称?例如。而是使用:
myMap.put("Foo", fooObject);
如果您怀疑范围内可能有多个 Foo
class,您可以使用完整的规范名称:
myMap.put(Foo.class.getCanonicalName(), fooObject);
Class 的实例在每个 ClassLoader
中都是唯一的,因此不需要覆盖 hashCode
或 equals
。
@talex 我测试如下,你似乎是对的:
public class ClassUnique {
public static void main(String [] args) throws ClassNotFoundException {
Class<?> c1 = Class.forName("java.util.Date");
Class<?> c2 = Class.forName("java.util.Date");
System.out.println(c1.equals(c2));
}
}
输出为true
编辑:@Maarten 我认为你是对的。特别是如果您 运行 在 Websphere 或 Weblogic 等应用程序容器中,可能会有多个 class 加载程序在运行,这可能会搞砸。所以最后最简单的正确解决方案是只使用 Class 实例本身。
我会考虑使用 IdentityHashMap。它不依赖于 equals
.
This class is designed for use only in the rare cases wherein reference-equality semantics are required.
Is java.lang.Class safe to use as a key for a java.util.HashMap?
是的。
Will myMap.get(Foo.class) always return the values which I put like myMap.put(fooObject.getClass(), fooObject)?
是的。
使用 Class
对象作为 HashMap
中的键是安全的。 Class
class 继承了 Object::equals
和 Object::hashCode
方法。因此 equals
for Class
objects 正在测试对象身份。
这是 Java 中类型相等的正确语义。 ClassLoader::defineClass
方法的实现确保您永远不会获得表示相同 Java 类型的两个不同 Class
对象。
但是,有一个皱纹。 Java 语言规范 (JLS 4.3.4) 指出:
At run time, several reference types with the same binary name may be loaded simultaneously by different class loaders. These types may or may not represent the same type declaration. Even if two such types do represent the same type declaration, they are considered distinct.
(二进制名称与命名类型的 FQDN 相关,并考虑了匿名 classes 和数组类型。)
这意味着如果您(成功)在两个不同的 classloader 中为具有相同完全限定名称的 class 调用 ClassLoader::defineClass
,您将得到不同的 Java 类型。无论您使用何种字节码。此外,如果您尝试从一种类型转换为另一种类型,您将得到一个 class 转换异常。
现在的问题是这在您的用例中重要吗?
答案:可能不会。
除非您(或您的框架)使用 classloader 做棘手的事情,否则不会出现这种情况。
如果是,那么您可能需要这两种类型(具有相同的 FQDN 和不同的 classloader)在 HashMap
中具有不同的条目。 (因为类型不同!)
但是如果你需要两种类型有相同的条目,那么你可以使用class的FQDN作为密钥,你可以使用Class::getCanonicalName
获得.如果您需要处理数组 classes 等,则使用 Class::getName
其中 returns 类型的二进制名称。
序列化机制如何?
无法使用对象序列化对 Class
对象进行序列化,因为 Class
未实现 Serializable
。如果您实现/使用其他支持 Class
对象序列化的序列化机制,则该机制需要与 JLS 4.3.4 兼容。
运行-time 和 compile-time 类型之间存在差异。如果(且仅当)它们由不同的 class 加载器加载,则可以同时加载多个 class 相同的完全限定 class 名称。那么这样的 class 是不同的 运行 时间类型,并且 不能相互转换 ,即使它们是相同的。
因此,您问题的答案仅取决于您认为理想的效果:
如果您希望在地图中将那些单独加载和不兼容的 class 区分对待,请使用 Class
作为键。永远不会有超过一个具有相同名称和加载程序的 Class
实例,因此 Class
class 不会正确地覆盖 hashCode
和 equals
方法。所以可以将它用作 HashMap
键,尽管 IdentityHashMap
会给出相同的行为,可能更有效。
如果您希望仅根据名称区分 class ,而不管它们是如何(或是否)加载的,则使用它们的字符串名称作为映射键。
考虑以下几点:
Map<Class<?>, Object> myMap = new HashMap<Class<?>, Object>();
Foo fooObject = New Foo();
myMap.put(fooObject.getClass(), fooObject)
注意java.lang.Class本身并没有实现hashCode()方法,而是隐式继承自java.lang.Object。我在 JDK 1.8.
中验证了这一点java.lang.Class
用作 java.util.HashMap
的密钥安全吗?
myMap.get(Foo.class)
总是 return 我输入的值 myMap.put(fooObject.getClass(), fooObject)
吗?考虑软件具有各种类加载器和序列化机制。还会是同样的结果吗?如果不是...还有什么替代方案?
在我的脑海中,是否有任何理由不使用字符串 class 名称?例如。而是使用:
myMap.put("Foo", fooObject);
如果您怀疑范围内可能有多个 Foo
class,您可以使用完整的规范名称:
myMap.put(Foo.class.getCanonicalName(), fooObject);
Class 的实例在每个 ClassLoader
中都是唯一的,因此不需要覆盖 hashCode
或 equals
。
@talex 我测试如下,你似乎是对的:
public class ClassUnique {
public static void main(String [] args) throws ClassNotFoundException {
Class<?> c1 = Class.forName("java.util.Date");
Class<?> c2 = Class.forName("java.util.Date");
System.out.println(c1.equals(c2));
}
}
输出为true
编辑:@Maarten 我认为你是对的。特别是如果您 运行 在 Websphere 或 Weblogic 等应用程序容器中,可能会有多个 class 加载程序在运行,这可能会搞砸。所以最后最简单的正确解决方案是只使用 Class 实例本身。
我会考虑使用 IdentityHashMap。它不依赖于 equals
.
This class is designed for use only in the rare cases wherein reference-equality semantics are required.
Is java.lang.Class safe to use as a key for a java.util.HashMap?
是的。
Will myMap.get(Foo.class) always return the values which I put like myMap.put(fooObject.getClass(), fooObject)?
是的。
使用 Class
对象作为 HashMap
中的键是安全的。 Class
class 继承了 Object::equals
和 Object::hashCode
方法。因此 equals
for Class
objects 正在测试对象身份。
这是 Java 中类型相等的正确语义。 ClassLoader::defineClass
方法的实现确保您永远不会获得表示相同 Java 类型的两个不同 Class
对象。
但是,有一个皱纹。 Java 语言规范 (JLS 4.3.4) 指出:
At run time, several reference types with the same binary name may be loaded simultaneously by different class loaders. These types may or may not represent the same type declaration. Even if two such types do represent the same type declaration, they are considered distinct.
(二进制名称与命名类型的 FQDN 相关,并考虑了匿名 classes 和数组类型。)
这意味着如果您(成功)在两个不同的 classloader 中为具有相同完全限定名称的 class 调用 ClassLoader::defineClass
,您将得到不同的 Java 类型。无论您使用何种字节码。此外,如果您尝试从一种类型转换为另一种类型,您将得到一个 class 转换异常。
现在的问题是这在您的用例中重要吗?
答案:可能不会。
除非您(或您的框架)使用 classloader 做棘手的事情,否则不会出现这种情况。
如果是,那么您可能需要这两种类型(具有相同的 FQDN 和不同的 classloader)在
HashMap
中具有不同的条目。 (因为类型不同!)但是如果你需要两种类型有相同的条目,那么你可以使用class的FQDN作为密钥,你可以使用
Class::getCanonicalName
获得.如果您需要处理数组 classes 等,则使用Class::getName
其中 returns 类型的二进制名称。
序列化机制如何?
无法使用对象序列化对 Class
对象进行序列化,因为 Class
未实现 Serializable
。如果您实现/使用其他支持 Class
对象序列化的序列化机制,则该机制需要与 JLS 4.3.4 兼容。
运行-time 和 compile-time 类型之间存在差异。如果(且仅当)它们由不同的 class 加载器加载,则可以同时加载多个 class 相同的完全限定 class 名称。那么这样的 class 是不同的 运行 时间类型,并且 不能相互转换 ,即使它们是相同的。
因此,您问题的答案仅取决于您认为理想的效果:
如果您希望在地图中将那些单独加载和不兼容的 class 区分对待,请使用
Class
作为键。永远不会有超过一个具有相同名称和加载程序的Class
实例,因此Class
class 不会正确地覆盖hashCode
和equals
方法。所以可以将它用作HashMap
键,尽管IdentityHashMap
会给出相同的行为,可能更有效。如果您希望仅根据名称区分 class ,而不管它们是如何(或是否)加载的,则使用它们的字符串名称作为映射键。