(Level: Beginner)
When you're starting out with
Java, chances are you'll be writing classes pretty quickly. This page provides you with a way to do it that is easy to remember and repeat.
It is intended for beginners - especially ones who are writing their first projects away from the classroom - but students who are a bit further along may also find some useful tips.
It
won't look like the classes you've seen in your books, so you might want to read the
CAVEAT section at the end to find out why.
In particular, it shows you an approach to writing
equals() that you probably won't have seen before. Don't worry, it's not wildly complicated; but it
does take a bit of explaining
so make sure you have your thinking cap on when you read the section.
Once you've written a few classes, you'll be familiar with the
pattern. You'll probably also know a bit more, so you won't need to keep to it slavishly.
It's quite long, so don't feel you need to read it all in one go. Just take a section at a time - especially the one on
the rules of equals() - and
make sure you understand what's going on before you continue to the next one.
.
NOTE: It is
NOT intended as a substitute for reading, or following a proper tutorial or class.
.
Prerequisites
At the very least, you are assumed to have seen some class definitions before and know what constructors and methods look like, and also to have seen some basic assignments and method calls; otherwise the code will probably look like gobbledygook.
You should also familiarize yourself with the APIs for the
Object,
String and
Integer classes before you go too far.
INTRODUCTION
For the purposes of this tutorial, the class we're creating is called
Person, and will contain two fields (or 'members'):
name - a Java
String.
age - a Java
int indicating age in years.
.
Methods
There are three important methods to know about when you're writing a class:
equals(Object),
hashCode() and
toString(), and these are the ones we're going to concentrate on.
Since they are all defined in the
Object class, you will be
overriding them; so they should all start with the "
@Override"
Annotation.
I'm also going to introduce you to a fourth method you probably
haven't seen before:
canEqual(Object).
You may have already heard that whenever you write an
equals() method, you should
also write a
hashCode() method - well,
canEqual() is a third method in that list.
Basically, they work as a trio, so think of them as
The Three Musketeers - "All for one, one for all":
If you write one, you MUST write all three; leave one out, leave all three out.
equals() is usually the "driving force" behind this decision; the other two methods simply make it work properly.
For now, take it as read that you should ALWAYS include a
toString() method.
.
Constructors
These look like methods, and are used to create new objects (ie, instances of a class) when the
new keyword is specified.
You can write
many of these, but when you're starting out, you will probably be getting input in
String form quite often (probably from a
Scanner), so I'm going to concentrate on two in particular:
Person(fields) and
Person(strings).
Constructors
always have the same name as their class.
.
Factory Methods
These are an alternate way of creating objects; and in many ways are superior to constructors. Indeed, many experienced developers use them to
hide constructors from clients.
In general, they are
static and
final, and convention says that they should usually be called
valueOf(...
) for a value class like
Integer. For example:
effectively does the same thing as:
but actually does it slightly better (I'll leave you to find out how

). Note the use of the class name in the first call, because the method is
static.
For more "informational" classes - like ours - there is no real 'set' name, although you will often see
instanceOf(...
) or
newInstance(...
) used if there is nothing better, or if the class only has one factory method. My advice:
make it as descriptive as you can.
We shall be creating three; however, since they don't really change the basic structure of our class, I have put them in a separate page called
FirstFactoryMethods.
KICKOFF
Right, we're ready to start writing our new class. In general, you'll want it to be visible to everyone, which means that it should be
public. This, in turn, means that it must be put in a file called
Person.java (note the spelling; Java is
case-sensitive).
So let's start with the definition:
Fairly straightforward: The "
class" keyword says that we're defining a class, and the "
final" keyword says that, at least for the moment, we don't intend to subclass (or "extend") it.
And that "at least for the moment" is very important:
The great thing about Java classes is that you can
change them. In fact, it's one of the reasons they were invented. But some things - specifically: visibility (public/private etc.), extendability, and immutability (see
below) -
can only be changed in ONE direction.
Also: extendability is a decision you
don't want to take lightly (although it might not seem so from your lessons so far). As
Joshua Bloch says in his
wonderful book (Item 17):
"Design and document for inheritance, or else
prohibit it".
final prohibits it ...
at least for the moment.
So:
Tip #1 (and you won't find this in too many books):
Unless you're defining an abstract class, make it "
final"
by default.
Obviously, if you
know you're going to extend it, you can leave it off; but very often you won't - and in those cases,
put it in.
You can always remove it later if you discover that you
do want to subclass;
but you can't ADD it.
Bloch freely admits that two of the classes
he wrote -
BigInteger and
BigDecimal -
should have been final. Unfortunately, it's too late to change now without possibly breaking existing code.
.
Another thing: Looking at that definition, you might think that your
Person class exists in a vacuum.
Well, it
doesn't - it
extends Object -
ALL Java classes do. So:
Tip #2:
Unless you're defining a subclass (in which case you'll be extending something else), include the fact that you're extending Object in your definition, viz:
Indeed, I know many experienced programmers who follow this practice religiously.
FIELDS
OK, let's add in those fields:
.
Tip #3:
ALWAYS, ALWAYS, ALWAYS make your fields private.
It's a primary rule of Object-Orientation:
Never expose internal fields (or logic) of your class to anyone.
.
The
final is optional, but I always put it in - even after 12 years of writing Java - because I don't want anyone changing my fields
unless I say they can.
And that's what
final does on a field - it says: "this field can only be set ONCE".
Basically, it makes your class
immutable (look it up

), which is actually
no bad thing. So:
Tip #4:
Get into the habit of putting "
final"
on your fields by default.
This is basically the same tip as for classes. If you
know you're going to need to change a field, by all means leave it off; but if you don't - and you should think very carefully before you DO allow fields to be changed -
put it in.
As above, you can always remove it later on if you find it too restrictive; but you
can't ADD it.
Person(fields) constructor
This is a constructor you will almost always want - a way to create an object given initial values for all its fields. In our case we have two, so it will look something like this:
The "
this" is important, because it allows the compiler to distinguish between the
class field called
name, and the
parameter field called
name. If you leave it out, it would simply assign the
parameter field to itself (because it's the closest definition), which effectively does nothing at all.
In fact, I would advise putting "
this." in front of
all your class field references.
However, there's something missing from the above code. Can you see what it is?
.
Based on what we've written, all of the following constructions would be perfectly valid:
But do we really want a Person with no name? Or one who is 1043 years old?
Tip #5:
Validate ALL information coming into constructors and public methods; and do it as quickly as possible.
and that last part is a basic design maxim that is well worth remembering:
Fail fast, and fail LOUD.
Simply put: if something goes wrong in your program,
you want to know about it straight away; and in a way you can't ignore.
.
The
first of those examples is probably the most important. In Java, Strings are
objects, and ANY object can have a value of
null.
Nulls are usually something you want to avoid, because they have an annoying habit of generating
NullPointerException's (NPEs) down the line; and NPEs can be one of the nastiest errors to try and track down after the fact.
So, back to our rule: "Fail FAST" - and it's
particularly important when it comes to
nulls.
Tip #6:
Eliminate nulls from your program as soon as you find them.
.
As you saw, a String can also be
blank (ie, all spaces) or have no characters in it at all (""), so we should eliminate any "names" that look like that too. Also,
age can't be negative; and we should probably put an upper limit on it - say 125 (since nobody has yet been known to reach that age).
Fortunately,
String has a nice little method called
trim() which saves you a lot of hassle when dealing with "blanks". In fact, if you haven't already, I suggest you take a good look at the String API before you continue much further.
.
So, let's re-write this constructor properly:
For the moment, we're simply saying "if the data is bad, make the program fail" and
IllegalArgumentException is the normal way to indicate a parameter (or "argument") error.
I don't propose to go through the code line by line. If you haven't done it by now, after all my urging, familiarise yourself with the
String API
NOW.
.
Modularity
The only problem with what we've done is that those checks are likely to be needed
whenever we have to deal with a name or an age; and if we leave them where they are, we'll have to copy them wherever they're needed, and
copying code is BAD.
So, lets set up a couple of little checker methods. We'll make them
private -
at least for the moment - since only our class is likely to need them; and they can also be
static since the validation doesn't depend on any particular instance of our class, viz:
and plugging those into our constructor, we get:
which can also be written as:
I strongly urge you to use this form, because it's a great reminder that the methods you're calling are
static.
Make sure you follow how all this works before you continue, because you will probably want a "validation" method for each field in your class; and they should all follow the same pattern:
The signature should be:
You can choose a prefix other than "valid" if you want, but be consistent about it.
Validate the supplied field.Return the supplied field if it passes all the tests.
And that last point is a good one to remember:
Methods should usually return values.
Obviously, there are exceptions, such as
main() and "setter" methods, but in general, avoid writing methods with a return type of
void.
.
And why the "
final"?
Tip #7:
Make your methods "final" by default.
Many will tell you that it's redundant for instance (non-
static) methods; especially when your class is
final or the method is
private. I disagree:
It stops anyone from accidentally overriding your methods.If you ever remove the final from the class definition and/or change the visibility, your methods remain final until YOU decide otherwise.The compiler may be able to optimize your code better. On its own, it's a bad reason to do it; but in this case it doesn't do any harm, and it may be an added bonus.It's just a good habit to get into. If you don't start now, you may forget to do it when it really DOES make a difference.
And, as with all the other cases we've seen, you can always remove it later on if you find it too restrictive; but you
can't ADD it.
Think of it this way - You (the developer) are the gaoler: classes, fields and methods are your prisoners; and "
final" is the key. If you think one of your lags is worthy, set them free;
but get it wrong, and repent at leisure, because once they're out,
you can't get 'em back.
Having said all that, the four methods that we're going to write probably
shouldn't be
final (
Sod's Law 
) because they're
meant to be overridden. There's absolutely
nothing wrong with putting it on them as well, but you may well find that you have to remove it later. Our examples will leave it out.
.
A look at the
complete class listing near the end may help to put all the stuff in this section into context.
.
One final thing (you probably thought I'd forgotten about it

):
What's that "
super()" statement all about?
What it says is: "call our superclass constructor" - and our superclass is
Object, remember?
The reason that you may not have seen it is that if you don't put it in
the compiler will add "
super()"
(and specifically that) for you silently.
Well,
don't let it.
For one thing,
it may be the wrong constructor.
Object has a constructor that takes no arguments (indeed, it's its
only constructor), but not all classes do - ours, for one.
So:
Tip #8:
Always include a specific call to super(...
) in your constructors.
And it must be
the first statement.
Person(strings)
This is the constructor you will need to convert "String-based" input (eg, from a
Scanner or a text file) to a
Person object. I'm surprised how many books leave it out, since you will almost always need it for the first few months that you're writing Java classes (and probably a lot longer than that).
.
Like
Person(fields), it is going to need input for each field in our class. The only difference being that ALL of them will be Strings; so in our case, it will look like this:
Obviously, if all the fields in your class are already Strings, you don't have to write it; however, the chances of that happening are pretty slim.
Or they darn well should be. If they
are all Strings,
look at them carefully, because there's a good chance that they're defined badly.
Strings should
NOT be used to store:
NumbersDatesPostcodesColoursTelephone numbers
or indeed, anything that has a predefined format, or a "meaning" beyond the simple characters themselves.
Indeed, it's arguable that even "
name" isn't a simple String, because it has a specific format - in the Western world: first name + " " + family name, and possibly some initials - but the way we've defined it, someone could just supply "XXX" and we'd never know it was bad.
However, that's a lesson for another day

