Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/382.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么不允许外部接口为HashMap提供hashCode/equals?_Java_Collections_Hashmap_Trove4j - Fatal编程技术网

Java 为什么不允许外部接口为HashMap提供hashCode/equals?

Java 为什么不允许外部接口为HashMap提供hashCode/equals?,java,collections,hashmap,trove4j,Java,Collections,Hashmap,Trove4j,对于树映射来说,提供自定义的比较器,从而覆盖添加到映射中的可比对象提供的语义是很简单的HashMaps但是不能以这种方式进行控制;提供哈希值和相等性检查的函数不能“侧加载” 我怀疑设计一个接口并将其改装成HashMap(或一个新类)既简单又有用?类似这样的东西,除了更好的名字: interface Hasharator<T> { int alternativeHashCode(T t); boolean alternativeEquals(T t1, T t2);

对于
树映射
来说,提供自定义的
比较器
,从而覆盖添加到映射中的
可比
对象提供的语义是很简单的
HashMap
s但是不能以这种方式进行控制;提供哈希值和相等性检查的函数不能“侧加载”

我怀疑设计一个接口并将其改装成
HashMap
(或一个新类)既简单又有用?类似这样的东西,除了更好的名字:

  interface Hasharator<T> {
    int alternativeHashCode(T t);
    boolean alternativeEquals(T t1, T t2);
  }

  class HasharatorMap<K, V> {
    HasharatorMap(Hasharator<? super K> hasharator) { ... }
  }

  class HasharatorSet<T> {
    HasharatorSet(Hasharator<? super T> hasharator) { ... }
  }
这是可行的吗,或者你能看到这种方法的根本问题吗

该方法是否用于任何现有(非JRE)LIB?(试过谷歌,运气不好。)

编辑:hazzen提出了很好的解决方案,但恐怕这是我试图避免的解决方案……;)

编辑:更改标题,不再提及“比较器”;我怀疑这有点让人困惑

编辑:与绩效相关的已接受答案;我想要一个更具体的答案

编辑:有一个实现;请参阅下面的公认答案


编辑:重新措辞第一句,以更清楚地表明我所追求的是侧加载(而不是排序;排序不属于HashMap)。

注意:如所有其他答案所述,HashMap没有明确的排序。他们只承认“平等”。从基于散列的数据结构中获得一个顺序是毫无意义的,因为每个对象都变成了一个散列——本质上是一个随机数

您可以随时为一个类编写一个哈希函数(通常情况下是必须的),只要您仔细地编写即可。这是一件很难正确完成的事情,因为基于散列的数据结构依赖于散列值的随机、均匀分布。在有效的Java中,有大量的文本用于正确实现具有良好行为的哈希方法

综上所述,如果您只想让哈希忽略
字符串的大小写,您可以为此在
字符串
周围编写一个包装类,并将其插入数据结构中

一个简单的实现:

public class LowerStringWrapper {
    public LowerStringWrapper(String s) {
        this.s = s;
        this.lowerString = s.toLowerString();
    }

    // getter methods omitted

    // Rely on the hashing of String, as we know it to be good.
    public int hashCode() { return lowerString.hashCode(); }

