Case Study: A Point Class
Let us consider the case of writing a class for keeping track of points
in the plane with integer coördinates. Here is a bare-bones class for doing
this. Note that it creates immutable objects. (You can add any getters you want)
Place this in a file named Point.java
.
public class Point
{
private final int x;
private final int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public Point()
{
this(0, 0);
}
@Override
public String toString()
{
return String.format("(%s, %s)", x, y);
}
}
Uh Oh!
Compile your code using javac
to make sure you have not typos. This
being done, let's inspect it in jshell
.
jshell> /open Point.java
jshell> Point p = new Point(3,4);
p ==> (3, 4)
jshell> Point q = new Point(3,4);
q ==> (3, 4)
jshell> p.equals(q)
$4 ==> false
Well this ain't fair! Our two object have identical state, yet Java says that they
are unequal. This is because, by default, the .equals()
method in Java
tests for equality of identity. Python fans, this means that it is behaving
just like Python's is
operator.
This same scheme inheres in Python. If you do not implement the dunder method
__eq__
in your class, the ==
operator behaves just like
is
. To check for equality of Points
, we must implement a
.equals()
methods.
Common NOOB Mistake
To properly implement equals()
, the correct signature
is public boolean equals(Object o)
, not public
boolean equals(Point p)
. If you use the highly-recommended
@Override
annotation, this error would be flagged by the
compiler.
Implementation
Here are the key steps to implementing this method.
- If we are comparing ourself to ourself, return
true
. - If we are comparing ourself to something that is not a
Point
, returnfalse
. This is called the species test. - Once you are at this point, you are guaranteed that the object
o
is, in fact, aPoint
. Now cast it to aPoint
. Do not be afraid. - Finally, check that the
Point
we are comparing ourselves to has the same coördinates that we have.
Let's go.
Step 1 compare the object to ourself. Notice that ==
in Java
compares for equality of identity. So if you are the same as the object you are
being compared to, return true
.
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
}
Step 2 Perform the species test using the instanaceof
operator. Notice the inner parentheses; they are needed.
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof Point))
{
return false;
}
}
Step 3 Perform the cast that is guaranteed to work.
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(!(o instanceof Point))
{
return false;
}
Point that = (Point) o;
}
Step 4 Check for equality of state.
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if((!o instanceof Point))
{
return false;
}
Point that = (Point) o;
return x == that.x && y == that.y;
}
Now let's inspect in jshell
.
jshell> /open Point.java
jshell> Point p = new Point(3,4);
p ==> (3, 4)
jshell> Point q = new Point(3,4);
q ==> (3, 4)
jshell> Point r = new Point();
r ==> (0, 0)
jshell> p.equals(q)
$5 ==> true
jshell> p.equals(r)
$6 ==> false
jshell> p.equals("Hey I am a string
| Error:
| unclosed string literal
| p.equals("Hey I am a string
| ^
jshell> p.equals("Hey I am a string");
$7 ==> false
Why the Object o in the equals method?
Take a look in ArrayList
's documentation. This is a parameterized (generic)
class with type parameter E
Your Point
class can be used as
an entry type like so.
ArrayList<Point> points = new ArrayList<>();
It has a method called contains
. Here is the text
from the API page.
public boolean contains(Object o)
Returns true if this list contains the specified element. More formally, returns true if and only if this list contains at least one element e such that Objects.equals(o, e).
Specified by:
contains
in interface Collection<E>
Specified by:
contains
in interface List<E>
Overrides:
contains
in class AbstractCollection<E>
Parameters:
o
- element whose presence in this list is to be tested
Returns: true if this list contains the specified element
The contains
method uses the equals()
method of the
class of the entries E
This method accepts as an argument an Object
,
not just an E
.
A variable of type Object
can point at any Java object. Take note of this
jshell
session.
jshell> /open Point.java
jshell> Point p = new Point(3,4);
p ==> (3, 4)
jshell> Object o = new Point(3,4);
o ==> (3, 4)
jshell> ArrayList<Point> points = new ArrayList<>();
points ==> []
jshell> points.add(p);
$6 ==> true
jshell> points.contains(o);
$7 ==> true
The complete program can be downloaded from the navigation area on the left.