. For our purposes, a String will do just fine. Check out the
StringsAreBad page for more details if you're interested.
.
OK, let's write it:
.
Blimey. That was quick.
Why?
Because we've already done most of the work.
Our
Person(fields) constructor already
knows how to convert and validate a name. It also knows how to validate an
age value, so all we need to do is to call it with the right types.
And that's what the
this(...
) statement does: it's very similar to the
super(...
) statement you saw earlier, except that it calls one of OUR constructors, rather than a superclass one.
It's called "constructor chaining" and, as you can see, it's very useful. The only thing to remember is that, like
super(...
), it must be the
first statement in the constructor.
.
And just to complete the explanation:
Integer.parseInt(...
) converts a String to an
int, and
age.trim(), as you saw above, removes spaces from the start and end of the supplied
age String - just in case they were supplied by accident.
It's a bit of a mouthful, but unfortunately necessary, because
this(...
) MUST be the
first statement, so something like:
won't work, even though it's arguably a lot clearer.
Hopefully, it also shows you why it's good for methods to return values:
parseInt(...
) uses the
result of
trim(), and in turn returns a result itself, which is used by
this(...
). This sort of "stacking" is very common, but it
only works if methods return values.
.
Again, I strongly urge you to get familiar with the APIs of
String and
Integer before you go any further.
toString()
This is probably the 2nd most important method to know about when writing a Java class, because it allows you to print out the contents of objects to the console (very useful when you're debugging).
If you
don't write your own, you will get the one that
Object provides for you, which will produce something like:
ie, not very useful. So:
Tip #9:
ALWAYS write a toString() method for your classes.
.
OK, so what do we want our "Person string" to look like?
A lot of beginners get obsessed with producing the most descriptive output they can, because they're thinking about exactly how it's going to look in some application they're writing.
My advice: DON'T.
The method is for
your class, not some application; and furthermore, it's primary use is as a
debugging aid, not as a vehicle for producing "fancy output". So
keep it simple.
That said, a good
toString() method should:
Output ALL of the fields in your class in text form.Use a format that can be easily added to. A simple one is comma-delimited text, eg:
although it may not be suitable for all types of data.
.
"But what if one of my fields is a JPEG?" I hear you cry.
If it is, it will certainly be an object, so you can call
its toString() method. And if the designer of your JPEG object didn't bother to write one? You'll probably get:
and you have my full permission to roast their reproductive organs over an open fire.
Seriously though, most designers of objects that aren't meant for text output will usually have the foresight to provide something that makes sense, eg:
where "
some URL" is the URL that contains the image.
For those that are interested,
the bit before the "
:" is the
MIME type for a JPEG according to
RFC 2046. And that illustrates another point:
Tip #10:
UseExistingStandards.
The link contains a few that you may find useful.
.
Going back to our format just for a second; I have to admit a slight preference for
semicolon-delimited text; simply because commas often occur naturally in Strings - certainly more often than semicolons. For example, if our
name field used a slightly different - but commonly-used - format:
the semicolon still allows us to separate "fields" visually:
.
Tip #11 (a common rookie mistake):
Don't include 'bookends' like "
[...
]"
or "
{...
}"
in your strings.
Why? Because it makes them harder to
add to, which, as you'll see
later, is quite important.
.
So, getting back on track with our method, we'll use semicolon-delimited text output, viz:
Notice the "
@Override"?
toString() is actually defined in
Object, so we're
overriding it.
Tip #12:
Remember to include "
@Override"
on ALL methods you override.
.
Otherwise: pretty simple, eh?
.
Not so fast, kemosabe. We're not quite finished yet. Check out the
Subclasses section for the rest of the story.
canEqual(Object)
This is that "new" method I told you about earlier; and you will need it whenever you write an
equals() method - the
Three Musketeers, remember?
Luckily, it's dead simple:
protected boolean canEqual(
Object other) {
return other instanceof Person;
}[/code]
.
That "
Object" is important because it will be called by our
equals() method.
DON'T write:
Note also:
We don't use "@Override", because it's not overriding anything (more's the pity).It's "protected", NOT "public".
It's there for our class and its subclasses; NOT for general use.
.
The other thing to note is the use of "
instanceof",
which is a
type check. Specifically:
If "other" is a Person, it returns true.If "other" is a subclass of Person, it also returns true.
otherwise, it returns
false -
and that includes two very important cases:
"other" is a superclass of Person."other" is null.
Remember this well, because it's important: The comparison works
downwards.
.
The only other thing to remember is that
whenever you write this method, the class after "
instanceof"
must be the
current one. More formally:
For any class C that includes it, canEqual() MUST look precisely as follows:
protected boolean canEqual(
Object other) {
return other instanceof
C;
}[/code]
It's also an exception to our
Tip 7 above: It should
not be
final because it's
meant to be overridden.
.
For now, that's it.
canEqual() checks if the supplied object "
other" is the same class as ours
or a subclass,
but it's designed to be overridden. More on that later.
equals(Object)
Finally, we come to the most important method you will ever learn about, because it allows you to compare your object with others to see if they are "equal", which is something you're going to need to do a LOT.
The actual meaning of "equal" will depend very much on the class you're writing, but usually it means that all of the fields in the object we're trying to compare with have the same values as ours.
Unfortunately, it's not quite as simple as that. In fact:
Writing a proper equals() method is NOT simple.
According to
this article by Martin Odersky et al:
"
...after studying a large body of Java code, the authors of a 2007 paper concluded that almost all implementations of equals() are faulty"
and I can well believe it.
.
However, we're fearless, right?
Let's have a first squint:
.
The first thing to notice is what it looks like:
That "
Object" is VERY important. One of the most common mistakes beginners make is to write:
which is WRONG, because then you aren't actually overriding
Object's equals() method, which is the whole point of the exercise.
The "
@Override" annotation makes sure that you can't accidentally make that mistake, because if you do the compiler will complain. So
don't forget Tip #12.
If you don't put it in, you won't get any errors because the 2nd signature is a perfectly valid method -
just not the one we want - and you'll probably only realise your mistake when your class starts behaving oddly.
.
Getting back to what "equals" means just for a second: it's worth noting that there are two basic definitions:
The two objects being compared are identical - ie, they are the same object.The two objects being compared contain the same information.
For the first, you can use '==', so the only one that concerns us when we're writing
equals() is the
second one and, as you'll see, it's
much more important.
If you don't write it, you will get
Object's definition, which is basically the same as '==' - ie,
not very useful.
.
OK, now we're ready to write our method:
Remember this pattern (apart from the deliberate mistake of course

) because, until you get much more advanced,
every equals() method you ever write should look similar.
.
Let's have a look at those steps in detail:
Check if other is the same object as this one - If it is, they MUST be equal, so we might as well return true straight away.Check if other is the same type as this one - If it isn't, they CAN'T be equal, so we might as well return false straight away.Cast other to our type - We need to do this because other is an Object, not a Person; and the check in Step 2 ensures that we now know that it is, in fact, a real Person object (or a subclass, in which case the cast is still fine).Check if this object is the same type as other. Now that probably sounds like the same check as Step 2, but it isn't - instanceof is directional, remember?Compare fields for equality - Did you spot the deliberate mistake? The check in Step 1 might give you a clue.
.
Basically, you shouldn't use '==' with objects because, as already stated,
it will only return true if the two objects being compared are the same object.
name is a
String, which is an
object, so:
will only return
true if
this.name is
the same String object as
that.name.
So what do we want instead?
which can be shortened to:
(at my age, I like to save typing).
.
So, putting all that together, we get:
.
So, why are we doing that same check (
canEqual()) in both directions?
Unfortunately, the explanation is quite involved; so, before we go into
that, let's have a look at the final method we're going to need...
hashCode()
This is probably the hardest method for beginners to get their heads around, but it's absolutely essential.
Whenever you write an equals() method, you MUST write a compatible hashCode() method - The
Three Musketeers, remember?
Right: So, what is a "hashcode"?
For Java purposes, it's a number that indicates when two objects
of the same class are
different; and since this is the flip-side to
equals(), it's important that the two methods are compatible.
The rules are detailed in
Object.hashCode(), but basically there's only one you need to remember:
If x.equals(y) returns true, then both x.hashCode() and y.hashCode() MUST return the same value.
And it causes no end of confusion, because most beginners assume that the above implies that if two objects are
different, they must return
different hashcodes.
NOT TRUE ... I'll say that a second time:
NOT TRUE.
The following is a perfectly legal (and
correct)
hashCode() method:
it's just not very useful.
The fact is that you
can't ensure that different objects will always produce different hashcodes, but the idea is that a good
hashCode() method
will do so as much as possible.
And this is where things get tricky. There's a lot to know about writing good
hashCode() methods, and a lot of very clever people have worked on the problem.
Luckily, you don't have to worry about it.
If you're on version 7 or above (and if you're not; why not?), there is a great new class called
Objects that contains a lovely
hash() method, which takes all the effort out of generating good hashes. Just call it with every field you use for comparison in your
equals() method. So in our case this would be:
If you're not yet on version 7, but on version 5 or later you can do something very similar by calling
Arrays.hashCode(), viz:
which can be shortened to:
(
Arrays is another class well worth getting to know)
If you're not on version 5, you'll have to do it
the hard way - and it serves you right.
For the complete class, I'm going to assume that you're running version 7.
.
And just in case you missed it in passing:
Your hashCode() calculation must include ALL fields used in your equals() comparison, and ONLY those fields.
It's all part of ensuring that the two methods are in sync.
.
As with the previous methods, we're not quite finished yet. Check out the
Subclasses section for more information.
THE RULES OF equals()
OK. Before we finish, we're going to re-visit
equals() once again.
Remember we did a
canEqual() check in both directions? Well, the reasons for that have to do with the
rules for an
equals() method, of which there are 5:
It must be reflexive. It must be symmetric. It must be transitive. It must be consistent. x.equals(null) must return false (assuming it doesn't throw an NPE).
These are explained in detail in
Object.equals() and, as you can see, they're quite involved.
And so is this explanation - so
put on your thinking cap on before you continue.
.
The two main ones to know about are numbers 2 and 3 (#1 was dealt with by our
Step 1 check). Lets have a look at #2:
It must be symmetric: basically what this means is that:
.
Now that may seem obvious, but it's remarkably easy to get wrong, because we're calling
two different methods:
x.equals(y) calls
x's method, and
y.equals(x) calls
y's.
If the two objects are unrelated, then our
Step 2 check is sufficient. For example, if
x is a
Person and
y is a
String, then
x.equals(y) will call
our method and fail on Step 2, because "
other" (
y) is not a
Person.
For
y.equals(x), we have to assume that
String's
equals() method contains a similar check to make sure that what is passed to it is a String. If it doesn't, the designer didn't do his job properly, and there's very little we can do about
that (it does, BTW

).
.
But what if
y is a
subclass of
x? Let's just suppose for a moment that
Person isn't final, and that someone has written a
RichGuy subclass that redefines
equals() -
and so has also done the same thing for canEqual() -
The Three Musketeers, remember?
x.equals(y) will call our method, but this time Step 2 will
pass, since "
other" (
y) is a
RichGuy, and
RichGuy is a
subclass of
Person.
So, we continue on to
Step 4:
that.canEqual(this).
Now we'll be calling
RichGuy's
canEqual() method, which should look like this:
and, since in this case "
other" (
x) is merely a lowly
Person,
that check will fail.
Remember: until the revolution comes,
equals() is
class-conscious 
.
And we'll deal with the
y.equals(x) part of this in the
Subclasses section below.
.
"So", I hear you ask, "does that mean that two classes in the
same hierarchy CAN'T be the same if one of them redefines
equals()?"
YES
"But why can't we just compare them on the basis of their superclass? Surely a
Person with a the same name and age as a
RichGuy could be considered equal, whichever way we compare them?"
A very good question.
.
Which brings us onto that rule #3:
It must be transitive: and what this means is that, for any three objects, it must be circularly consistent - ie:
.
So, let's consider three objects: a
RichGuy (
x), a
Person (
y), and another
RichGuy (
z), whose names and ages are ALL
the same (Joe Bloggs, age: 47).
The trouble is,
RichGuy's are
status-conscious (which is probably why we had to write the stupid
equals() method for them to begin with):
A millionaire is NOT the same thing as a
billionaire - at least to them - "different order of breed don't you know".
So
RichGuy's
equals() method will contain some comparison of
net worth.
Now let's further say that our first
RichGuy (
x) really is a billionaire, but our second (
z) is merely a
millionaire.
Do you see where this is going?
.
If we compare based on superclass stuff (name and age) when two objects are
different, we can't suddenly abandon it when they're
the same; because then:
x (our billionaire RichGuy) would equal y (our Person) - same name and age.y would equal z (our millionaire RichGuy) - same name and age.
BUT:
x would NOT equal z - same name and age, but different net worths.
So
transitivity is the reason; and let's just say it one more time:
Objects of two different classes in the same hierarchy CANNOT be equal if one of those classes redefines equals().
.
Actually,
as with almost everything in programming, that's not quite true