    // We overrode hashCode, so we MUST also override equals. It is required
    // that if a.equals(b), then a.hashCode() == b.hashCode(), so we must
    // restore that invariant.
    public boolean equals(Object obj) {
        if (obj instanceof LowerStringWrapper) {
            return lowerString.equals(((LowerStringWrapper)obj).lowerString;
        } else {
            return lowerString.equals(obj);
        }
    }

    private String s;
    private String lowerString;
}

.NET通过IEqualityComparer(对于可以比较两个对象的类型)和IEquatable(对于可以将自身与另一个实例进行比较的类型)实现了这一点

事实上,我认为在java.lang.Object或System.Object中定义等式和哈希代码是一个错误。特别是平等,很难用继承的方式来定义。我一直想把这件事写在博客上


但是是的,基本上这个想法是正确的。

问乔什·布洛赫这个问题问得好。我在Java7中以RFE的形式提交了这个概念,但它被放弃了,我相信原因是与性能相关的。不过,我同意应该这样做。

我怀疑这样做是因为它会阻止哈希代码缓存

我尝试创建一个通用的映射解决方案,其中所有键都以静默方式包装。事实证明,包装器必须保存包装的对象、缓存的哈希代码和对负责相等性检查的回调接口的引用。这显然不如使用包装类有效,在包装类中,您只需缓存原始密钥和一个以上的对象(请参见Hazzen的答案)


(我还遇到了一个与泛型相关的问题;get方法接受对象作为输入,因此负责哈希的回调接口必须执行额外的instanceof check。或者,map类必须知道其键的类。)

这是一个有趣的想法,但它的表现绝对可怕。这样做的原因是非常基本的:不能依赖顺序。哈希表非常快(),因为它们索引表中元素的方式是:通过计算该元素的伪唯一整数哈希并访问数组中的该位置。它实际上是计算内存中的一个位置并直接存储元素

这与平衡的二叉搜索树(
TreeMap
)形成对比,它必须从根开始,每次需要查找时都向下搜索到所需的节点。维基百科有一些。总而言之,树映射的效率取决于一致的顺序,因此元素的顺序是可预测和合理的。然而,由于“遍历到目的地”方法对性能的影响,BST只能提供O(log(n))性能。对于大型地图,这可能会对性能造成重大影响

可以对哈希表施加一致的排序,但要做到这一点,需要使用类似于
LinkedHashMap
的技术并手动维护排序。或者,可以在内部维护两个独立的数据结构:哈希表和树。表可用于查找,而树可用于迭代。当然,问题是这会占用两倍以上的内存。而且,插入的速度只有树的速度:O(log(n))。并发技巧可以降低这一点,但这不是可靠的性能优化

简而言之,您的想法听起来非常好,但如果您真的尝试实现它,您会发现这样做会带来巨大的性能限制。最后的结论是(几十年来一直如此):如果您需要性能,请使用哈希表;如果您需要排序并且可以忍受性能下降,请使用平衡的二进制搜索树。我担心,要有效地结合这两种结构,就必须保证其中一种结构的安全性。

有我想要的功能,他们称之为哈希策略


他们的映射具有不同的限制和不同的先决条件,因此这并不意味着Java的“本机”HashMap的实现是可行的。

com.google.common.coll中有这样一个功能
public class LowerStringWrapper {
    public LowerStringWrapper(String s) {
        this.s = s;
        this.lowerString = s.toLowerString();
    }

    // getter methods omitted

    // Rely on the hashing of String, as we know it to be good.
    public int hashCode() { return lowerString.hashCode(); }

    // We overrode hashCode, so we MUST also override equals. It is required
    // that if a.equals(b), then a.hashCode() == b.hashCode(), so we must
    // restore that invariant.
    public boolean equals(Object obj) {
        if (obj instanceof LowerStringWrapper) {
            return lowerString.equals(((LowerStringWrapper)obj).lowerString;
        } else {
            return lowerString.equals(obj);
        }
    }

    private String s;
    private String lowerString;
}
protected int hash(Object key) { ... }
protected boolean isEqualKey(Object key1, Object key2) { ... }
protected boolean isEqualValue(Object value1, Object value2) { ... }
protected HashEntry createEntry(
    HashEntry next, int hashCode, Object key, Object value) { ... }
public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}
public class Data
{
    private final int id;

    public Data(int id)
    {
        this.id = id;
    }

    public int getId()
    {
        return id;
    }

    // No equals or hashcode
}
java.util.Set<Data> set =
  new UnifiedSetWithHashingStrategy<>(HashingStrategies.fromFunction(Data::getId));
Assert.assertTrue(set.add(new Data(1)));

// contains returns true even without hashcode and equals
Assert.assertTrue(set.contains(new Data(1)));

// Second call to add() doesn't do anything and returns false
Assert.assertFalse(set.add(new Data(1)));
UnifiedSetWithHashingStrategy<String> set = 
  new UnifiedSetWithHashingStrategy<>(HashingStrategies.fromFunction(String::toLowerCase));
set.add("ABC");
Assert.assertTrue(set.contains("ABC"));
Assert.assertTrue(set.contains("abc"));
Assert.assertFalse(set.contains("def"));
Assert.assertEquals("ABC", set.get("aBc"));
public static final HashingStrategy<String> CASE_INSENSITIVE =
  new HashingStrategy<String>()
  {
    @Override
    public int computeHashCode(String string)
    {
      int hashCode = 0;
      for (int i = 0; i < string.length(); i++)
      {
        hashCode = 31 * hashCode + Character.toLowerCase(string.charAt(i));
      }
      return hashCode;
    }

    @Override
    public boolean equals(String string1, String string2)
    {
      return string1.equalsIgnoreCase(string2);
    }
  };