简体   繁体   English

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

[英]How to override equals(), hashcode() and compareTo() for a HashSet

I am trying to override the mentioned methods for my HashSet : 我试图覆盖我的HashSet提到的方法:

Set<MyObject> myObjectSet = new HashSet<MyObject>();

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

  [...]
}

How do I override the hashcode(), equals() and compareTo() method? 如何覆盖hashcode(),equals()和compareTo()方法?


Currently I have the following: 目前我有以下内容:

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

I read that comparing by id is not enough this is object is a persistent entity for the DB (see here ). 我读到,通过id进行比较是不够的,这是对象是DB的持久实体(参见此处 )。

The name and number aren't unique across all objects of this type. 此类型的所有对象的名称和编号都不是唯一的。

So how should I override it? 那我应该怎么覆盖呢?
Do I also need to compare the hashMap inside it? 我还需要比较里面的hashMap吗?

I am confused. 我很迷惑。 The only unique thing about the object is the the map myMap which gets populated later in the lifecycle. 关于该对象的唯一独特之处是地图myMap将在生命周期的后期填充。

How do I check for its equality? 我如何检查其相等性?

Based on all the responses I have changed the methods to the following 基于所有响应,我已将方法更改为以下内容

 @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()));
    }

This fails at the compareTo method, "this method is undefined for the type Map 这在compareTo方法中失败,“对于类型Map,此方法未定义

This is what intellij default option gives 这是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;
  }
}

But, since you said 但是,既然你说过

The only unique thing about the object is the the map myMap which gets populated later in the lifecycle. 关于该对象的唯一独特之处是地图myMap将在生命周期的后期填充。

I would just keep myMap and skip both name and number (But this begs the question, why would you include a redundant data- name and number in all the elements of your collection?) 我会保留myMap并跳过名称和号码(但是这引出了一个问题,为什么你会在你的集合的所有元素中包含冗余数据名和数字?)

Then it becomes 然后它变成了

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

Keep in mind that, there are other ways too for the equals and hashcode methods. 请记住,equals和hashcode方法还有其他方法。 For example, Here are the options that intelliJ gives for code generation 例如,以下是intelliJ为代码生成提供的选项

在此输入图像描述

To Answer Further question about CompareTo 要回答有关CompareTo的更多问题

Unlike Equals and Hashcode, here is no contract exist between compareTo and any other behaviors. 与Equals和Hashcode不同,compareTo和任何其他行为之间不存在契约。 You don't really need to do anything with compareTo until you want to make use of it for say, sorting. 你不需要对compareTo做任何事情,直到你想利用它来进行排序。 To read more about CompareTo Why should a Java class implement comparable? 阅读有关CompareTo的更多信息为什么Java类应该具有可比性?

The basic question here is "How can you determine if two objects are equal to each other?" 这里的基本问题是“你怎么能确定两个物体是否相等?”

This is a simple question for simple objects. 对于简单对象,这是一个简单的问题。 However, it becomes increasingly difficult with even slightly more complex objects. 然而,即使是稍微复杂的物体也变得越来越困难。

As stated in the original question: 如原始问题所述:

The only unique thing about the object is the the map myMap which gets populated later in the lifecycle. 关于该对象的唯一独特之处是地图myMap将在生命周期的后期填充。

Given two instances of the type MyObject , the member variables myMap must be compared with each other. 给定两个MyObject类型的实例,必须将成员变量myMap相互比较。 This map is of type Map<String, String> . 此映射的类型为Map<String, String> A few questions immediately come to mind: 立刻想到几个问题:

  • How do the keys & values define equality? 键和值如何定义相等?
    • (does a key=value pair need to be compared as a unit?) (键=值对是否需要作为一个单元进行比较?)
    • (or should only the values be compared to each other?) (或者只应将这些值相互比较?)
  • How does the order of the keys in the map affect equality? 地图中键的顺序如何影响相等?
    • (should keys in the list be sorted, so that ABC is equivalent to BCA?) (如果列表中的键被排序,那么ABC等同于BCA?)
    • (or does 1-2-3 mean something different than 3-2-1?) (1-2-3意味着什么不同于3-2-1?)
  • Does upper/lower case make any different to the equality of the values? 大写/小写是否与值的相等有任何不同?
  • Will these objects ever be stored in some kind of Java HashSet or Java TreeSet ? 这些对象是否会存储在某种Java HashSetJava TreeSet中
    • (do you need to store the same object several times in the same collection?) (你需要在同一个集合中多次存储同一个对象吗?)
    • (or should objects with equal hashcodes only be stored once?) (或者具有相同哈希码的对象是否只能存储一次?)
  • Will these objects ever require sorting as part of a list or Java Collection ? 这些对象是否需要排序作为列表或Java集合的一部分
  • How should the comparison function arrange non-equal objects in a list? 比较函数应该如何在列表中排列不相等的对象?
    • (how should key order determine if an object will come earlier or later in a list?) (关键顺序如何确定对象是否会在列表中更早或更晚出现?)
    • (how should values determine order, especially if several values are different?) (值应如何确定顺序,尤其是在多个值不同的情况下?)