. There
are ways of doing mixed-type comparisons while still observing the "transitive" rule, but they get
very involved - much
too involved for a tutorial like this.
The methodology you've been given is enough to serve you for a while; and far superior to
some others you may see.
.
PHEW.
Do you see now what I was talking about earlier?
equals() is
NOT simple - especially when class hierarchies get involved.
.
And before we put this to bed completely, it's probably worth mentioning one other thing:
Unless you know how a superclass does it's equals() check - and sometimes even then - you CANNOT
add a subclass to an existing hierarchy AND
redefine its equals() method.
It's just a fact of life. Basically, you're at the mercy of the writer of the superclass.
The methodology in this tutorial
does allow you to add classes and redefine
equals() - with the restrictions already mentioned -
but only for hierarchies written the same way.
And I'm afraid that's all you can reasonably hope for.
.
There is a LOT to know about writing
equals() methods - believe me, we've only scratched the surface here - but for now,
remember the pattern you saw earlier, because pretty much every one you write should look similar.
You should also check the
Subclasses section, because we're not quite finished yet...
The Complete Class
Quite a lot to take in, eh? Here's our final class without all the woffle and, as you'll see, it's nowhere near as big as you probably thought.
I've swapped the methods around a bit according to the way I like to see them - constructors first,
private methods next, then
protected, then
public; with methods arranged in alphabetical sequence - but you're free to choose any order that makes sense to you. Just
be consistent about it:
.
.
You're now ready to add anything else that takes your fancy. And if it seems like a lot of code right now, believe me, it's
peanuts compared to what you
will write.
We're not quite done yet, though...
SUBCLASSES
Up to now, we've assumed that our
Person class is a
top-level class (ie, it
extends Object); but what if the one that you're defining isn't?
.
Let's suppose that we've decided that we
do want to allow
Person to be subclassed, and so we've removed the
final keyword from its class definition.
We now want to define an
Employee class that extends
Person, and adds a
salary field. The definition will initially look like this:
Note that we don't need to include
Person's fields, because they're implicit in the
extends Person statement. Now that may seem obvious, but it implies something else too:
We now have an active superclass and, unless we want to repeat all of its code, we need some way to re-use it.
And to do that, you'll need to become familiar with a new keyword:
super.
You already saw it in the
Person(fields) constructor, but now we're going to use it as a
reference.
"
super" is a bit like "
this", but refers specifically to the "superclass part" of our object; so when we write something like "
super.equals(...
)", what we're actually saying is: "call the
equals() method defined for our
superclass;
NOT ours".
.
Just to be consistent, let's add a validation method for our
salary field:
.
Now, let's deal with each case in turn:
.
Employee(fields)
This is pretty straightforward. The main thing to remember is that since
Employee is a
subclass of
Person, we also need to include
its fields, viz:
The only thing worthy of note here is that
super(...
) call:
Employee is a subclass, so it will be calling the
Person(fields) constructor. Do you see why I told you to
always include that call now? If we'd left it up to the compiler, it would have inserted:
which
doesn't exist (
Person doesn't
have a
Person() constructor).
So you'd get a compiler error
for code you didn't write.
.
Employee(strings)
Our "string-conversion" constructor. Again, this is pretty straightforward:
The only difference here is that the
super(name, age) call is now calling the
Person(strings) constructor, because both
name and
age are Strings.
Hopefully, the rest is familiar by now.
.
canEqual(Object)
As you saw earlier, overriding this method is incredibly simple. In our case:
Remember the "
@Override" annotation, because now we
are overriding; and remember also that it takes an
Object, not an
Employee, and that it's "
protected", NOT "
public". Unfortunately, the compiler can't warn you if you get that last bit wrong, so you just need to remember it.
Tip #13:
Don't make methods public unless they need to be.
.
equals()
If your class is part of a hierarchy, you'll have to include a new Step, viz:
Why? Because hopefully, the person who wrote the superclass to yours (in this case, us) has
also read this tutorial, and has hidden its contents
even from you.
Therefore, the only way to
know if the superclass portion of your object is "equal" is to call
its equals() method. If it returns false, then obviously the two objects can't be "equal", so we may as well return
false straight away.
.
Moreover, we can
replace Steps
2 AND
4 with our new Step, viz:
Hunh? How does that work? We haven't written any
this.canEqual(other) check, so how can we suddenly cast
other to an
Employee?
.
The answer is that
super.equals(other) calls
Person's
equals() method, and
Step 2 in
that method calls OUR
canEqual() method -
that's how overriding works - so it can't possibly return
true unless
other is an
Employee (or a subclass).
And it doesn't matter how many classes there are between us and
Person because -
providing they are ALL written the same way -
super.equals() must eventually call
Person's method.
Furthermore, we
also know that
other is NOT a
subclass of
Employee that redefines
equals(), since it also has to pass
Step 4; so we can be sure that our
equals() method will also follow the
'transitivity' rule.
So in fact,
equals() for a subclass is actually
simpler than for a top-level one.
Nice, eh?
.
The main thing to remember is that this pattern relies on two things:
ALL subclass equals() methods follow it.The Three Musketeers rule - Specifically: if you override equals(), you MUST override canEqual(); if you don't, leave it out.
.
Note that we
can't do this for a top-level class like
Person, because
Object's
equals() method only returns
true if the
this and
other are
the same object (ie, it behaves like '==').
.
hashCode()
The only thing to remember here is that, for a subclass,
you must include the superclass's hashcode in the calculation of yours. Just think of it as one of your class's "fields".
So, if you're on version 7 this would be:
Otherwise, on version 5 or later:
Note that, as with
equals(), we
can't do this for a top-level class like
Person, because
Object's
hashCode() method returns a different value for each object,
regardless of content.
.
toString()
As you can probably imagine, this is similar to the previous two. Maybe you can see why we chose semicolon-delimited output now - because it's
easy to add to, viz:
which will return a String like:
and, as before, we can't do this for a top-level class like
Person, because we'd get:
The complete Employee Class
So here's what a
subclass looks like without all the woffle:
.
.
As you can see, quite similar to our
Person class. You just need to remember those "
super" calls.
OTHER STUFF
In describing the methodology, we've glossed over some stuff that needs to be mentioned; and, as you can imagine, most of it has to do with our old friend:
equals()
Type checking
There
is an alternative way to write
Step 2 of our method.
Instead of:
you can use:
It all has to do with your definition of a "type". The version I gave you allows you to compare a Person with a
subclass of a Person - providing it doesn't redefine
equals() of course - the alternative above
doesn't.
Obviously, I prefer the version I gave you; but you may well see the alternative used in some books. The arguments for each style are probably too advanced to go into at the moment, but a few in favour of the alternative are:
It's simple.It doesn't require a canEqual() method.It's logically SAFE (remember all those rules?)
If
other is not
specifically a
Person -
even if it's a subclass - the method returns
false.
This safety comes at the price of flexibility (and some might say "objectivity") however; and I'm a great believer in the old quote of Einstein's (paraphrased):
"
Everything should be as simple as possible, but no simpler."
.
Consistency
Remember that
Rule #4 - "it must be
consistent"?
What that means is that
x.equals(y) must
consistently return the same value every time it's called,
unless a field used in the equals() comparison has changed. And
hashCode() has a very similar rule.
Unfortunately, it manifests itself with hashed collections like
HashSet and
HashMap.
.
Let's assume, just for a moment, that we allowed clients to
change the
salary of an
Employee. We might end up with a case like this:
everything fine so far. Now we give Joe a raise:
and check him again:
Hunh? What just happened? We didn't remove Joe from the Set, so what's going on?
.
ALL collections in the Java Collections Framework (well, except for
one 
) use
equals() to determine whether an object exists or not.
The trouble is, hashed collections ALSO use
hashCode() to place objects in internal areas called "buckets" - it's one of the things that makes them so darn fast. The idea is that when you do a search, a
HashSet only has to check the bucket where it put the object to see if there is one in there that is "equal".
Unfortunately, now that we've changed Joe's salary, his
hashCode() probably
won't be the same as it was when we added him, which means that we'll now be looking for him in
the wrong bucket. Even worse: on rare occasions, we might just be "lucky" with our new hashcode, and our
HashSet will find Joe; so we can't even rely on the search failing. And believe me, when you're trying to debug,
inconsistency is
far worse than failure.
Do you see now why I suggested that you make ALL your fields
final?
Hashed collections and mutability
just don't mix. And there are other cases where changing the contents of an object can make
equals() and/or
hashCode() behave oddly too, so think very carefully before you allow it.
.
Documentation
You may have noticed that one thing conspicuously absent from our class
listing is any sort of documentation. That's because we've been concentrating on the
content, but it's definitely NOT something I'd advise in general.
One of the most important aspects of writing good classes is
good documentation. Furthermore, Java has a wonderful tool called
javadoc, which takes most of the effort out of producing it, and is also where you get all those nice
API pages from.
Just to give you a quick taster, here's how our
Person.equals() method might look with a few
javadoc comments:
which will produce output similar (but not identical) to
this.
And, other than writing the comments themselves, the only thing you have to do is run the
javadoc command. Notice how you can also mix in HTML to get lists, paragraphs and different fonts if you want.
A detailed review of the tool is beyond the scope of this tutorial, but I strongly urge you to read its
howto manual before you write too many classes; and
start using it as a matter of course.
LESSONS
This is just a re-hash of some of the things already mentioned:
Always, always, always make your fields private.Fail fast, and fail LOUD.Check ALL parameter values supplied to methods and constructors - especially nulls.Always put an explicit "super(...)" or "this(...)" call in line 1 of your constructors.Put "this." in front of all class field references.Write all your equals() methods the same way (ie, use the steps defined in this tutorial).Write hashCode() and canEqual() methods whenever you override equals() - remember: The Three Musketeers.Use equals(), NOT '==', whenever you're comparing objects.Always write a toString() method, and keep it simple.Make all your classes, fields and methods final by default. You can always remove it later on if you find it too restrictive, but you can't ADD it.
CAVEAT
The methodology detailed in this page creates
immutable,
final classes, which might not look like the ones you've seen in your books or tutorials. This is because they're trying to teach you about aspects of Object-Orientation like inheritance and polymorphism and overriding; and so aren't too worried about "real-world" issues like
making your classes safe.
The fact is that big class hierarchies have rather gone out of fashion, and
composition is often the preferred way of "extending" behaviour; so chances are that a lot of classes you write
will be
final (but not all

).
What I've tried to do is show you a bulletproof approach for writing a
real class that is easy to follow, but that you can depart from if you need to (probably by taking out
final when you KNOW it isn't applicable). And, as I said before, that's the great thing about Java classes:
you can change them if you need to - but
only if you start out the right way.
I've also deliberately left out all sorts of things, like "getter" and "setter" methods, that you can find out about elsewhere. What this tutorial provides you with is a foundation to build on.
Basically, we've only scratched the surface. Now it's time for you to learn some more.
FURTHER READING
How to write an Equality method in Java - Martin Odersky et al, 2009.
Effective Java, version 2 - Joshua Bloch, Addison-Wesley 2008.
The Java Tutorials - Sun Microsystems/Oracle Corporation (
Full index).
How to Write Doc Comments for the Javadoc Tool - Sun Microsystems/Oracle Corporation.
Secrets of equals() - Part 1 - Angelika Langer & Klaus Kreft, 2002.
Part 2 (advanced)
CategoryWinston