是否有可能在java中创建像Comparator,但用于实现自定义equals()和hashCode()

时间:2011-03-05 13:27:25

标签: java collections equals hashcode

我有一个对象数组,我想将它与另一个对象数组连接起来,除了具有相同id的对象。这些对象在系统中的许多地方使用,并且没有实现哈希码或等号。所以我不想实现hashCode()equals(),因为我害怕在使用该对象的系统中某处破坏某些东西而我不知道这一点。

我想将所有对象放在一个集合中,但不知何故使对象使用自定义hashCode()equals()。像自定义Comparator,但是等于。

8 个答案:

答案 0 :(得分:34)

是的,可以做这样的事情。但它不允许您将对象放入HashMap,HashSet等。这是因为标准集合类期望关键对象提供equalshashCode方法。 (这就是他们设计工作的方式......)

备选方案:

  1. 实现一个包含真实类实例的包装类,并提供自己的equalshashCode实现。

  2. 实现自己的基于哈希表的类,可以使用“hashable”对象来提供equals和hashcode功能。

  3. 咬紧牙关并在相关课程上实施equalshashCode覆盖。

  4. 事实上,第3个选项可能是最好的,因为您的代码库很可能需要使用这些对象相等的一致概念。还有其他一些事情表明您的代码需要进行大修。例如,它当前正在使用一个对象数组而不是一个Set实现来表示显然应该是一个集合。

    另一方面,当前实现可能存在/是某些真实(或想象)的性能原因;例如减少内存使用量。在这种情况下,您可能应该编写一堆辅助方法来执行操作,例如连接表示为数组的2个集合。

答案 1 :(得分:13)

当用户想要等价关系时,有90%的时间已经有了一个更简单的解决方案。你想只根据id去除一堆东西吗?你可以把它们全部放到一个地图中,用ids作为键,然后得到那个的values()集合吗?

答案 2 :(得分:10)

HashingStrategy是您正在寻找的概念。它是一个策略接口,允许您定义equals和hashcode的自定义实现。

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

正如其他人所指出的那样,您无法使用内置HashingStrategyHashSet的{​​{1}}。 Eclipse Collections包含一个名为HashMap的集合和一个名为UnifiedSetWithHashingStrategy的地图。

让我们看一个例子。这是我们可以在UnifiedMapWithHashingStrategy中使用的简单Data类。

UnifiedSetWithHashingStrategy

以下是设置public class Data { private final int id; public Data(int id) { this.id = id; } public int getId() { return id; } // No equals or hashcode } 并使用它的方法。

UnifiedSetWithHashingStrategy

为什么不使用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))); Map使用UnifiedSetWithHashingStrategy的一半内存,使用UnifiedMap的内存的四分之一。有时你没有方便的密钥,必须创建一个合成的密钥,就像一个元组。这会浪费更多的记忆。

我们如何执行查找?请记住,集合有HashMap,但不是contains()。除了get()之外,UnifiedSetWithHashingStrategy还会实施Pool,因此它还会实现MutableSet的形式。

注意:我是Eclipse Collections的提交者。

答案 3 :(得分:4)

当然,您可以创建一些外部对象,提供相等比较和HashCode。但Java的内置集合不会使用这样的对象进行比较/查找。

我曾经在我的包集合中创建了这样的界面(刚刚翻译成英文):

public interface HashableEquivalenceRelation {

    /**
     * Returns true if two objects are considered equal.
     *
     * This should form an equivalence relation, meaning it
     * should fulfill these properties:
     *  <ul>
     *    <li>Reflexivity:  {@code areEqual(o, o)}
     *            should always return true.</li>
     *    <li>Symmetry: {@code areEqual(o1,o2) == areEqual(o2,o1)}
     *            for all objects o1 and o2</li>
     *    <li>Transitivity: If {@code areEqual(o1, o2)} and {@code areEqual(o2,o3)},
     *            then {@code areEqual(o1,o3}} should hold too.</li>
     *  </ul>
     * Additionally, the relation should be temporary consistent, i.e. the
     * result of this method for the same two objects should not change as
     * long as the objects do not change significantly (the precise meaning of
     * <em>change significantly</em> is dependent on the implementation).
     *
     * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)}
     * must be true too.
     */
    public boolean areEqual(Object o1, Object o2);

    /**
     * Returns a hashCode for an arbitrary object.
     *
     * This should be temporary consistent, i.e. the result for the same
     * objects should not change as long as the object does not change significantly
     * (with change significantly having the same meaning as for {@link areEqual}).
     *
     * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)}
     * must be true too.
     */
    public int hashCode(Object o);

}

我有一组接口CustomCollectionCustomSetCustomListCustomMap等定义为java.util中的接口,但是使用了这样的接口所有方法的等价关系,而不是Object.equals给出的内置关系。我也有一些默认实现:

/**
 * The equivalence relation induced by Object#equals.
 */
public final static EquivalenceRelation DEFAULT =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return
                o1 == o2 ||
                o1 != null &&
                o1.equals(o2);
        }
        public int hashCode(Object ob)
        {
            return
                ob == null?
                0 :
                ob.hashCode();
        }
        public String toString() { return "<DEFAULT>"; }
    };

/**
 * The equivalence relation induced by {@code ==}.
 * (The hashCode used is {@link System#identityHashCode}.)
 */
public final static EquivalenceRelation IDENTITY =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) { return o1 == o2; }
        public int hashCode(Object ob) { return System.identityHashCode(ob); }
        public String toString() { return "<IDENTITY>"; }
    };

/**
 * The all-relation: every object is equivalent to every other one.
 */
public final static EquivalenceRelation ALL =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) { return true; }
        public int hashCode(Object ob) { return 0; }
        public String toString() { return "<ALL>"; }
    };

/**
 * An equivalence relation partitioning the references
 * in two groups: the null reference and any other reference.
 */
public final static EquivalenceRelation NULL_OR_NOT_NULL =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return (o1 == null && o2 == null) ||
                (o1 != null && o2 != null);
        }
        public int hashCode(Object o) { return o == null ? 0 : 1; }
        public String toString() { return "<NULL_OR_NOT_NULL>"; }
    };

/**
 * Two objects are equivalent if they are of the same (actual) class.
 */
public final static EquivalenceRelation SAME_CLASS =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return o1 == o2 || o1 != null && o2 != null &&
                o1.getClass() == o2.getClass();
        }
        public int hashCode(Object o) { return o == null ? 0 : o.getClass().hashCode(); }
        public String toString() { return "<SAME_CLASS>"; }
    };


/**
 * Compares strings ignoring case.
 * Other objects give a {@link ClassCastException}.
 */
public final static EquivalenceRelation STRINGS_IGNORE_CASE =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return o1 == null ?
                o2 == null :
                ((String)o1).equalsIgnoreCase((String)o2);
        }
        public int hashCode(Object o)
        {
            return o == null ? -12345 : ((String)o).toUpperCase().hashCode();
        }
        public String toString() { return "<STRINGS_IGNORE_CASE>"; }
    };


/**
 * Compares {@link CharSequence} implementations by content.
 * Other object give a {@link ClassCastException}.
 */
public final static EquivalenceRelation CHAR_SEQUENCE_CONTENT =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) 
        {
            CharSequence seq1 = (CharSequence)o1;
            CharSequence seq2 = (CharSequence)o2;
            if (seq1 == null ^ seq2 == null) // nur eins von beiden null
                return false;
            if (seq1 == seq2)   // umfasst auch den Fall null == null
                return true;
            int size = seq1.length();
            if (seq2.length() != size)
                return false;
            for (int i = 0; i < size; i++)
                {
                    if (seq1.charAt(i) != seq2.charAt(i))
                        return false;
                }
            return true;
        }
        /**
         * Entrspricht String.hashCode
         */
        public int hashCode(Object o)
        {
            CharSequence sequence = (CharSequence)o;
            if (sequence == null)
                return 0;
            int hash = 0;
            int size = sequence.length();
            for (int i = 0; i < size; i++)
                {
                    hash = hash * 31 + sequence.charAt(i);
                }
            return hash;
        }
    };

答案 4 :(得分:1)

在这里使用TreeSet帮助吗? TreeSet实际上使用compare / compareTo执行排序和基于集合的行为,并允许您定义使用i n one of the constructors的自定义Comparator。

答案 5 :(得分:1)

刚刚遇到这个问题并制定了一个简单的解决方案。不确定它的内存密集程度;我确信人们可以对其进行改进。

Comparator返回0时,元素匹配。

public static <E> Set<E> filterSet(Set<E> set, Comparator<E> comparator){
    Set<E> output = new HashSet<E>();
    for(E eIn : set){
        boolean add = true;
        for(E eOut : output){
            if(comparator.compare(eIn, eOut) == 0){
                add = false;
                break;
            }
        }
        if(add) output.add(eIn);
    }
    return output;
}

我的用例是我需要过滤掉重复的网址,就像指向同一文档的网址一样。 URL对象具有samePage()方法,如果除片段之外的所有内容都相同,则返回true。

filtered = Misc.filterSet(filtered, (a, b) -> a.sameFile(b) ? 0 : 1);

答案 6 :(得分:0)

使用比较器进行重复数据连接时,您将无法成功。大概你想要做这样的事情:

List<Object> list = new ArrayList<Object>();
list.addAll( a );
list.addAll( b );
Collections.sort( list, new MyCustomComparator() );

问题是比较器不仅需要比较equals / not-equals,还要比较相对顺序。如果对象x和y不相等,则必须回答一个是否大于另一个。你将无法做到这一点,因为你实际上并没有尝试比较对象。如果您没有给出一致的答案,您将把排序算法发送到无限循环。

我确实有一个解决方案。 Java有一个名为LinkedHashSet的类,其优点是它不允许插入重复项,但维护插入顺序。而不是实现比较器,实现一个包装类来保存实际对象并实现hashCode / equals。

答案 7 :(得分:0)

使用番石榴Equivalence

Equivalence<T> equivalence = new Equivalence<T>() {
    @Override
    protected boolean doEquivalent(T a, T b) {
        return CustomComparator.equals(a, b);
    }

    @Override
    protected int doHash(T item) {
        return CustomHashCodeGenerator.hashCode(item);
    }
};
List<T> items = getItems();
Set<Equivalence.Wrapper<T>> setWithWrappedObjects = items.stream()
    .map(item -> equivalence.wrap(item))
    .collect(Collectors.toSet());