Effective Java Review (90 Part Series)
1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
… 86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances
Today we are starting a new chapter. This new chapter covers methods common to all objects. And what are methods that are common to all objects? Well since every object eventually inherits from Object
it would be methods on that object. The method we have a pleasure to talk about today is the equals
method.
The equals method seems simple to override but it is actually easy to get ourselves into trouble with it as Effective Java details. So why must we override the equals
method? Well actually we don’t have to. If your class fulfills any of the following requirement there is no need to override the equals
method:
- Each instance of a class is inherently unique.
Thread
for example doesn’t make sense to have two instances be equals. - If there is no desire for two objects of the type to be compared. This does seem risky in that if others are consuming your class you can’t know for sure if they will ever want to compare two instances of the class.
- The superclass implements an
equals
method that is appropriate for the class. Examples of this are the implementation ofequals
inAbstractList
thus none needed inArrayList
. - If the class is private or package-private and you can be sure that equals will never be called on the class.
- If the class uses instance control. This is something like the singleton model we talked about in a previous item or enums where there is only a single instance. In these cases logical equality is the same as instance equality.
So we have gone over times that we can avoid overriding the equals
function. But what must we do if we do need to overwrite this function. Well we must meet the contract of the function. To meet this function’s contract we must meet the four properties for non-null objects:
-
Reflexive: An object should be equal to itself (
x.equals(x) = true
) -
Symmetric: If
x.equals(y) = true
, theny.equals(x)
must equal true as well. (And if the former returns false, the latter should as well) -
Transitive: As an extension of the symmetric property. If
x.equals(y) = true
andy.equals(z) = true
thenx.equals(z)
must equal true. -
Consistent: Multiple invocations of
x.equals(y)
should consistently return the same result. Thus there shouldn’t be any side effects of the execution. -
The final item, less so a property, is that for any non null
x
.x.equals(null)
should equal false.
The above properties can appear pretty daunting and very mathematical when you first read them and they should be taken seriously. That being said it is fairly straightforward once you learn about them.
Reflexive – This first property is quite straightforward. Objects should be equal to themselves. This is usually quite easy to accomplish especially given not overriding the equals
function we do get this property for free.
Symmetry – The next property is more meaty. This one says that if x
says it is equals to y
, y
should also say it’s equal to x
. Let’s look at an example of class that does not meet this property.
public final class CaseInsensitiveString {
private final String value;
public CaseInsensitiveString(String value) {
this.value = Objects.requireNonNull(value);
}
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString) {
return value.equalsIgnoreCase((CaseInsensitiveString)o);
} else if (o instanceof String) {
// This breaks symmetry.
return value.equalsIgnoreCase(o);
}
return false;
}
}
Enter fullscreen mode Exit fullscreen mode
In the above class we see that if a String
is passed into the CaseInsentiveString
object in order to check equality we check against a case-less string. However if a string was compared with a case-less string it would take into account the case of the string. Thus since caseInsensitiveString.equals(string) is not necessarily equals to string.equals(caseInsensitiveString)
. So how would we fix the above implementation? By simplifying it:
@Override
public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString)o).value.equalsIgnoreCase(o);
}
Enter fullscreen mode Exit fullscreen mode
Transitive This is where things get really fun. The transitive property says that if a.equals(b)
and b.equals(c)
then it should mean that a.equals(c)
. So let’s see at an example of how this property can be broken. Consider the following class:
public class Animal {
private final int numberOfLegs;
public Animal(int numberOfLegs) {
this.numberOfLegs = numberOfLegs;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Animal)) {
return false;
}
return ((Animal)o).numberOfLegs == numberOfLegs;
}
}
Enter fullscreen mode Exit fullscreen mode
So as you can see we have a very simple (and super contrived) example class to hold animals where apparently the way we determine if two animals are the same are if they have the same number of legs. Now let’s extend this class:
public class Dog extends Animal {
private final String breed;
public Dog(String breed, int numberOfLegs) {
super(numberOfLegs);
this.breed = breed;
}
}
Enter fullscreen mode Exit fullscreen mode
Okay, so we added one value to this class on top of what Animal
already had. How should we write the equals function? Let’s look at one attempt:
@Override
public boolean equals(Object o) {
if(!(o instanceof Dog)) {
return false;
}
Dog dog = (Dog) o;
return super.equals(dog) && breed.equals(dog.breed);
}
Enter fullscreen mode Exit fullscreen mode
So unsurprisingly we are asserting that one dog is equal to another if it has the same amount of legs and is the same breed. Seems simple enough, but do you see what property we may have broken? We actually broke symmetry. Let’s see how. Consider the following two object.
Animal animal = new Animal(4);
Dog pitbull = new Dog("Pitbull", 4);
Enter fullscreen mode Exit fullscreen mode
So if we have had animal.equals(pitbull)
it would return true
however if we flipped it and executed pitbull.equals(animal)
it would return false
. Whoops. Maybe if we made the equals function a little smarter we can fix this.
@Override
public boolean equals(Object o) {
if(!(o instanceof Animal)) {
return false;
}
if(!(o instanceof Dog)) {
return o.equals(this)
}
Dog dog = (Dog) o;
return super.equals(dog) && breed.equals(dog.breed);
}
Enter fullscreen mode Exit fullscreen mode
Great! We fixed our symmetry problem but how did we do with our transitive property?
Dog pitbull = new Dog("Pitbull", 4);
Animal animal = new Animal(4);
Dog basset = new Dog("Basset", 4);
Enter fullscreen mode Exit fullscreen mode
In this case pitbull.equals(animal)
would equal true
and animal.equals(basset)
equals true but pitbull.equals(basset)
does not equal true
. Uh oh. So what to do? How do we fix this? Well it turns out this problem is not really fixable. That’s right. What we have just witnessed here is a fundamental problem with these equivalence relationships in object-oriented languages. One suggested way to fix this issue that is sometimes suggested is to simply use getClass()
instead of the instanceof
checks. This has the effect of only allowing equivalence if the implementing classes are of the same type. This however does violate the Liskov Substitution Principle and breaks some concepts of object-oriented design. The Liskov Substitution Principle simply states that an object of a subtype type should be able to replace any existence of one of it’s parent’s types. The method that Effective Java pitches is to favor composition over inheritance which is a tip later in the book. The high level synopsis of this technique is, instead of inheriting the type, you simple hold an instance of that type allowing better control of how the different pieces of data can be used and then we don’t get into trouble with the Liskov substitution principle.
Consistency: This bring us to consistency. This one basically comes down to not relying on unreliable resources. So don’t use random number generators as part of your equality check.
Non-nullality: This last one is pretty easy. If someone passes in null
to an equals
function, just return false
and don’t throw a NullPointerException
.
Finally let’s go over some steps for a high quality equals
implementation according to Effective Java.
- Use the
==
operator to check if the objects are the same reference. This is a nice performance optimization. - Use the
instanceof
operator to make sure you were given an object of the correct type and also to handle theNon-nullality
requirement. - Cast your object to the correct type.
- For each “significant” field of the class check the equality. For primitive outside of
float
andDouble
(which you should useFloat.compare
andDouble.compare()
respectively) check equality with==
. For reference types use recursiveequals()
calls. To avoid NullPointerExceptions consider usingObjects.equals
to make these comparisons. Other things to think about are if you can compare cheaper fields before the more expensive fields. - Always override
hashCode
if you overrideequals
. - Don’t try to be too clever
- Make sure you are meeting the override correctly. It’s
public boolean equals(Object o)
notpublic boolean equals(MyType o)
. This is one of the reason using the@Override
annotation is useful.
Wow that was a lot more work than you may have initially considered, it definitely was for me. So is it worth it? Well once you violate the equals
contract there is no knowing how other objects will act when dealing with objects of your class. This is especially visible when using your class within collections.
So is there an easy button? There kind of is. As has been talked about before, Lombok is a great tool for Java development and getting rid of boilerplate. Lombok has a great annotation @EqualsAndHashCode
. I would highly suggest using it rather than writing it yourself. IDEs also often have tools built in that help generate these methods. All this auto-generation is great but I do still think it’s important to know what makes a good equals function, plus it helps you become a better developer and understand more fully how the magic gets made.
So what are your experiences with the equals
method? Any horror stories? Any weird bugs? Let us know in the comments.
Effective Java Review (90 Part Series)
1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
… 86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances
暂无评论内容