使用Class作为HashMap的关键是否会导致不良影响?

问题描述 投票:7回答:6

考虑以下:

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)会不会像myMap.put(fooObject.getClass(), fooObject)那样返回我的价值?考虑该软件具有各种类加载器和序列化机制。它仍然会是相同的结果吗?如果不是......会有什么选择?

java class hashmap hashcode
6个回答
3
投票

java.lang.Class是否可以安全地用作java.util.HashMap的键?

是。

myMap.get(Foo.class)是否总是返回我放置的值,如myMap.put(fooObject.getClass(),fooObject)?

是。

使用Class对象作为HashMap中的关键是安全的。 Class类继承了Object::equalsObject::hashCode方法。因此equals对象的Class正在测试对象身份。

这是Java中类型相等的正确语义。 ClassLoader::defineClass方法的实现确保您永远不会获得表示相同Java类型的两个不同的Class对象。

然而,有一个皱纹。 Java语言规范(JLS 4.3.4)声明:

在运行时,具有相同二进制名称的多个引用类型可以由不同的类加载器同时加载。这些类型可能代表也可能不代表相同的类型声明。即使两个这样的类型确实代表相同的类型声明,它们也被认为是不同的。

(二进制名称与命名类型的FQDN相关,并考虑了匿名类和数组类型。)

这意味着如果您(成功)在两个不同的类加载器中为具有相同完全限定名称的类调用ClassLoader::defineClass,您将获得不同的Java类型。无论您使用哪个字节码。此外,如果您尝试从一种类型转换为另一种类型,您将获得一个类转换异常。


现在问题是你的用例中有关系吗?

答:可能不是。

  • 除非您(或您的框架)使用类加载器进行棘手的操作,否则情况不会出现。
  • 如果是,那么您可能需要两种类型(具有相同的FQDN和不同的类加载器)才能在HashMap中具有不同的条目。 (因为类型不同!)
  • 但是,如果您需要两种类型具有相同的条目,那么您可以使用类的FQDN作为密钥,您可以使用Class::getCanonicalName获取该密钥。如果你需要处理数组类等,那么使用Class::getName返回类型的二进制名称。

序列化机制怎么样?

Class对象无法使用对象序列化进行序列化,因为Class不实现Serializable。如果您实现/使用支持Class对象序列化的其他一些序列化机制,那么该机制需要与JLS 4.3.4兼容。


7
投票

在我的头顶,是否有任何理由不使用字符串类名?例如。改为使用:

myMap.put("Foo", fooObject);

如果你是偏执狂,也许在范围内可能有多个Foo类,你可以使用完整的规范名称:

myMap.put(Foo.class.getCanonicalName(), fooObject);

4
投票

类的实例对于ClassLoader是唯一的,因此不需要覆盖hashCodeequals


1
投票

运行时和编译时类型之间存在差异。如果(并且仅当)它们由不同的类加载器加载,则可以同时加载同一个完全限定类名的多个类。然后这些类是不同的运行时类型,并且不能相互转换,即使它们是相同的。

因此,您的问题的答案仅取决于您认为合适的效果:

  • 如果您希望将这些单独加载和不兼容的类视为地图中的不同类,请使用Class作为键。永远不会有多个具有相同名称和加载器的Class实例,因此Class类正确地不会覆盖hashCodeequals方法。所以将它用作HashMap键是好的,尽管IdentityHashMap会给出相同的行为,可能更有效。
  • 如果您希望仅根据名称区分类,无论它们是如何(或是否)加载,请使用其字符串名称作为映射键。

0
投票

我会考虑使用IdentityHashMap。它不依赖于equals

此类仅用于需要引用相等语义的罕见情况。


0
投票

@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实例本身。

© www.soinside.com 2019 - 2024. All rights reserved.