java.lang.Object
is the ancestor
of all objects; it’s the primordial class from which all other classes are
ultimately derived. Methods defined in Object
are, therefore, very important because
they appear in every instance of every class, throughout all of Java. At
last count, there were nine public methods and two protected methods in
Object
. Five of these are versions of
wait()
and notify()
that are used to synchronize threads on
object instances, as we’ll discuss in Chapter 9.
The remaining four methods are used for basic comparison, conversion, and
administration.
Every object has a toString()
method that
can be called when it’s to be represented as a text value. PrintStream
objects use toString()
to print data, as discussed in Chapter 12. toString()
is also used implicitly when an
object is referenced in a string concatenation. Here are some
examples:
MyObj
myObject
=
new
MyObj
();
Answer
theAnswer
=
new
Answer
();
System
.
out
.
println
(
myObject
);
String
s
=
"The answer is: "
+
theAnswer
;
To be friendly, a new kind of object can override toString()
and implement its own version that
provides appropriate information about itself. This is particularly
helpful in debugging, where it is common to print the string value of an
object to see what is going on. Two other methods, equals()
and hashCode()
, may also require specialization when
you create a new class.
equals()
determines
whether two objects are equivalent. Precisely what that means for a
particular class is something that you’ll have to decide for yourself.
Two String
objects, for example, are
considered equivalent if they hold precisely the same characters in the
same sequence:
String
userName
=
"Joe"
;
...
if
(
userName
.
equals
(
suspectName
)
)
arrest
(
userName
);
Using equals()
is not the same as the “==” operator in
Java:
if
(
userName
==
suspectName
)
// Wrong!
This statement tests whether the two reference variables, userName
and suspectName
, refer to the same object. It is a
test for identity, not equality. Two variables that
are identical (point to the same object) will necessarily be equal, but
the converse is not always true. It is possible in Java to construct two
String
objects with the same contents
that are, nonetheless, different instances of the String
class—although, as we’ll describe
later, Java tries to help you avoid that when it can.
A class should override the equals()
method if it needs to implement its
own notion of equality. If you have no need to compare objects of a
particular class, you don’t necessarily need to override equals()
.
Watch out for accidentally overloading equals()
if you mean to override it. With
overloading, the method signatures differ; with overriding, they must be
the same. The equals()
method
signature specifies an Object
argument so that an object can be compared to any other kind of object,
not only those of its own class type. You’ll probably want to consider
only objects of the same type for equivalence. But in order to override
(not overload) equals()
, the method
must specify its argument to be an Object
.
Here’s an example of correctly overriding an equals()
method in class Shoes
with an equals()
method in subclass Sneakers
. Using its own method, a Sneakers
object can compare itself with any
other object:
class
Sneakers
extends
Shoes
{
public
boolean
equals
(
Object
arg
)
{
if
(
(
arg
!=
null
)
&&
(
arg
instanceof
Sneakers
)
)
{
// compare arg with this object to check equivalence
// If comparison is okay...
return
true
;
}
return
false
;
}
...
}
If we specified public boolean
equals(Sneakers arg) ...
in the Sneakers
class, we’d overload the equals()
method instead of overriding it. If
the other object happens to be assigned to a non-Sneakers
variable, the method signature won’t
match. The result: superclass Shoes
’s
implementation of equals()
is called,
which may or may not be what you intended.
The hashCode()
method
returns an integer that is a hashcode for the object. A hashcode is like a signature or
checksum for an object; it’s a random-looking identifying number that is
usually generated from the contents of the object. The hashcode should
always be different for instances of the class that contain different
data, but should be the same for instances that compare “equal” with the
equals()
method. Hashcodes are used
in the process of storing objects in a Hashtable
or a similar
kind of collection. (A Hashtable
is
sometimes called a dictionary or associative array in other languages.)
A random distribution of the hashcode values helps the Hashtable
optimize its storage of objects by
serving as an identifier for distributing them into storage evenly and
quickly locating them later.
The default implementation of hashCode()
in Object
does not really implement this scheme.
Instead it assigns each object instance a unique number. If you don’t
override this method when you create a subclass, each instance of your
class will have a unique hashcode. This is sufficient for some objects.
However, if your classes have a notion of equivalent objects (if you
have overridden equals()
) and you
want equal objects to serve as equivalent keys in a Hashtable
, you should override hashCode()
so that your equivalent objects
generate the same hashcode value. We’ll return to the topic of hashcodes in more detail in Chapter 11 when we discuss the Hashtable
and HashMap
classes.
Objects can use the clone()
method of the Object
class to make copies of themselves. A
copied object is a new object instance, separate from the original. It
may or may not contain exactly the same state (the same instance
variable values) as the original; that is controlled by the object being
copied. Just as important, the decision as to whether the object allows
itself to be cloned at all is up to the object.
The Java Object
class provides
the mechanism to make a simple copy of an object including all of its
“shallow” state—a bitwise copy. But by default,
this capability is turned off. (We’ll show why in a moment.) To make
itself cloneable, an object must implement the java.lang.Cloneable
interface. This is a flag interface indicating to Java that the object
wants to cooperate in being cloned (the interface does not actually
contain any methods). If the object isn’t cloneable, the clone()
method throws a CloneNotSupportedException
.
clone()
is a protected method,
so by default it can be called only by an object on itself, an object in
the same package, or another object of the same type or a subtype. If we
want to make an object cloneable by everyone, we have to override its
clone()
method and make it
public.
Here is a simple, cloneable class—Sheep
:
import
java.util.HashMap
;
public
class
Sheep
implements
Cloneable
{
HashMap
flock
=
new
HashMap
();
public
Object
clone
()
{
try
{
return
super
.
clone
();
}
catch
(
CloneNotSupportedException
e
)
{
throw
new
Error
(
"This should never happen because we implement Cloneable!"
);
}
}
}
Sheep
has one instance
variable, a HashMap
called flock
(which the sheep uses to keep track of
its fellow sheep). Our class implements the Cloneable
interface, indicating that it is OK
to copy Sheep
, and it has overridden
the clone()
method to make it public.
Our clone()
simply returns the object
created by the superclass’s clone()
method—a copy of our Sheep
.
Unfortunately, the compiler is not smart enough to figure out that the
object we’re cloning will never throw the CloneNotSupportedException
, so we have to
guard against it anyway. Our sheep is now cloneable. We can make copies
like so:
Sheep
one
=
new
Sheep
();
Sheep
anotherOne
=
(
Sheep
)
one
.
clone
();
The cast is necessary here because the return type of clone()
is Object
. We can do better by changing the
return type of the overridden clone()
method in the subclass and moving the cast into the clone()
method itself, to make things a little
easier on the users of the class:
public
Sheep
clone
()
{
try
{
return
(
Sheep
)
super
.
clone
();
}
catch
(
CloneNotSupportedException
e
)
{
throw
new
Error
(
"This should never happen!"
);
}
}
// usage
Sheep
one
=
new
Sheep
();
Sheep
anotherOne
=
one
.
clone
();
In either case, we now have two sheep instead of one. A properly
implemented equals()
method would
tell us that the sheep are equivalent, but ==
tells us that they are, in fact, two
distinct instances of Sheep
. Java has made a
shallow copy of our Sheep
. What’s so shallow about it? Java has
simply copied the values of our variables. That means that the flock
instance variable in each of our
Sheep
still holds the same
information—that is, both sheep have a reference to the same HashMap
. The situation looks like that shown
in Figure 7-1.
This may or may not be what you intended. If we instead want our
Sheep
to have separate copies of its
full state (or something in between), we can take control ourselves. In
the following example, DeepSheep
, we
implement a deep copy, duplicating our own flock
variable:
public
class
DeepSheep
implements
Cloneable
{
HashMap
flock
=
new
HashMap
();
public
DeepSheep
clone
()
{
try
{
DeepSheep
copy
=
(
DeepSheep
)
super
.
clone
();
copy
.
flock
=
(
HashMap
)
flock
.
clone
();
return
copy
;
}
catch
(
CloneNotSupportedException
e
)
{
throw
new
Error
(
"This should never happen!"
);
}
}
}
Our clone()
method now clones
the HashMap
as well. Now, when a
DeepSheep
is cloned, the situation
looks more like that shown in Figure 7-2.
Each DeepSheep
now has its own
full copy of the map, which can contain different elements. You can see
now why objects are not cloneable by default. It would make no sense to
assume that all objects can be sensibly duplicated with a shallow copy.
Likewise, it makes no sense to assume that a deep copy is necessary, or
even correct. In this case, we probably don’t need a deep copy; the
flock contains the same members no matter which sheep you’re looking at,
so there’s no need to copy the HashMap
. But the decision depends on the
object itself and its requirements.
The last method of Object
we
need to discuss is getClass()
. This
method returns a reference to the Class
object that produced the Object
instance. We’ll talk about it
next.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.