Answers to each of these questions will vary between applications. 每个问题的答案因应用程序而异。 In order to keep this applicable to a general audience, the following assumptions are being made: 为了使其适用于一般受众,正在做出以下假设:

  • To maintain a deterministic comparison, keys will be sorted 为了保持确定性比较,将对键进行排序
  • Values will be considered to be case-sensitive 值将被视为区分大小写
  • Keys and values are inseparable, and will be compared as a unit 键和值是不可分割的,将作为一个单元进行比较
  • The Map will be flattened into a single String, so results can be compared easily Map将被展平为单个String,因此可以轻松比较结果

The beauty of using equals() , hashCode() , and compareTo() is that once hashCode() is implemented properly, the other functions can be defined based on hashCode() . 使用equals()hashCode()compareTo()是,一旦hashCode()被正确实现,其他函数可以基于hashCode()来定义。

Considering all of that, we have the following implementation: 考虑到所有这些,我们有以下实现:

@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());
}

All the critical work is being done inside of hashCode() . 所有关键工作都在hashCode()内部完成。 For sorting, the compareTo() function only needs to return a negative/zero/positive number -- a simple hashCode() diff. 对于排序, compareTo()函数只需要返回一个负数/零/正数 - 一个简单的hashCode()差异。 And the equals() function only needs to return true/false -- a simple check that compareTo() equals zero. equals()函数只需要返回true / false - compareTo()等于零的简单检查。


For further reading, there is a famous dialogue by Lewis Carroll on the foundations of logic, which touches on the basic question of equality: 为了进一步阅读,路易斯·卡罗尔(Lewis Carroll)就逻辑基础进行了一次着名的对话,其中涉及平等的基本问题:

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

And, in regard to even simple grammatical constructs, there is a fine example of two "equal" sentences at the start of chapter 6, "Pig and Pepper" , from Alice in Wonderland : 而且,就简单的语法结构而言,在爱丽丝梦游仙境第6章“猪和胡椒”的开头有两个“平等”句子的优秀例子:

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. 然后他们鞠躬低沉,卷曲纠缠在一起。

compareTo() is relevant to sorting. compareTo()与排序有关。 It has no relevance to a HashSet or HashMap . 它与HashSetHashMap无关。

A properly working equals() and hashCode() are vital for members of hash-based collections. 正确工作的equals()hashCode()对于基于散列的集合的成员至关重要。 Read their specifications in the Javadoc for Object . 在Javadoc for Object阅读它们的规范。

Possibly the definitive recommendations for implementing these are in Joshua Bloch's Effective Java . 实现这些的最终建议可能在Joshua Bloch的Effective Java中 I recommend reading the relevant chapter -- it's easily Google-able. 我建议阅读相关章节 - 它很容易谷歌。 There's no point in trying to paraphrase it all here. 试图在这里解释一切毫无意义。


One thing that may have escaped your notice, is that your field myMap has a working equals() and hashCode() of its own, so you don't have to do anything special with it. 可能没有引起注意的一件事是你的字段myMap有一个自己的equals()hashCode() ,所以你不必对它做任何特殊的事情。 If you can guarantee that none of the fields are null, a reasonable hashCode() would be (following Bloch's system): 如果你可以保证没有字段为null,那么合理的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;
}

(You'll need more code if any of these could be null) (如果其中任何一个可能为null,您将需要更多代码)


Pretty much all IDEs will automatically generate both equals() and hashcode() , using all the fields in the class. 几乎所有IDE都会使用类中的所有字段自动生成equals()hashcode() They'll use something very similar to Bloch's recommendations. 他们将使用与Bloch的建议非常相似的东西。 Hunt around the UI. 寻找用户界面。 You'll find it. 你会找到的。

Another alternative is to use Apache ReflectionUtils, which allows you to simply use: 另一种方法是使用Apache ReflectionUtils,它允许您简单地使用:

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

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

This works out which fields to use at runtime, and applies Bloch's methods. 这解决了在运行时使用哪些字段,并应用Bloch的方法。

If you want to make myMap implements comparable, and any other methods that you want, create decorator that implement comparable interface and delegate all other methods to enclosing myMap instance. 如果你想使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();
    }

}

You may use this map in anywhere where you use myMap 您可以在使用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
        }

    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM