Avoid equals
(New page: '''Avoid equals()''' This is not a widely known maxim; I haven't seen it documented anywhere else, but I think it is used by others. My normal design practice is to make sure I construct...) |
BenMcDonald (Talk | contribs) |
||
Line 16: | Line 16: | ||
* When real-world domain objects can be considered equal, then implementing ''equals()'' makes sense, e.g. when the objects represent some kind of measurement, such as a ''Location'' class that contains ''latitude'' and ''longitude'' coordinates. If the location of a ship turns out to be equal to the location of an iceberg, the ship sinks. Similarly, it makes sense for two ''Set''s to be equal if they contain the same things. | * When real-world domain objects can be considered equal, then implementing ''equals()'' makes sense, e.g. when the objects represent some kind of measurement, such as a ''Location'' class that contains ''latitude'' and ''longitude'' coordinates. If the location of a ship turns out to be equal to the location of an iceberg, the ship sinks. Similarly, it makes sense for two ''Set''s to be equal if they contain the same things. | ||
+ | |||
+ | * In the [[Flyweight]] design pattern, the identity tests will not work as expected without an equals override. | ||
== Rationale == | == Rationale == |
Revision as of 23:10, 21 July 2009
Avoid equals()
This is not a widely known maxim; I haven't seen it documented anywhere else, but I think it is used by others.
My normal design practice is to make sure I construct a unique object per concept. For example, to model a real-world student, I construct one Student object, and refer to it wherever the program needs to use that student.
A central getStudent() method (maybe in class University) is the only place that calls the Student() constructor. It keeps a collection of all previously constructed students (the relationship from University to Student) and looks in it for an existing Student before constructing a new Student.
This principle is named "Avoid equals", because for me, the need to write equals() is usually the flag that indicates I need to rethink my design.
This idea is supported by a Composition hierarchy.
Exceptions
- Immutable objects don't cause problems with equals(). String is an example -- you can't change a String, so having identical copies is OK. (Even so, the Java compiler actually re-uses identical Strings using a process called interning.)
- When real-world domain objects can be considered equal, then implementing equals() makes sense, e.g. when the objects represent some kind of measurement, such as a Location class that contains latitude and longitude coordinates. If the location of a ship turns out to be equal to the location of an iceberg, the ship sinks. Similarly, it makes sense for two Sets to be equal if they contain the same things.
- In the Flyweight design pattern, the identity tests will not work as expected without an equals override.
Rationale
The reason for this rule is the same reason that database designers avoid redundant data. As soon as you have two copies of the same information, you risk changing it in one place and having the copies get out of synch.
Immutable objects can be cloned without risk because they can't get out of synch, and equals() is then valuable, as long as a sensible Equals idiom is followed.
Functional programming takes this idea all the way: everything becomes immutable, and equals() may be used with gay abandon. Some OO designers prefer the ideology of avoiding mutability where possible, and are consequently less wary of equals().
BTW, I'm not averse to calling equals(), just to implementing it. The default behaviour of equals() is to compare references (==), and that's what I want. That means that I'm happy that Collections call equals() and get the default behaviour.