3

I have a domain model in which I use a few aggregation relations, i.e. an object of class A contains zero or more objects of class B.

I use Java for the implementation and I represent such an aggregation as a field of type List<B> in A, and a field of type A in B. In this way, each object can be the root of an aggregation tree. Classes A and B may also contain other shallow fields, i.e. fields of type int, float, String, and so on.

Now I need to define different kinds of equality methods on my model:

  1. Shallow equality: compare two instances of A by comparing its shallow fields only, i.e. leaving out references to other domain objects. In this case, I am only interested to know if two nodes have the same contents.
  2. Deep equality: compare two instances of A by comparing its shallow fields and by recursively comparing its children. In this case, I want to check if two complete trees are equal.

I considered overriding the hashCode() and equals() methods for class A but I do not know if this should be the shallow equality or the deep equality method. Once I decide which of the two equality methods is implemented as A.equals(), I will define the other method with another name. This is an important choice because the equals() method determines such things as membership in a Set.

So, is one of the two possibilities (shallow versus deep equality) considered a more idiomatic choice for implementing the equals method in Java?

  • 4
    I would use equals for deep comparing, and some other name (matches, for example) for shallow comparing. This would match how equals is expected to work. – superM Jan 18 '14 at 16:57
  • 1
    Why do you think that Strings should be “shallow” equal only? – Donal Fellows Jan 18 '14 at 17:09
  • @DonalFellows: Because they are not part of my aggregation hierarchy. A node of type A can have attributes of type int, float, String, etc, and sub-nodes of type B. By shallow I mean looking at the attributes of a node, by deep I mean looking at the attributes of a node, and at its sub-nodes, recursively. The String class is not part of my domain model. – Giorgio Jan 18 '14 at 17:14
10

I prefer to think of "equals" this way: if a.equals(b) then you can replace all references to b with references to a and the program behavior will not change. This is true for immutable value classes like String and should be true for quasi-value classes like Date. I think this is the way most Java programmers expect "equals" to behave.

Defining equals in some other way, so that things are sort-of equal, is likely to lead to subtle bugs when some future programmer puts these instances in a hash table or set. That programmer will then hate you forever.

  • I think this is the most reasonable answer: equals should not express some arbitrary equivalence. Being able to swap two equals objects without changing the behaviour seems a very good criterion to me. If a method does not pass this check, it should be called something else (or I should change my class model). – Giorgio Jan 18 '14 at 21:17
4

This is an important choice because the equals() method determines such things as membership in a Set.

You sort of answered your own question there. It's your decision based on your requirements. If you need your objects to be placed in a Set based on shallow equality, you need to implement equals() and hashcode() accordingly. If you need to base it on deep equality, you implement it that way.

1

As the other answer says, you need to decide based on your own requirements.

Something else to consider is that you should compare equality of fields in your object using your own decision of what is important, but let those objects themselves decide what is important within them.

so if you have class A with int x, int[] y and B z then:

Do you compare x? Do you compare y? Do you compare z? Those are your choices. Comparing y would almost certainly involve use of Arrays.equals() as you would want to compare all members of y.

Whether the .equals() method in class B then goes on to do further deep comparison of its members is then a decision to be delegated to that class B. You wouldn't try and do a comparison of the contents of B from within A unless you really really had to.

So the choice isn't really "deep" or not. It is what members of this class need to be compared. If some of those members are Objects themselves then those objects define their own equality algorithm and that may again dig deeper into further member objects or super class. Each stage just worries about it's own equality though.

  • The point is that, according to my requirements, I need to implement both, and I was wondering which one I should implement by overriding equals and which one I should implement as a separate method (e.g. match, as suggested by superM). I thought that there might be some general recommendations, e.g. a deep implementation of equals implies a corresponding deep implementation of hashCode, which might become quite expensive. – Giorgio Jan 18 '14 at 18:10
  • @Giorgio That sounds like your requirements are unclear about what it means for two things to be equal. – Donal Fellows Jan 18 '14 at 18:17
  • In general use equals for the one that will be used more often in your program (particularly one that will be used in sets/hashmaps/etc. Really it does sound like you have a design issue here though, consider refactoring your objects so you have a consistent idea of what it means to be equal. – Tim B Jan 18 '14 at 18:18
  • Also note that sometimes what it means for things to be equal can be catastrophically different to what you expect. The classic example is java.net.URL, which compares the host part of a URL according to the IP address that they resolve to. This can have some really odd consequences when you're somewhere with DNS problems (and is in fact entirely wrong for HTTP/HTTPS urls). Beware of equality! – Donal Fellows Jan 18 '14 at 18:20
  • Thank you both: you are probably right: I should split the class into two classes, each with its own equals implementation. – Giorgio Jan 18 '14 at 18:22
0

References which are held for the purpose of identifying objects should reference-compared. References which are held for the purpose of encapsulating the state of an object should be 'deeply' compared. Collections, however, pose a problem in that the use of items in the collection is dependent not upon the collection itself, but upon the thing holding a reference to the collection. Collections are generally instantiated by code which knows how the contents will be used, but Framework collections rarely provide any means for making such a distinction (there are a few types like IdentitySet which clearly specify that the keys encapsulate identity, but none of the types distinguish their contents well enough to allow equals or clone to operate at the proper semantic level).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.