是否有可能在java中做比较器,但实现自定义equals()和hashCode()
我有一个对象的数组,我想连接它与另一个对象的数组,除了具有相同的ID的对象。 该对象在系统中的许多地方使用,没有哈希码或等于实现。 所以我不想实现hashCode()和equals(),因为我害怕在系统中某处使用那些对象,我不知道这些。 所以我想把所有的对象放在一个集合中,但不知何故使该对象使用自定义提供的hashCode()和equals()。 像自定义比较器(),但等于。
是的,有可能做这样的事情。 但是它不允许你把你的对象放到HashMap,HashSet等中。这是因为标准集合类需要关键对象提供equals
和hashCode
方法。 (这是他们devise工作的方式…)
备择scheme:
-
实现一个包装类,它包含一个真实类的实例,并提供自己的
equals
和hashCode
的实现。 -
实现你自己的基于散列表的类,它可以使用“可散列”对象来提供equals和hashcodefunction。
-
硬着头皮实现
equals
和hashCode
覆盖相关的类。
实际上,第三个选项可能是最好的,因为你的代码库很可能需要使用一致的概念来表示这些对象是平等的。 还有其他的事情表明你的代码需要彻底检修。 例如,它正在使用一个对象数组而不是Set实现来表示显然应该是一个集合的事实。
另一方面,也许在当前的实施中存在一些真实的(或想象的)性能原因; 例如减less内存使用量。 在这种情况下,你可能应该写一大堆帮助方法来进行操作,比如连接2个表示为数组的集合。
当用户想要一个等价关系时,90%的时间已经是一个更直接的解决scheme。 你想重新删除一堆基于id的东西? 你可以把它们全部放到一个Map中,把ID作为键,然后得到values()
集合吗?
哈希策略是你正在寻找的概念。 这是一个策略接口,允许您定义equals和hashcode的自定义实现。
public interface HashingStrategy<E> { int computeHashCode(E object); boolean equals(E object1, E object2); }
正如其他人已经指出的,你不能使用内置HashSet
或HashMap
的HashingStrategy
。 Eclipse Collections包含一个名为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
。 有时你没有一个方便的键,必须创build一个合成的,就像一个元组。 这可能浪费更多的记忆。
我们如何执行查找? 记住集合contains()
,但不contains()
get()
。 UnifiedSetWithHashingStrategy
除了MutableSet
之外还实现了MutableSet
,所以它也实现了get()
的forms。
注意:我是Eclipse集合的提交者。
当然,您可以创build一些提供相等比较和HashCode的外部对象。 但是,Java的内置集合不会使用这样的对象进行比较/查找。
我曾经在我的软件包集合中创build了一个这样的界面(刚刚翻译成英文):
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, ie 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, ie 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); }
比起我定义的一组接口CustomCollection
, CustomSet
, CustomList
, CustomMap
等定义类似于java.util
的接口,但是对所有的方法使用了这样的等价关系,而不是Object.equals
给定的内build关系。 我也有一些默认的实现:
/** * 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; } };
会在这里使用TreeSet的帮助? TreeSet实际上使用compare / compareTo来执行sorting和基于行为的设置,并允许您定义一个自定义比较器,以供其中一个构造函数使用 。
刚刚有这个问题,并制定了一个简单的解决scheme。 不知道它是如何内存密集的; 我相信人们可以细化它。
当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,就像指向同一个文档的URL一样。 URL对象有一个samePage()
方法,如果除了片段之外的所有内容都相同,将会返回true。
filtered = Misc.filterSet(filtered, (a, b) -> a.sameFile(b) ? 0 : 1);
使用比较器不能成功执行重复连接。 大概你正在做这样的事情:
List<Object> list = new ArrayList<Object>(); list.addAll( a ); list.addAll( b ); Collections.sort( list, new MyCustomComparator() );
问题是比较器不仅要比较等于/不等于,而且要比较相对顺序。 给定对象x和y不相等,如果一个大于另一个,则必须回答。 你将无法做到这一点,因为你实际上并没有试图比较对象。 如果你没有给出一致的答案,你会发送sortingalgorithm到一个无限循环。
我有你的解决scheme。 Java有一个名为LinkedHashSet的类,它的优点是不允许重复插入,但是维护插入顺序。 实现一个包装类来保存实际的对象,并实现hashCode / equals,而不是实现一个比较器。