如何为HashSet重写equals(),hashcode()和compareTo()

时间:2017-06-16 18:58:03

标签: java equals hashset hashcode

我正在尝试覆盖我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方法中失败,&#34;此方法未定义类型Map

4 个答案:

答案 0 :(得分:1)

这是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;
  }
}

但是,既然你说过

  

关于对象的唯一独特之处在于myMap得到的地图   在生命周期的后期填充。

我会保留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为代码生成提供的选项

enter image description here

回答有关CompareTo的更多问题

与Equals和Hashcode不同,compareTo和任何其他行为之间不存在契约。你不需要对compareTo做任何事情,直到你想利用它来进行排序。要详细了解CompareTo Why should a Java class implement comparable?

答案 1 :(得分:1)

这里的基本问题是&#34;如何确定两个对象是否彼此相等?&#34;

对于简单对象,这是一个简单的问题。然而,即使是稍微复杂的物体也变得越来越困难。

如原始问题所述:

  

关于该对象的唯一独特之处是地图myMap将在生命周期的后期填充。

给定类型MyObject的两个实例,必须将成员变量myMap相互比较。此地图的类型为Map<String, String>。立即想到几个问题:

  • 钥匙和钥匙怎么样?值定义了相等吗?
    • (键=值对是否需要作为一个单元进行比较?)
    • (或者只应将这些值相互比较?)
  • 地图中键的顺序如何影响相等?
    • (如果列表中的键被排序,那么A-B-C相当于B-C-A?)
    • (1-2-3意味着什么不同于3-2-1?)
  • 大写/小写是否与值的相等有任何不同?
  • 这些物品是否会以某种Java HashSetJava TreeSet存储?
    • (您是否需要在同一个集合中多次存储同一个对象?)
    • (或者具有相同哈希码的对象只能存储一次吗?)
  • 这些对象是否需要排序作为列表的一部分或Java Collection
  • 比较功能应如何在列表中排列不相等的对象?
    • (关键顺序如何确定某个对象是否会在列表中更早或更晚出现?)
    • (值如何确定顺序,尤其是在多个值不同的情况下?)

每个问题的答案因应用程序而异。为了使其适用于一般受众,正在做出以下假设:

  • 为了保持确定性比较,将对键进行排序
  • 值将被视为区分大小写
  • 键和值是不可分割的,将作为一个单元进行比较
  • 地图将被展平为一个字符串,因此可以轻松比较结果

使用equals()hashCode()compareTo()的好处是,一旦hashCode()正确实施,其他功能可以基于hashCode()定义

考虑到所有这些,我们有以下实现:

@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()函数只需要返回一个负数/零/正数 - 一个简单的hashCode()差异。并且equals()函数只需要返回true / false - 一个compareTo()等于零的简单检查。

为了进一步阅读,刘易斯卡罗尔就逻辑基础进行了一次着名的对话,其中涉及平等的基本问题:

https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles

而且,就简单的语法结构而言,有一个很好的例子就是两个&#34;相等&#34; chapter 6, "Pig and Pepper"开头的句子,来自爱丽丝梦游仙境

  鱼足者开始从他的胳膊下面写下一封很棒的信,然后把它交给对方,用严肃的语气说,#34;对于公爵夫人。女王邀请她去玩槌球。&#34;青蛙步兵以同样庄严的语气重复着,#34;来自女王。邀请公爵夫人去玩槌球。&#34;然后他们鞠躬低沉,卷曲纠结在一起。

答案 2 :(得分:0)

compareTo()与排序有关。它与HashSetHashMap无关。

正常工作的equals()hashCode()对基于哈希的集合的成员至关重要。请阅读Javadoc中Object的说明。

实施这些建议的明确建议可能在Joshua Bloch的 Effective Java 中。我建议阅读相关章节 - 它很容易谷歌。试图在这里解释一切都没有意义。

您可能已经注意到的一件事是,您的字段myMap有自己的equals()hashCode(),因此您无需执行任何特殊操作它。如果你可以保证没有一个字段为空,那么合理的hashCode()将是(遵循布洛赫的系统):

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;
}

(如果其中任何一个可能为null,则需要更多代码)

几乎所有IDE都会使用类中的所有字段自动生成equals()hashcode()。他们将使用与Bloch的建议非常相似的东西。寻找用户界面。你会找到的。

另一种选择是使用Apache ReflectionUtils,它允许您简单地使用:

@Override
public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this);
}

@Override
public boolean equals(final Object obj) {
    return EqualsBuilder.reflectionEquals(this, obj);
}

这将确定在运行时使用哪些字段,并应用Bloch的方法。

答案 3 :(得分:0)

如果您想使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
        }

    }