如何覆盖 HashSet 的 equals()、hashcode() 和 compareTo()
How to override equals(), hashcode() and compareTo() for a HashSet
我正在尝试为我的 HashSet
:
重写上述方法
Set<MyObject> myObjectSet = new HashSet<MyObject>();
MyObject:
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
Map<String,String> myMap;
public MyObject(String name, int number, Map<String,String> myMap) {
this.name = name;
this.number = number;
this.myMap = myMap;
}
[...]
}
如何覆盖 hashcode()、equals() 和 compareTo() 方法?
目前我有:
public int hashCode () {
return id.hashCode();
}
// override the equals method.
public boolean equals(MyObject s) {
return id.equals(s.id);
}
// override compareTo
public int compareTo(MyObject s) {
return id.compareTo(s.id);
}
我读到通过 id 比较是不够的,这是对象是数据库的持久实体(参见 here)。
此类型的所有对象的名称和编号并非唯一。
那么我该如何覆盖它呢?
我还需要比较里面的hashMap吗?
我很困惑。该对象的唯一独特之处在于 myMap 地图,它在生命周期的后期被填充。
如何检查它的相等性?
根据所有回复,我将方法更改为以下内容
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap()));
}
这在 compareTo 方法处失败,“此方法未定义类型 Map
这是intellij默认选项给出的
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
if (number != myObject.number) return false;
if (name != null ? !name.equals(myObject.name) : myObject.name != null) return false;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + number;
result = 31 * result + (myMap != null ? myMap.hashCode() : 0);
return result;
}
}
但是,既然你说了
The only unique thing about the object is the the map myMap which gets
populated later in the lifecycle.
我会保留 myMap 并跳过名称和编号(但这引出了一个问题,为什么要在集合的所有元素中包含冗余数据名称和编号?)
则变成
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
}
请记住,equals 和 hashcode 方法还有其他方法。例如,以下是 intelliJ 为代码生成提供的选项
回答有关 CompareTo 的进一步问题
与 Equals 和 Hashcode 不同,compareTo 和任何其他行为之间不存在契约。在您想要使用它进行排序之前,您实际上不需要对 compareTo 做任何事情。阅读更多关于 CompareTo Why should a Java class implement comparable?
compareTo()
与排序相关。它与 HashSet
或 HashMap
.
无关
正常工作的 equals()
和 hashCode()
对于基于散列的集合的成员至关重要。在 Javadoc 中阅读 Object
.
的规范
Joshua Bloch 的 Effective Java 可能是实施这些的最终建议。我建议阅读相关章节——它很容易 Google-able。在这里解释一切是没有意义的。
有一件事可能没有引起您的注意,即您的字段 myMap
有自己的工作 equals()
和 hashCode()
,因此您无需执行任何操作特别的。如果您可以保证 none 个字段为空,则合理的 hashCode()
将是(遵循 Bloch 的系统):
public int hashCode() {
int result = 44; // arbitrarily chosen
result = 31 * result + (int) (id ^ (id >>> 32));
result = 31 * result + name.hashCode();
result = 31 * result + number;
result = 31 * result + myMap.hashCode();
return result;
}
(如果其中任何一个为空,您将需要更多代码)
几乎所有 IDE 都会使用 class 中的所有字段自动生成 equals()
和 hashcode()
。他们将使用与 Bloch 的建议非常相似的东西。在 UI 周围搜寻。你会找到的。
另一种选择是使用 Apache ReflectionUtils,它允许您简单地使用:
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(final Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
这会计算出在运行时使用哪些字段,并应用 Bloch 的方法。
这里的基本问题是"How can you determine if two objects are equal to each other?"
这是一个针对简单对象的简单问题。然而,即使对象稍微复杂一点,它也会变得越来越困难。
如原问题所述:
The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.
给定类型 MyObject
的两个实例,成员变量 myMap
必须相互比较。此地图的类型为 Map<String, String>
。立即想到几个问题:
- 键和值如何定义相等性?
- (key=value对是否需要作为一个单位进行比较?)
- (或者应该只比较值?)
- 映射中键的顺序如何影响相等性?
- (列表中的键是否应该排序,以便 A-B-C 等同于 B-C-A?)
- (或者 1-2-3 与 3-2-1 的含义不同吗?)
- upper/lower大小写对值的相等有什么不同吗?
- 这些对象是否会存储在某种 Java HashSet or Java TreeSet 中?
- (是否需要在同一个集合中多次存储同一个对象?)
- (或者具有相同哈希码的对象应该只存储一次?)
- 这些对象是否需要作为列表的一部分进行排序或 Java Collection?
- 比较函数应该如何排列列表中不相等的对象?
- (键顺序应如何确定一个对象在列表中是先出现还是晚出现?)
- (值应如何确定顺序,尤其是当多个值不同时?)
每个问题的答案因应用程序而异。为了使其适用于一般受众,做出以下假设:
- 为了保持确定性比较,将对键进行排序
- 值将被视为区分大小写
- 键和值是不可分割的,将作为一个单元进行比较
- Map 将被展平为单个字符串,因此可以轻松比较结果
使用equals()
、hashCode()
、compareTo()
的妙处在于,一旦hashCode()
实现得当,其他函数就可以在[=15的基础上定义了=].
考虑到所有这些,我们有以下实现:
@Override
public boolean equals(final Object o)
{
if (o instanceof MyObject)
{
return (0 == this.compareTo(((MyObject) o)));
}
return false;
}
@Override
public int hashCode()
{
return getKeyValuePairs(this.myMap).hashCode();
}
// Return a negative integer, zero, or a positive integer
// if this object is less than, equal to, or greater than the other object
public int compareTo(final MyObject o)
{
return this.hashCode() - o.hashCode();
}
// The Map is flattened into a single String for comparison
private static String getKeyValuePairs(final Map<String, String> m)
{
final StringBuilder kvPairs = new StringBuilder();
final String kvSeparator = "=";
final String liSeparator = "^";
if (null != m)
{
final List<String> keys = new ArrayList<>(m.keySet());
Collections.sort(keys);
for (final String key : keys)
{
final String value = m.get(key);
kvPairs.append(liSeparator);
kvPairs.append(key);
kvPairs.append(kvSeparator);
kvPairs.append(null == value ? "" : value);
}
}
return 0 == kvPairs.length() ? "" : kvPairs.substring(liSeparator.length());
}
所有关键工作都在 hashCode()
内部完成。对于排序,compareTo()
函数只需要 return 一个 negative/zero/positive 数字——一个简单的 hashCode()
差异。而 equals()
函数只需要 return true/false -- 一个简单的检查 compareTo()
是否等于零。
为了进一步阅读,刘易斯·卡罗尔 (Lewis Carroll) 关于逻辑基础的著名对话涉及平等的基本问题:
https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles
而且,即使是简单的语法结构,chapter 6, "Pig and Pepper" 开头的两个 "equal" 句子也是一个很好的例子,来自 爱丽丝梦游仙境[=102] =]:
The Fish-Footman began by producing from under his arm a great letter, and this he handed over to the other, saying, in a solemn tone, "For the Duchess. An invitation from the Queen to play croquet." The Frog-Footman repeated, in the same solemn tone, "From the Queen. An invitation for the Duchess to play croquet." Then they both bowed low and their curls got entangled together.
如果您想使 myMap
实现可比较,以及您想要的任何其他方法,请创建实现可比较接口的装饰器,并将所有其他方法委托给封闭的 myMap
实例。
public class ComparableMap implements Map<String, String>, Comparable<Map<String, String>> {
private final Map<String, String> map;
public ComparableMap(Map<String, String> map) {
this.map = map;
}
@Override
public int compareTo(Map<String, String> o) {
int result = 0;
//your implementation based on values on map on you consider one map bigger, less or as same as another
return result;
}
@Override
public boolean equals(Object obj) {
return map.equals(obj);
}
@Override
public int hashCode() {
return map.hashCode();
}
// map implementation methods
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public String get(Object key) {
return map.get(key);
}
@Override
public String put(String key, String value) {
return map.put(key, value);
}
@Override
public String remove(Object key) {
return map.remove(key);
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
map.putAll(m);
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<String> keySet() {
return map.keySet();
}
@Override
public Collection<String> values() {
return map.values();
}
@Override
public Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
}
您可以在您使用的任何地方使用此地图myMap
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
ComparableMap myMap;
public MyObject(String name, int number, Map<String, String> myMap) {
this.name = name;
this.number = number;
this.myMap = new ComparablemyMap(myMap);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap())); //now it works
}
}
我正在尝试为我的 HashSet
:
Set<MyObject> myObjectSet = new HashSet<MyObject>();
MyObject:
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
Map<String,String> myMap;
public MyObject(String name, int number, Map<String,String> myMap) {
this.name = name;
this.number = number;
this.myMap = myMap;
}
[...]
}
如何覆盖 hashcode()、equals() 和 compareTo() 方法?
目前我有:
public int hashCode () {
return id.hashCode();
}
// override the equals method.
public boolean equals(MyObject s) {
return id.equals(s.id);
}
// override compareTo
public int compareTo(MyObject s) {
return id.compareTo(s.id);
}
我读到通过 id 比较是不够的,这是对象是数据库的持久实体(参见 here)。
此类型的所有对象的名称和编号并非唯一。
那么我该如何覆盖它呢?
我还需要比较里面的hashMap吗?
我很困惑。该对象的唯一独特之处在于 myMap 地图,它在生命周期的后期被填充。
如何检查它的相等性?
根据所有回复,我将方法更改为以下内容
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap()));
}
这在 compareTo 方法处失败,“此方法未定义类型 Map
这是intellij默认选项给出的
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
if (number != myObject.number) return false;
if (name != null ? !name.equals(myObject.name) : myObject.name != null) return false;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + number;
result = 31 * result + (myMap != null ? myMap.hashCode() : 0);
return result;
}
}
但是,既然你说了
The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.
我会保留 myMap 并跳过名称和编号(但这引出了一个问题,为什么要在集合的所有元素中包含冗余数据名称和编号?)
则变成
import java.util.Map;
public class MyObject {
String name;
int number;
Map<String,String> myMap;
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyObject myObject = (MyObject) o;
return myMap != null ? myMap.equals(myObject.myMap) : myObject.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
}
请记住,equals 和 hashcode 方法还有其他方法。例如,以下是 intelliJ 为代码生成提供的选项
回答有关 CompareTo 的进一步问题
与 Equals 和 Hashcode 不同,compareTo 和任何其他行为之间不存在契约。在您想要使用它进行排序之前,您实际上不需要对 compareTo 做任何事情。阅读更多关于 CompareTo Why should a Java class implement comparable?
compareTo()
与排序相关。它与 HashSet
或 HashMap
.
正常工作的 equals()
和 hashCode()
对于基于散列的集合的成员至关重要。在 Javadoc 中阅读 Object
.
Joshua Bloch 的 Effective Java 可能是实施这些的最终建议。我建议阅读相关章节——它很容易 Google-able。在这里解释一切是没有意义的。
有一件事可能没有引起您的注意,即您的字段 myMap
有自己的工作 equals()
和 hashCode()
,因此您无需执行任何操作特别的。如果您可以保证 none 个字段为空,则合理的 hashCode()
将是(遵循 Bloch 的系统):
public int hashCode() {
int result = 44; // arbitrarily chosen
result = 31 * result + (int) (id ^ (id >>> 32));
result = 31 * result + name.hashCode();
result = 31 * result + number;
result = 31 * result + myMap.hashCode();
return result;
}
(如果其中任何一个为空,您将需要更多代码)
几乎所有 IDE 都会使用 class 中的所有字段自动生成 equals()
和 hashcode()
。他们将使用与 Bloch 的建议非常相似的东西。在 UI 周围搜寻。你会找到的。
另一种选择是使用 Apache ReflectionUtils,它允许您简单地使用:
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(final Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
这会计算出在运行时使用哪些字段,并应用 Bloch 的方法。
这里的基本问题是"How can you determine if two objects are equal to each other?"
这是一个针对简单对象的简单问题。然而,即使对象稍微复杂一点,它也会变得越来越困难。
如原问题所述:
The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.
给定类型 MyObject
的两个实例,成员变量 myMap
必须相互比较。此地图的类型为 Map<String, String>
。立即想到几个问题:
- 键和值如何定义相等性?
- (key=value对是否需要作为一个单位进行比较?)
- (或者应该只比较值?)
- 映射中键的顺序如何影响相等性?
- (列表中的键是否应该排序,以便 A-B-C 等同于 B-C-A?)
- (或者 1-2-3 与 3-2-1 的含义不同吗?)
- upper/lower大小写对值的相等有什么不同吗?
- 这些对象是否会存储在某种 Java HashSet or Java TreeSet 中?
- (是否需要在同一个集合中多次存储同一个对象?)
- (或者具有相同哈希码的对象应该只存储一次?)
- 这些对象是否需要作为列表的一部分进行排序或 Java Collection?
- 比较函数应该如何排列列表中不相等的对象?
- (键顺序应如何确定一个对象在列表中是先出现还是晚出现?)
- (值应如何确定顺序,尤其是当多个值不同时?)
每个问题的答案因应用程序而异。为了使其适用于一般受众,做出以下假设:
- 为了保持确定性比较,将对键进行排序
- 值将被视为区分大小写
- 键和值是不可分割的,将作为一个单元进行比较
- Map 将被展平为单个字符串,因此可以轻松比较结果
使用equals()
、hashCode()
、compareTo()
的妙处在于,一旦hashCode()
实现得当,其他函数就可以在[=15的基础上定义了=].
考虑到所有这些,我们有以下实现:
@Override
public boolean equals(final Object o)
{
if (o instanceof MyObject)
{
return (0 == this.compareTo(((MyObject) o)));
}
return false;
}
@Override
public int hashCode()
{
return getKeyValuePairs(this.myMap).hashCode();
}
// Return a negative integer, zero, or a positive integer
// if this object is less than, equal to, or greater than the other object
public int compareTo(final MyObject o)
{
return this.hashCode() - o.hashCode();
}
// The Map is flattened into a single String for comparison
private static String getKeyValuePairs(final Map<String, String> m)
{
final StringBuilder kvPairs = new StringBuilder();
final String kvSeparator = "=";
final String liSeparator = "^";
if (null != m)
{
final List<String> keys = new ArrayList<>(m.keySet());
Collections.sort(keys);
for (final String key : keys)
{
final String value = m.get(key);
kvPairs.append(liSeparator);
kvPairs.append(key);
kvPairs.append(kvSeparator);
kvPairs.append(null == value ? "" : value);
}
}
return 0 == kvPairs.length() ? "" : kvPairs.substring(liSeparator.length());
}
所有关键工作都在 hashCode()
内部完成。对于排序,compareTo()
函数只需要 return 一个 negative/zero/positive 数字——一个简单的 hashCode()
差异。而 equals()
函数只需要 return true/false -- 一个简单的检查 compareTo()
是否等于零。
为了进一步阅读,刘易斯·卡罗尔 (Lewis Carroll) 关于逻辑基础的著名对话涉及平等的基本问题:
https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles
而且,即使是简单的语法结构,chapter 6, "Pig and Pepper" 开头的两个 "equal" 句子也是一个很好的例子,来自 爱丽丝梦游仙境[=102] =]:
The Fish-Footman began by producing from under his arm a great letter, and this he handed over to the other, saying, in a solemn tone, "For the Duchess. An invitation from the Queen to play croquet." The Frog-Footman repeated, in the same solemn tone, "From the Queen. An invitation for the Duchess to play croquet." Then they both bowed low and their curls got entangled together.
如果您想使 myMap
实现可比较,以及您想要的任何其他方法,请创建实现可比较接口的装饰器,并将所有其他方法委托给封闭的 myMap
实例。
public class ComparableMap implements Map<String, String>, Comparable<Map<String, String>> {
private final Map<String, String> map;
public ComparableMap(Map<String, String> map) {
this.map = map;
}
@Override
public int compareTo(Map<String, String> o) {
int result = 0;
//your implementation based on values on map on you consider one map bigger, less or as same as another
return result;
}
@Override
public boolean equals(Object obj) {
return map.equals(obj);
}
@Override
public int hashCode() {
return map.hashCode();
}
// map implementation methods
@Override
public int size() {
return map.size();
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return map.containsValue(value);
}
@Override
public String get(Object key) {
return map.get(key);
}
@Override
public String put(String key, String value) {
return map.put(key, value);
}
@Override
public String remove(Object key) {
return map.remove(key);
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
map.putAll(m);
}
@Override
public void clear() {
map.clear();
}
@Override
public Set<String> keySet() {
return map.keySet();
}
@Override
public Collection<String> values() {
return map.values();
}
@Override
public Set<Entry<String, String>> entrySet() {
return map.entrySet();
}
}
您可以在您使用的任何地方使用此地图myMap
public class MyObject implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int number;
ComparableMap myMap;
public MyObject(String name, int number, Map<String, String> myMap) {
this.name = name;
this.number = number;
this.myMap = new ComparablemyMap(myMap);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MyComplexObj myComplexObj = (MyComplexObj) o;
return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
}
@Override
public int hashCode() {
return myMap != null ? myMap.hashCode() : 0;
}
public int compareTo(MyComplexObj o) {
return myMap.compareTo(o.getMyMap())); //now it works
}
}