【问题标题】:Why doesn't the Java library provide `HashSet.get(Object o)` and `HashMap.getKey(Object o)`为什么Java库不提供`HashSet.get(Object o)`和`HashMap.getKey(Object o)`
【发布时间】:2014-09-26 20:52:24
【问题描述】:

我知道这个问题已经在 SO 上被问过几次,但我仍然没有找到令人满意的解决方案,而且我不确定该走哪条路。问题是:

为什么 Java 库不提供 HashSet.get(Object o)HashMap.getKey(Object o) 方法来返回映射中的实际实例以提供相等的实例?示例:

// Retrieve a house with ID=10 that contains additional information like size,
// location and price.
houses.get(new House(10));

我认为最好的答案可以在here 找到。所以这是我知道的混合答案:

  • 既然已经有了实例,为什么还需要它?尝试获取您已经拥有的相同对象是没有意义的。该对象有一个标识符(控制它与其他 Foo 类型的相等性)以及任何数量的其他字段,这些字段不影响它的身份。我希望能够通过构造一个“相等”的 Foo 对象(文本取自其中一个 cmets)从 Set 中获取对象(其中包括额外的字段)。 -> 没有答案

  • 迭代Collection 并使用equals() 搜索实例。 这使用线性搜索,在大型集合中非常慢。 -> 不好的答案

  • 使用 HashMap 代替 HashSet 我不需要映射,而且我认为在 getHouses() 这样的方法中返回映射是不够的。 getter 应该返回 Set 而不是 Map

  • 使用TreeSet.ceiling - 不知道

  • 下面的这个 hacky 代码(仅限 Java 8 HashSet)使用反射并提供了缺失的功能。我在其他答案中没有找到类似的东西(不足为奇)。如果定义了目标 Java 版本并且未来的 Java 版本最终会提供这样的方法,这可能是一个可接受的解决方案,现在我们有接口的 default 方法。可以想到default E get(E o){stream().filter(e->e.equals(o)).findAny().orElse(null);}


// Alternative: Subclass HashSet/HashMap and provide a get()/getKey() methods
public static <T> T getFromSet(HashSet<T> set, T key) throws Exception {
    Field mapField = set.getClass().getDeclaredField("map");
    mapField.setAccessible(true);
    HashMap<T, Object> map = (HashMap) mapField.get(set);
    Method getNodeMethod = map.getClass().getDeclaredMethod("getNode",
            int.class, Object.class);
    getNodeMethod.setAccessible(true);

    return (T) ((Map.Entry) getNodeMethod.invoke(map, key.hashCode(),
            key)).getKey();
}

以下是问题:

  • 最好的解决方案是使用HashMap&lt;House, House&gt; 而不是HashSet&lt;House&gt;
  • 是否有其他库提供此功能并支持并发访问?
  • 您知道解决此功能的错误吗?

关于 SO 的类似问题:

【问题讨论】:

  • 恕我直言,在这个陈述中思考“对象有一个标识符(它控制它与其他 Foo 类型的相等性)加上任何数量的其他不影响它的身份的字段”——我想你这里真正需要的是一个以该标识符为键、对象为值的 Map。
  • 你有没有想过不是这个世界完全疯了,而是你的系统设计错了?如果两个实体相等,则意味着它们不仅可以互换,而且实际上彼此无法区分。这就是我们所说的两件事相等的意思。如果在您的设计中并非如此,则意味着您正在滥用equals() 方法。
  • @biziclop [equals() == true => 这些实例彼此之间没有区别] 这对于java.* 中的许多类来说并非如此。我宁愿说,如果两个对象引用同一个现实世界的实例,则它们是相等的。
  • @biziclop 这是一个例子:HttpCookie c1 = new HttpCookie("name", "value"); c1.setComment("c1"); HttpCookie c2 = new HttpCookie("name", "value"); c2.setComment("c2"); System.out.printf("c1.comment=%s, c2.comment=%s, c1.equals(c2)=%b\n", c1.getComment(), c2.getComment(), c1.equals(c2));
  • @biziclop 我正在使用 House 创建一个示例,该示例显示了从Set 检索元素的原理,仅此而已。我正在处理的数据完全不同,它们的标识和可选值都在 RFC 中定义。正如我所说:当它们引用同一个现实世界对象时,两个实例应该相等。 ...所以现在我很好奇:会在equals() 中包含评论吗?您自相矛盾:这 2 个 Cookie 应该被认为是平等的,但可以相互区分。

标签: java collections


【解决方案1】:

Map 没有getKey(Object o),因为它不是双向映射。它只将键映射到值,而不是相反。

Set 没有 get(Object o),因为这是 Map 的工作。

House 对象映射到另一个House 对象只是您的糟糕设计。您想通过地址或数字或类似的方式获取House,因此您有一个或多个地图可以为您提供这些映射(或者更有可能是一个数据库)。您的问题只对您有意义,因为您“以错误的方式”思考。

你的陈述证明了你的“错误的思维方式”

我不需要地图,而且我认为在 getHouses() 之类的方法。 getter 应该返回一个 Set 而不是 Map。

我从未听说过 getter 不能返回 Map。虽然我可能会将其命名为getHouseMap()。你从一个微不足道的小问题中制造了一个大问题。无论如何,这是数据库的工作,因此您的数据集必须非常小。

【讨论】:

    【解决方案2】:

    这种行为没有得到满足的原因是,创建一个带有无效数据的 House 实例只是为了获得一个带有有效数据的实例,这种设计真的很糟糕。

    组合是这里的正确解决方案:

    /** immutable class containing all the fields defining identity */
    public final class HouseIdentifier {
        private final String id;
    }
    
    public class House {
        private final HouseIdentifier id;
        /** all the mutable, ephemeral properties of the house should go here */
        private int size;
        private Person owner;
    }
    

    如果您像这样设计类层次结构,那么查找所需的只是简单明了的Map&lt;HouseIdentifier, House&gt;

    【讨论】:

    • 这样做的好处是,您可以明确说明您是在处理识别您的实例的东西,还是它是否是包含“所有数据”的“真实”实例。这是一个很好的优势。
    猜你喜欢
    • 2015-09-30
    • 1970-01-01
    • 2013-01-16
    • 2012-12-01
    • 2020-09-23
    • 1970-01-01
    • 1970-01-01
    • 2015-07-03
    • 1970-01-01
    相关资源
    最近更新 更多