The Object
SuperclassS2C Home « The Object
Superclass
We finish the section by looking at the Object
superclass, the 'daddy' of all classes. To explain what we mean by the last sentence, we need to first look back to the first Java class we wrote on the site,
the Hello.java
class:
public class Hello {
public static void main (String[] args) {
System.out.println("Hello World");
}
}
Unbeknown to us, this simple program was implicitly extended by the compiler, in fact every program we write has an implicit superclass called Object
. How can this be though?
Looking back to earlier lessons in the section how can our Garbage
class extend Truck
and Object
, we know we can only extend one class. Well the compiler
just travels up the inheritance tree and tries to extend our Truck
class. The Truck
class extends the Vehicle
class so the compiler travels up the tree again. The Vehicle
class extends nothing so the compiler implicitly extends the Vehicle
class with Object
. What this means for us is that we get to inherit the methods of Object
which we will talk
about as we go through this lesson. For now, we just need to know that we always inherit from Object
at the top of any hierarchy we create. Before we look at the methods of the Object
class
lets list some points about Object
:
- Any class that doesn't implicitly extend another class, is implicitly extended by the compiler to extend
Object
. Object
is a concrete class so we can create objects of typeObject
.- Every class inherits the non-final methods of
Object
and these methods can be overridden. - We can use
Object
as a polymorphic type for methods that need to work with objects of any type.- So if we can use
Object
as a polymorphic type, why not useObject
with all our methods, parameters and return types? There are two very good reasons not to do this:- Java is a strongly typed language and as such cares about the type of the variables we declare. As we know whenever we declare a variable in Java, we have to give it a type, or the compiler complains when
we come to compile the code. If we made every parameter and return type
Object
bang goes our type-safety. Instead of catching errors at compile time, we may run code for months before we get a runtime error, or even worse not notice an error at all, not a pleasant prospect. - Inheritance, remember this only works up the inheritance tree as explained in inheritance method invocation. So if we made everything reference
Object
, what happens when we try to use theload()
method from ourVehicle
classes on theObject
reference type? The compiler doesn't allow it asObject
knows nothing of subclass methods unless they have been overridden. Therefore the only methods that can be called onObject
are its own methods, or overrides thereof.
- Java is a strongly typed language and as such cares about the type of the variables we declare. As we know whenever we declare a variable in Java, we have to give it a type, or the compiler complains when
we come to compile the code. If we made every parameter and return type
Object
reference type polymorphically, and on occasions this is exactly what we want to do, it comes with a price. - So if we can use
Object
Methods OverviewTop
The table below shows the declarations of all the methods in the Object
class:
Method Declaration | Description |
---|---|
Discussed in this lesson | |
public boolean equals(Object obj) | Indicates whether another object is equal to the invoking object. |
protected void finalize() | Called by the garbage collector on an object before that object is discarded. |
public final Class<? extends Object> getClass() | Obtains and returns the runtime class of an object. The <? extends Object> syntax is an example of a generic type which is discussed in the Generics section. |
public int hashCode() | Returns the hash code value associated with the object. |
public String toString() | Returns a string description of the object. |
Discussed in the Concurrency Section | |
public final void notify() | Wakes up a single thread that is waiting on the invoking object's lock. |
public final void notifyAll() | Wakes up all threads that are waiting on the invoking object's lock. |
public final void wait() | Current thread is put into a wait state until another thread invokes the notify() or notifyAll() method for this object. |
public final void wait(long timeout) | Current thread is put into a wait state until either another thread invokes the notify() or notifyAll() method for this object, or a specified amount of time has elapsed. |
public final void wait(long timeout, int nanos) | Current thread is put into a wait state until another thread invokes the notify() or notifyAll() method for this object, or a specified amount of time has elapsed, or a certain amount of real time has elapsed. |
Not discussed | |
protected Object clone() | Creates and returns a shallow copy of this object. |
We will go through the first four methods in much greater detail as we go through this lesson. Use the links in the table above to go the Concurrency section for more information on the other Object
methods
we discuss on the site. For more information on the clone()
method follow the link below.
Java DocumentationTop
As mentioned in the first two sections Java comes with very rich documentation. The following link will take you to the online version of documentation for the JavaTM 2 Platform Standard Edition 5.0 API Specification.
Take a look at the documentation for the Object
class which you can find by scrolling down the lower left pane and clicking on Object. You will go back to this documentation time and time again so if you haven't done so already I suggest adding this link to your browser's favourites toolbar for fast access.
The finalize()
MethodTop
- The
finalize()
method is only ever invoked once, for any given object, by the JVM.
The finalize()
method of the Object
class is called by the garbage collector on an object before that object is discarded. The finalize method in the Object
class doesn't execute any specific tasks, but gives us the opportunity to override. We may want to do this to clear up any resources held by an object such as closing a file used by it.
We have no control over when the garbage collector runs as this is in the control of the JVM. The garbage collector runs its collection efficiently and this means it usually runs when there are a lot of objects to discard.
Overriding finalize()
Top
To see an overridden finalize()
method in action we are going to have to instantiate a lot of objects and then make these objects eligible for garbage collection:
/*
Class to show an overridden finalize() method in action
*/
public class Finalize {
private int i;
Finalize(int i) {
this.i = i;
}
/*
Instantiate a Finalize object and immediately descope it
*/
public void CreateFinalize(int i) {
Finalize f = new Finalize(i);
}
/*
Overridden finalize() method
*/
protected void finalize() {
System.out.println("The finalize() method called on object: " + i);
}
/*
You may need to change the iterator to get this to work!
*/
public static void main (String[] args) {
Finalize f = new Finalize(0);
for (int i=1; i < 300000; i++) {
f.CreateFinalize(i);
}
}
}
Save, compile and run the Finalize
class in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our Finalize
class on my machine. You may have to increase the iterator within the for
loop to get it to work.
The getClass()
MethodTop
The getClass()
method of the Object
class obtains and returns the runtime class of an object, or put another way the object that was instantiated.
/*
Test Class for the getClass() method
*/
public class GetClassTest {
public static void main (String[] args) {
Vehicle car = new Car();
System.out.println(car.getClass());
Bus bus = new Bus();
System.out.println(bus.getClass());
}
}
Save, compile and run the file in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our ATest
test class. As you can see the method returns the class of the instantiated object.
The toString()
MethodTop
The toString()
method of the Object
class returns a string description of the object which consists of the name of the class, the @
symbol and the unsigned hexadecimal
representation of the hash code of the object. The official documentation from Oracle encourages us to override this method in our subclasses, which as we now know, are all subclasses of Object
. Before we
create an override lets look at the output of the toString()
method we inherit from Object
:
/*
Test Class for the Object toString() method
*/
public class ToStringTest {
public static void main (String[] args) {
Carrier carrier = new Carrier(10, 20);
System.out.println(carrier.toString());
}
}
Save, compile and run the file in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our ToStringTest
test class. As you can see the method returns the class of the instantiated object, the @
symbol and a number, not very
meaningful.
Overriding toString()
Top
What we need to do is override the Object toString()
method in our Carrier
class to give us some more information about the object state of instances we create:
/*
A Carrier class
*/
public class Carrier {
...
/*
A Carrier class
*/
public String toString() {
String strVal = "[" + this.people + ", " + this.cost + "]";
}
}
After adding the toString
override to the Carrier
class, save and compile it and rerun the ToStringTest
test class in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our ToStringTest
test class. We have overridden the Object toString()
method in our Carrier
class with something more meaningful to the class in question.
Checking Object EqualityTop
The equals()
method of the Object
class indicates whether another object is equal to the invoking object by using the ==
relational operator. What the method is actually
doing is checking the bits in both reference variables and returning a boolean
value. Therefore the equals()
method of the Object
class only ever returns true
when the
reference variables refer to the same object. Following is the contract specified in the Oracle documentation for the equals()
method of the Object
class:
- It is reflexive: for any non-null reference value
x
,x.equals(x)
should returntrue
. - It is symmetric: for any non-null reference values
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
. - It is transitive: for any non-null reference values
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
. - It is consistent: for any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used in equals comparisons on the objects is modified. - For any non-null reference value
x
,x.equals(null)
should returnfalse
.
The equals()
MethodTop
To illustrate usage of the equals()
method of the Object
class we will make a new TestEquals
class:
/*
A TestEquals class
*/
public class TestEquals {
public static void main(String[] args) {
Carrier a = new Carrier(10, 20);
Carrier b = new Carrier(10, 20);
Carrier c = a;
System.out.println("Is a = b? " + a.equals(b));
System.out.println("Is a = c? " + a.equals(c));
System.out.println("Is b = c? " + b.equals(c));
}
}
Save, compile and run the TestEquals
class in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our TestEquals
test class. Although the instances referenced by a
and b
have exactly the same object state they are not the
same object and so the equals()
method of the Object
class returns false
. The c
reference is initialized to refer to a
and therefore refers to the same
object reference and as such the equals()
method of the Object
class returns true
.
Overriding equals()
Top
But what if want to think of two objects as equal if part of their object state is shared? Well then we need to override the equals()
method of the Object
class in our own
classes. To demonstrate how to do this we will make a new Contractor
class where we will consider objects equal if they have the same name and location as part of their object state:
/*
A Contractor class
*/
public class Contractor {
private String name = ""; // The Contractor Name.
private String location = ""; // City Location.
private String owner = ""; // The Customer Id.
Contractor() {
}
Contractor(String name, String location, String owner) {
this.name = name;
this.location = location;
this.owner = owner;
}
public boolean equals(Object obj) {
if (this==obj) { // Same reference variable so equal
return true;
}
if (!(obj instanceof Contractor)) // Make sure we have a Contractor instance
return false;
Contractor other = (Contractor) obj; // We need to cast to Contractor for comparison
if (location == null) { // Compare locations
if (other.location != null)
return false;
} else if (!location.equals(other.location))
return false;
if (name == null) { // Compare names
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
Lets go through our equals()
override. The first thing we do is check for the same object using the ==
relational operator, if they are, we don't need to do anymore and return
true
. The object we want to check against is passed in polymorphically as type Object
and so we need to make sure it is indeed a Contractor
object and we use the
instanceof
operator to do this. Once we know we have the correct object type coming in we can cast it to the Contractor
type for
comparison. We then do checks on each property to make sure they are equal using the equals()
method of String
which is overridden in that class already. If all tests pass for both, then we know
these two objects have the same state for the fields we are interested in and return true
.
Save and compile the Contractor
class in directory c:\_OOConcepts in the usual way.
We should test our class to make sure the equals()
override is working as intended:
/*
A ContractorTest class
*/
public class ContractorTest {
public static void main(String[] args) {
Contractor a = new Contractor();
Contractor b = new Contractor();
Contractor c = new Contractor("Contractor A", "Essex", "1234");
Contractor d = new Contractor("Contractor B", "Essex", "1234");
Contractor e = new Contractor("Contractor A", "Essex", "5678");
System.out.println("Is a = b? " + a.equals(b));
System.out.println("Is a = c? " + a.equals(c));
System.out.println("Is c = d? " + c.equals(d));
System.out.println("Is c = e? " + c.equals(e));
System.out.println("Is d = e? " + d.equals(e));
}
}
Save, compile and run the ContractorTest
class in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our ContractorTest
test class. Although the instances referenced by a
and b
have exactly the same object state they are not the
same object and so the equals()
method of the Object
class returns false
. The first test returns true
as we haven't initialized any instance variables in a
and b
and so name and location are both set to null
in both objects and so are indeed equal. The only other test that returns true
is the fourth test where we compare c
and e
which both have the same name and location in both objects and so are equal. All other tests return false
which is what we want so our override works correctly.
The hashCode()
MethodTop
The hashCode()
method returns the hash code value associated with the object. Following is the contract specified in the Oracle documentation for the hashCode()
method of the Object
class:
- Whenever it is invoked on the same object more than once during an execution of a Java application, the
hashCode()
method must consistently return the same integer, provided no information used inequals()
comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application. - If two objects are equal according to the
equals(Object)
method, then calling thehashCode()
method on each of the two objects must produce the same integer result. - It is not required that if two objects are unequal according to the
equals(Object)
method, then calling thehashCode()
method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.
From this contract we can see that the hashCode()
method of the Object
class, relies on the equals()
method of the Object
class. What this means to us is whenever we
override the equals
method of the Object
class, we should also override the hashCode()
method to keep the general contract for the hashCode()
method,
which states that equal objects must have equal hash codes. We can tabularize the association between the equals
and hashCode()
contracts to make understanding easier:
Condition | Mandatory | Not Mandatory But Possible |
---|---|---|
From the equals() viewpoint | ||
if x.equals(y) returns true | x.hashCode() == y.hashCode() | |
if x.equals(y) returns false | No hashCode() requirement | |
From the hashCode() viewpoint | ||
x.hashCode() == y.hashCode() | x.equals(y) returns true | |
x.hashCode() != y.hashCode() | x.equals(y) returns false |
So it looks like our Contractor
class is breaking this contract. To see this we need to update our ContractorTest
class to output the hashcodes for each object and check them against this table:
/*
A ContractorTest class
*/
public class ContractorTest {
public static void main(String[] args) {
Contractor a = new Contractor();
Contractor b = new Contractor();
Contractor c = new Contractor("Contractor A", "Essex", "1234");
Contractor d = new Contractor("Contractor B", "Essex", "1234");
Contractor e = new Contractor("Contractor A", "Essex", "5678");
System.out.println("Is a = b? " + a.equals(b));
System.out.println("Is a = c? " + a.equals(c));
System.out.println("Is c = d? " + c.equals(d));
System.out.println("Is c = e? " + c.equals(e));
System.out.println("Is d = e? " + d.equals(e));
System.out.println("The hashcode of a: = " + a.hashCode());
System.out.println("The hashcode of b: = " + b.hashCode());
System.out.println("The hashcode of c: = " + c.hashCode());
System.out.println("The hashcode of d: = " + d.hashCode());
System.out.println("The hashcode of e: = " + e.hashCode());
}
}
Save, compile and run the ContractorTest
class in directory c:\_OOConcepts in the usual way.
The above screenshot shows the output of running our ContractorTest
test class. We can clearly see from this that we have broken the hashCode
contract. The hash codes of a
and
b
should be the same as should those for c
and e
.
Overriding hashCode()
Top
Because we have overridden the equals()
method of Object
, we also need to override the hashCode()
method. Add the new hashCode()
override
below to the Contractor
class:
/*
A Contractor class
*/
public class Contractor {
...
public int hashCode() {
String hashKey = name + location;
return hashKey.hashCode();
}
}
Lets go through our hashCode()
override. We are concatenating the name and location instance variables we used for our equals()
override into a
String
object. We then use the hashCode()
method of the String
class to convert the string to a hashcode and return it. By using the same variables we can be assured that the
returned hashcode will be equal, when our equals()
override returns true
.
Add our hashCode()
override to the Contractor
class and save and compile the Contractor
class in directory c:\_OOConcepts in the usual way.
We should test our class to make sure the hashCode()
override now honours its contract and is working as intended. So rerun the ContractorTest
test class.
The above screenshot shows the output of running our ContractorTest
test class. We can see that the hashcodes for a
and b
are the same, as are those for c
and
e
. The contract for our hashCode()
override is now fine.
Lesson 8 Complete
We finished the section on OO Concepts by looking at the Object object, the daddy of all classes.
What's Next?
In the next section we finish our studies of flow control by looking at exceptions and assertions.