Java supports class reuse through inheritance and composition. In this
two-part article we'll focus on inheritance, one of the fundamental
concepts of object-oriented programming. First, you'll learn how to use
the
Inheritance can descend through multiple levels, leading to ever-more-specific categories. As an example, Figure 1 shows car and truck inheriting from vehicle; station wagon inheriting from car; and garbage truck inheriting from truck. Arrows point from more-specific "child" categories (lower down) to less-specific "parent" categories (higher up).
This example illustrates single inheritance in which a child category inherits state and behaviors from one immediate parent category. In contrast, multiple inheritance enables a child category to inherit state and behaviors from two or more immediate parent categories. The hierarchy in Figure 2 illustrates multiple inheritance.
Categories are described by classes. Java supports single inheritance through class extension, in which one class directly inherits accessible fields and methods from another class by extending that class. Java doesn't support multiple inheritance through class extension, however.
Listing 1. The
The
These examples codify is-a relationships:
Child classes inherit accessible fields and methods from their parent classes and other ancestors. They never inherit constructors, however. Instead, child classes declare their own constructors. Furthermore, they can declare their own fields and methods to differentiate them from their parents. Consider Listing 2.
Listing 2. An
Listing 3 presents a
Listing 3. A
Listing 4 further extends
Listing 4. A
Listing 5.
Listing 6. Declaring a
Listing 7. Overriding
To complete this example, I've excerpted a
You can detect an attempt to overload instead of override a method at compile time by prefixing a subclass's method header with the
When you declare a method or field
Java provides a standard class library consisting of thousands of classes and other reference types. Despite the disparity in their capabilities, these types form one massive inheritance hierarchy by directly or indirectly extending the
We'll look at each of these methods and how they enable you to perform special tasks in the context of your Java classes. First, let's consider the basic rules and mechanisms for
Because you can extend at most one other class (recall from Part 1 that Java doesn't support class-based multiple inheritance), you're not forced to explicitly extend
Compile Listing 3 (
Listing 4 also declares a
Compile Listing 4 (
Shallow cloning works well when all fields are of the primitive type and (in many cases) when any reference fields refer to immutable (unchangeable) objects. However, if any referenced objects are mutable, changes made to any one of these objects can be seen by the original object and its clone(s). Listing 5 demonstrates.
Compile Listing 5 (
Compile Listing 6 (
Continuing,
Compile Listing 7 (
Although the
You must override
The
Continuing, the object argument is cast to
Compile Listing 8 (
Compile Listing 9 (
The
The default
When
The
You typically override
The JDK documentation for
Suppose you were to execute the following code fragment, which instantiates
Listing 11's overriding
extends
keyword to derive a child class from a parent
class, invoke parent class constructors and methods, and override
methods. Object
is Java's ultimate superclass, from which every other class inherits.
Relating classes through inheritance
Inheritance is a programming construct that software developers use to establish is-a relationships between categories. Inheritance enables us to derive more-specific categories from more-generic ones. The more-specific category is a kind of the more-generic category. For example, a checking account is a kind of account in which you can make deposits and withdrawals. Similarly, a truck is a kind of vehicle used for hauling large items.Inheritance can descend through multiple levels, leading to ever-more-specific categories. As an example, Figure 1 shows car and truck inheriting from vehicle; station wagon inheriting from car; and garbage truck inheriting from truck. Arrows point from more-specific "child" categories (lower down) to less-specific "parent" categories (higher up).
This example illustrates single inheritance in which a child category inherits state and behaviors from one immediate parent category. In contrast, multiple inheritance enables a child category to inherit state and behaviors from two or more immediate parent categories. The hierarchy in Figure 2 illustrates multiple inheritance.
Categories are described by classes. Java supports single inheritance through class extension, in which one class directly inherits accessible fields and methods from another class by extending that class. Java doesn't support multiple inheritance through class extension, however.
Class extension: Is-a relationships
Java supports class extension via theextends
keyword. When present, extends
specifies a parent-child relationship between two classes. Below I use extends
to establish a relationship between classes Vehicle
and Car
, and then between Account
and SavingsAccount
:
Listing 1. The extends
keyword specifies a parent-child relationship
class Vehicle
{
// member declarations
}
class Car extends Vehicle
{
// inherit accessible members from Vehicle
// provide own member declarations
}
class Account
{
// member declarations
}
class SavingsAccount extends Account
{
// inherit accessible members from Account
// provide own member declarations
}
extends
keyword is specified after the class name and before another class name. The class name before extends
identifies the child and the class name after extends
identifies the parent. It's impossible to specify multiple class names after extends
because Java doesn't support class-based multiple inheritance.
These examples codify is-a relationships:
Car
is a specialized Vehicle
and SavingsAccount
is a specialized Account
. Vehicle
and Account
are known as base classes, parent classes, or superclasses. Car
and SavingsAccount
are known as derived classes, child classes, or subclasses.
Child classes inherit accessible fields and methods from their parent classes and other ancestors. They never inherit constructors, however. Instead, child classes declare their own constructors. Furthermore, they can declare their own fields and methods to differentiate them from their parents. Consider Listing 2.
Listing 2. An Account
parent class declares its own constructor, fields, and methods
class Account
{
private String name;
private long amount;
Account(String name, long amount)
{
this.name = name;
setAmount(amount);
}
void deposit(long amount)
{
this.amount += amount;
}
String getName()
{
return name;
}
long getAmount()
{
return amount;
}
void setAmount(long amount)
{
this.amount = amount;
}
}
Listing 2 describes a generic bank account class that has a name and an
initial amount, which are both set in the constructor. Also, it lets
users make deposits. (You can make withdrawals by depositing negative
amounts of money but we'll ignore this possibility.) Note that the
account name must be set when an account is created.
SavingsAccount
child class that extends its Account
parent class.
Listing 3. A SavingsAccount
child class extends its Account
parent class
class SavingsAccount extends Account
{
SavingsAccount(long amount)
{
super("savings", amount);
}
}
The SavingsAccount
class is trivial because it doesn't need
to declare additional fields or methods. It does, however, declare a
constructor that initializes the fields in its Account
superclass. Initialization happens when Account
's constructor is called via Java's super
keyword, followed by a parenthesized argument list.
Listing 4 further extends
Account
with a CheckingAccount
class.
Listing 4. A CheckingAccount
child class extends its Account
parent class
class CheckingAccount extends Account
{
CheckingAccount(long amount)
{
super("checking", amount);
}
void withdraw(long amount)
{
setAmount(getAmount() - amount);
}
}
CheckingAccount
is a little more substantial than SavingsAccount
because it declares a withdraw()
method. Notice this method's calls to setAmount()
and getAmount
, which CheckingAccount
inherits from Account
. You cannot directly access the amount
field in Account
because this field is declared private
(see Listing 2).
Demonstrating the account class hierarchy
I've created anAccountDemo
application class that lets you try out the Account
class hierarchy. First take a look at AccountDemo
's source code.
Listing 5. AccountDemo
demonstrates the account class hierarchy
class AccountDemo
{
public static void main(String[] args)
{
SavingsAccount sa = new SavingsAccount(10000);
System.out.println("account name: " + sa.getName());
System.out.println("initial amount: " + sa.getAmount());
sa.deposit(5000);
System.out.println("new amount after deposit: " + sa.getAmount());
CheckingAccount ca = new CheckingAccount(20000);
System.out.println("account name: " + ca.getName());
System.out.println("initial amount: " + ca.getAmount());
ca.deposit(6000);
System.out.println("new amount after deposit: " + ca.getAmount());
ca.withdraw(3000);
System.out.println("new amount after withdrawal: " + ca.getAmount());
}
}
The main()
method in Listing 5 first demonstrates SavingsAccount
, then CheckingAccount
. Assuming Account.java
, SavingsAccount.java
, CheckingAccount.java
, and AccountDemo.java
source files are in the same directory, execute either of the following commands to compile all of these source files:
javac AccountDemo.java
javac *.java
Execute the following command to run the application:
java AccountDemo
You should observe the following output:
account name: savings
initial amount: 10000
new amount after deposit: 15000
account name: checking
initial amount: 20000
new amount after deposit: 26000
new amount after withdrawal: 23000
Method overriding
A subclass can override (replace) an inherited method so that the subclass's version of the method is called instead. An overriding method must specify the same name, parameter list, and return type as the method being overridden. To demonstrate, I've declared aprint()
method in the Vehicle
class below.
Listing 6. Declaring a print()
method to be overridden
class Vehicle
{
private String make;
private String model;
private int year;
Vehicle(String make, String model, int year)
{
this.make = make;
this.model = model;
this.year = year;
}
String getMake()
{
return make;
}
String getModel()
{
return model;
}
int getYear()
{
return year;
}
void print()
{
System.out.println("Make: " + make + ", Model: " + model + ", Year: "
+
year);
}
}
Next, I override print()
in the Truck
class.
Listing 7. Overriding print()
in a Truck
subclass
public class Truck extends Vehicle
{
private double tonnage;
Truck(String make, String model, int year, double tonnage)
{
super(make, model, year);
this.tonnage = tonnage;
}
double getTonnage()
{
return tonnage;
}
void print()
{
super.print();
System.out.println("Tonnage: " + tonnage);
}
}
Truck
's print()
method has the same name, return type, and parameter list as Vehicle
's print()
method. Note, too, that Truck
's print()
method first calls Vehicle
's print()
method by prefixing super.
to the method name. It's often a good idea to execute the superclass logic first and then execute the subclass logic.
To complete this example, I've excerpted a
VehicleDemo
class's main()
method:
Truck truck = new Truck("Ford", "F150", 2008, 0.5);
System.out.println("Make = " + truck.getMake());
System.out.println("Model = " + truck.getModel());
System.out.println("Year = " + truck.getYear());
System.out.println("Tonnage = " + truck.getTonnage());
truck.print();
The final line, truck.print();
, calls truck
's print()
method. Note that Truck
's print()
first calls Vehicle
's print()
to output the truck's make, model, and year; then it outputs the truck's tonnage. This portion of the output is shown below:
Make: Ford, Model: F150, Year: 2008
Tonnage: 0.5
Method overloading instead of overriding
Suppose you replaced theprint()
method in Listing 7 with the one below:
void print(String owner)
{
System.out.print("Owner: " + owner);
super.print();
}
The modified Truck
class now has two print()
methods: the preceding explicitly-declared method and the method inherited from Vehicle
. The void print(String owner)
method doesn't override Vehicle
's print()
method. Instead, it overloads it.
You can detect an attempt to overload instead of override a method at compile time by prefixing a subclass's method header with the
@Override
annotation:
@Override
void print(String owner)
{
System.out.print("Owner: " + owner);
super.print();
}
Specifying @Override
tells the compiler that the given
method overrides another method. If someone attempted to overload the
method instead, the compiler would report an error. Without this
annotation, the compiler would not report an error because method
overloading is legal.
Method overriding and protected methods
Java provides theprotected
keyword for use in a method-overriding context. You can also use protected
for fields. This keyword is commonly used to identify methods that are
designed to be overridden, given that not all accessible methods should
be overridden.
When you declare a method or field
protected
, the method or
field is accessible to all of the code within any class that has been
declared in the same package. It's also accessible to subclasses
regardless of their packages.Java provides a standard class library consisting of thousands of classes and other reference types. Despite the disparity in their capabilities, these types form one massive inheritance hierarchy by directly or indirectly extending the
Object
class. This is also true for any classes and other reference types that you create. Exploring the root of all classes
Object
is the root class, or ultimate superclass, of all other Java classes. Stored in the java.lang
package, Object
declares the following methods, which all other classes inherit:
Class<?> getClass()
protected Object clone()
boolean equals(Object obj)
protected void finalize()
int hashCode()
String toString()
void wait()
void wait(long timeout)
void wait(long timeout, int nanos)
void notify()
void notifyAll()
final
. For example, the non-final
toString()
method can be overridden, whereas the final
wait()
methods cannot.
We'll look at each of these methods and how they enable you to perform special tasks in the context of your Java classes. First, let's consider the basic rules and mechanisms for
Object
inheritance.
Extending Object
A class can explicitly extendObject
, as demonstrated in Listing 1.
Listing 1. Explicitly extending Object
public class Employee extends Object
{
private String name;
public Employee(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public static void main(String[] args)
{
Employee emp = new Employee("John Doe");
System.out.println(emp.getName());
}
}
Object
. If you did explicitly extend Object
, you would be unable to extend any other class. Therefore, you would typically extend Object
implicitly, as demonstrated in Listing 2.
Listing 2. Implicitly extending Object
public class Employee
{
private String name;
public Employee(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
public static void main(String[] args)
{
Employee emp = new Employee("John Doe");
System.out.println(emp.getName());
}
}
Compile Listing 1 or Listing 2 as follows:
Information about classes: getClass()
ThegetClass()
method returns the runtime class of the object on which this method is called. The runtime class is represented by a Class
object, which is found in the java.lang
package. Class
is the entry point into the Java Reflection API, which I'll introduce
in a future article. For now, know that a Java application uses Class
and the rest of the Java Reflection API to learn about its own structure.
Object cloning: clone()
Theclone()
method creates and returns a copy of the object on which it's called. Because clone()
's return type is Object
, the object reference that clone()
returns must be cast to the object's actual type before assigning that
reference to a variable of the object's type. Listing 3 presents an
application that demonstrates cloning.
Listing 3. Cloning an object
class CloneDemo implements Cloneable
{
int x;
public static void main(String[] args) throws CloneNotSupportedException
{
CloneDemo cd = new CloneDemo();
cd.x = 5;
System.out.println("cd.x = " + cd.x);
CloneDemo cd2 = (CloneDemo) cd.clone();
System.out.println("cd2.x = " + cd2.x);
}
}
Listing 3's CloneDemo
class implements the Cloneable
interface, which is found in the java.lang
package. Cloneable
is implemented by the class (via the implements
keyword) to prevent Object
's clone()
method from throwing an instance of the CloneNotSupportedException
class (also found in java.lang
).
CloneDemo
declares a single int
-based instance field named x
and a main()
method that exercises this class. main()
is declared with a throws
clause that passes CloneNotSupportedException
up the method-call stack.
main()
first instantiates CloneDemo
and initializes the resulting instance's copy of x
to 5
. It then outputs the instance's x
value and calls clone()
on this instance, casting the returned object to CloneDemo
before storing its reference. Finally, it outputs the clone's x
field value.
Compile Listing 3 (
javac CloneDemo.java
) and run the application (java CloneDemo
). You should observe the following output:
cd.x = 5
cd2.x = 5
Overriding clone()
The previous example didn't need to overrideclone()
because the code that calls clone()
is located in the class being cloned (CloneDemo
). If the call to clone()
were located in a different class, however, then you would need to override clone()
. Because clone()
is declared protected
, you would receive a "clone has protected access in Object"
message if you didn't override it before calling the class. Listing 4
presents a refactored Listing 3 that demonstrates overriding clone()
.
Listing 4. Cloning an object from another class
class Data implements Cloneable
{
int x;
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
class CloneDemo
{
public static void main(String[] args) throws CloneNotSupportedException
{
Data data = new Data();
data.x = 5;
System.out.println("data.x = " + data.x);
Data data2 = (Data) data.clone();
System.out.println("data2.x = " + data2.x);
}
}
Listing 4 declares a Data
class whose instances are to be cloned. Data
implements the Cloneable
interface to prevent a CloneNotSupportedException
from being thrown when the clone()
method is called. It then declares int
-based instance field x
, and overrides the clone()
method. The clone()
method executes super.clone()
to call its superclass's (that is, Object
's) clone()
method. The overriding clone()
method identifies CloneNotSupportedException
in its throws
clause.
Listing 4 also declares a
CloneDemo
class that: instantiates Data
, initializes its instance field, outputs the value of the instance field, clones the Data
object, and outputs its instance field value.
Compile Listing 4 (
javac CloneDemo.java
) and run the application (java CloneDemo
). You should observe the following output:
data.x = 5
data2.x = 5
Shallow cloning
Shallow cloning (also known as shallow copying) refers to duplicating an object's fields without duplicating any objects that might be referenced from that object's reference fields. Listing 3 and Listing 4 actually demonstrated shallow cloning. Each of thecd
-, cd2
-, data
-, and data2
-referenced fields identifies an object that has its own copy of the int
-based x
field.
Shallow cloning works well when all fields are of the primitive type and (in many cases) when any reference fields refer to immutable (unchangeable) objects. However, if any referenced objects are mutable, changes made to any one of these objects can be seen by the original object and its clone(s). Listing 5 demonstrates.
Listing 5. The problem with shallow cloning in a reference field context
class Employee implements Cloneable
{
private String name;
private int age;
private Address address;
Employee(String name, int age, Address address)
{
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
Address getAddress()
{
return address;
}
String getName()
{
return name;
}
int getAge()
{
return age;
}
}
class Address
{
private String city;
Address(String city)
{
this.city = city;
}
String getCity()
{
return city;
}
void setCity(String city)
{
this.city = city;
}
}
class CloneDemo
{
public static void main(String[] args) throws CloneNotSupportedException
{
Employee e = new Employee("John Doe", 49, new Address("Denver"));
System.out.println(e.getName() + ": " + e.getAge() + ": " +
e.getAddress().getCity());
Employee e2 = (Employee) e.clone();
System.out.println(e2.getName() + ": " + e2.getAge() + ": " +
e2.getAddress().getCity());
e.getAddress().setCity("Chicago");
System.out.println(e.getName() + ": " + e.getAge() + ": " +
e.getAddress().getCity());
System.out.println(e2.getName() + ": " + e2.getAge() + ": " +
e2.getAddress().getCity());
}
}
Listing 5 presents Employee
, Address
, and CloneDemo
classes. Employee
declares name
, age
, and address
fields; and is cloneable. Address
declares an address consisting of a city and its instances are mutable. CloneDemo
drives the application.
CloneDemo
's main()
method creates an Employee
object and clones this object. It then changes the city's name in the original Employee
object's address
field. Because both Employee
objects reference the same Address
object, the changed city is seen by both objects.
Compile Listing 5 (
javac CloneDemo.java
) and run this application (java CloneDemo
). You should observe the following output:
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Chicago
Deep cloning
Deep cloning (also known as deep copying) refers to duplicating an object's fields such that any referenced objects are duplicated. Furthermore, the referenced objects of referenced objects are duplicated, and so forth. Listing 6 refactors Listing 5 to demonstrate deep cloning.Listing 6. Deep cloning the address field
class Employee implements Cloneable
{
private String name;
private int age;
private Address address;
Employee(String name, int age, Address address)
{
this.name = name;
this.age = age;
this.address = address;
}
@Override
public Object clone() throws CloneNotSupportedException
{
Employee e = (Employee) super.clone();
e.address = (Address) address.clone();
return e;
}
Address getAddress()
{
return address;
}
String getName()
{
return name;
}
int getAge()
{
return age;
}
}
class Address
{
private String city;
Address(String city)
{
this.city = city;
}
@Override
public Object clone()
{
return new Address(new String(city));
}
String getCity()
{
return city;
}
void setCity(String city)
{
this.city = city;
}
}
class CloneDemo
{
public static void main(String[] args) throws CloneNotSupportedException
{
Employee e = new Employee("John Doe", 49, new Address("Denver"));
System.out.println(e.getName() + ": " + e.getAge() + ": " +
e.getAddress().getCity());
Employee e2 = (Employee) e.clone();
System.out.println(e2.getName() + ": " + e2.getAge() + ": " +
e2.getAddress().getCity());
e.getAddress().setCity("Chicago");
System.out.println(e.getName() + ": " + e.getAge() + ": " +
e.getAddress().getCity());
System.out.println(e2.getName() + ": " + e2.getAge() + ": " +
e2.getAddress().getCity());
}
}
Listing 6 shows that Employee
's clone()
method first calls super.clone()
, which shallowly copies the name
, age
, and address
fields. It then calls clone()
on the address
field to make a duplicate of the referenced Address
object. Address
overrides the clone()
method and reveals a few differences from previous classes that override this method:Address
doesn't implementCloneable
. It's not necessary because onlyObject
'sclone()
method requires that a class implement this interface, and thisclone()
method isn't being called.- The overriding
clone()
method doesn't throwCloneNotSupportedException
. This exception is thrown only fromObject
'sclone()
method, which isn't called. Therefore, the exception doesn't have to be handled or passed up the method-call stack via a throws clause. Object
'sclone()
method isn't called (there's nosuper.clone()
call) because shallow copying isn't required for theAddress
class -- there's only a single field to copy.
Address
object, it suffices to create a new Address
object and initialize it to a duplicate of the object referenced from the city
field. The new Address
object is then returned.
Compile Listing 6 (
javac CloneDemo.java
) and run this application (java CloneDemo
). You should observe the following output:
John Doe: 49: Denver
John Doe: 49: Denver
John Doe: 49: Chicago
John Doe: 49: Denver
Cloning arrays
Array types have access to theclone()
method, which lets you shallowly clone an array. When used in an array context, you don't have to cast clone()
's return value to the array type. Listing 7 demonstrates array cloning.
Listing 7.Shallowly cloning a pair of arrays
class City
{
private String name;
City(String name)
{
this.name = name;
}
String getName()
{
return name;
}
void setName(String name)
{
this.name = name;
}
}
class CloneDemo
{
public static void main(String[] args)
{
double[] temps = { 98.6, 32.0, 100.0, 212.0, 53.5 };
for (int i = 0; i < temps.length; i++)
System.out.print(temps[i] + " ");
System.out.println();
double[] temps2 = temps.clone();
for (int i = 0; i < temps2.length; i++)
System.out.print(temps2[i] + " ");
System.out.println();
System.out.println();
City[] cities = { new City("Denver"), new City("Chicago") };
for (int i = 0; i < cities.length; i++)
System.out.print(cities[i].getName() + " ");
System.out.println();
City[] cities2 = cities.clone();
for (int i = 0; i < cities2.length; i++)
System.out.print(cities2[i].getName() + " ");
System.out.println();
cities[0].setName("Dallas");
for (int i = 0; i < cities2.length; i++)
System.out.print(cities2[i].getName() + " ");
System.out.println();
}
}
Listing 7 declares a City
class that stores the name and (eventually) other details about a city, such as its population. The CloneDemo
class provides a main()
method to demonstrate array cloning.
main()
first declares an array of double precision
floating-point values that denote temperatures. After outputting this
array's values, it clones the array -- note the absence of a cast
operator. Next, it outputs the clone's identical temperature values.
Continuing,
main()
creates an array of City
objects, outputs the city names, clones this array, and outputs the
cloned array's city names. As a proof that shallow cloning was used,
note that main()
changes the name of the first City
object in the original array and then outputs all of the city names in
the second array. The second array reflects the changed name.
Compile Listing 7 (
javac CloneDemo.java
) and run this application (java CloneDemo
). You should observe the following output:
98.6 32.0 100.0 212.0 53.5
98.6 32.0 100.0 212.0 53.5
Denver Chicago
Denver Chicago
Dallas Chicago
Comparing objects: equals()
Theequals()
method lets you compare the contents of two objects to see if they are equal. This form of equality is known as content equality.
Although the
==
operator compares two primitive values for
content equality, it doesn't work the way you might expect (for
performance reasons) when used in an object-comparison context. In this
context, ==
compares two object references to determine whether or not they refer to the same object. This form of equality is known as reference equality
Object
's implementation of the equals()
method compares the reference of the object on which equals()
is called with the reference passed as an argument to the method. In other words, the default implementation of equals()
performs a reference equality check. If the two references are the same, equals()
returns true; otherwise, it returns false.
You must override
equals()
to perform content equality. The rules for overriding the equals()
method are stated in Oracle's official documentation for the Object
class. They're worth repeating below:
- Be reflexive: For any non-null reference value
x
,x.equals(x)
should return true. - Be symmetric: For any non-null reference values
x
andy
,x.equals(y)
should return true if and only ify.equals(x)
returns true. - Be transitive: For any non-null reference values
x
,y
, andz
, ifx.equals(y)
returns true andy.equals(z)
returns true, thenx.equals(z)
should return true. - Be consistent: For any non-null reference values
x
andy
, multiple invocations ofx.equals(y)
consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
x
, x.equals(null)
should return false.
Content equality
The small application in Listing 8 demonstrates how to properly overrideequals()
to perform content equality.
Listing 8. Comparing objects for content equality
class Employee
{
private String name;
private int age;
Employee(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o)
{
if (!(o instanceof Employee))
return false;
Employee e = (Employee) o;
return e.getName().equals(name) && e.getAge() == age;
}
String getName()
{
return name;
}
int getAge()
{
return age;
}
}
class EqualityDemo
{
public static void main(String[] args)
{
Employee e1 = new Employee("John Doe", 29);
Employee e2 = new Employee("Jane Doe", 33);
Employee e3 = new Employee("John Doe", 29);
Employee e4 = new Employee("John Doe", 27 + 2);
// Demonstrate reflexivity.
System.out.println("Demonstrating reflexivity...");
System.out.println();
System.out.println("e1.equals(e1): " + e1.equals(e1));
System.out.println();
// Demonstrate symmetry.
System.out.println("Demonstrating symmetry...");
System.out.println();
System.out.println("e1.equals(e2): " + e1.equals(e2));
System.out.println("e2.equals(e1): " + e2.equals(e1));
System.out.println("e1.equals(e3): " + e1.equals(e3));
System.out.println("e3.equals(e1): " + e3.equals(e1));
System.out.println("e2.equals(e3): " + e2.equals(e3));
System.out.println("e3.equals(e2): " + e3.equals(e2));
System.out.println();
// Demonstrate transitivity.
System.out.println("Demonstrating transitivity...");
System.out.println();
System.out.println("e1.equals(e3): " + e1.equals(e3));
System.out.println("e3.equals(e4): " + e3.equals(e4));
System.out.println("e1.equals(e4): " + e1.equals(e4));
System.out.println();
// Demonstrate consistency.
System.out.println("Demonstrating consistency...");
System.out.println();
for (int i = 0; i < 5; i++)
{
System.out.println("e1.equals(e2): " + e1.equals(e2));
System.out.println("e1.equals(e3): " + e1.equals(e3));
}
System.out.println();
// Demonstrate the null check.
System.out.println("Demonstrating null check...");
System.out.println();
System.out.println("e1.equals(null): " + e1.equals(null));
}
}
Listing 8 declares an Employee
class that describes employees as combinations of names and ages. This class also overrides equals()
to properly compare two Employee
objects.
The
equals()
method first verifies that an Employee
object has been passed. If not, it returns false. This check relies on the instanceof
operator, which also evaluates to false when null
is passed as an argument. Note that doing this satisfies the final rule above: "for any non-null reference value x
, x.equals(null)
should return false."
Continuing, the object argument is cast to
Employee
. You don't have to worry about a possible ClassCastException
because the previous instanceof
test guarantees that the argument has Employee
type. Following the cast, the two name
fields are compared, which relies on String
's equals()
method, and the two age
fields are compared.
Compile Listing 8 (
javac EqualityDemo.java
) and run the application (java EqualityDemo
). You should observe the following output:
Demonstrating reflexivity...
e1.equals(e1): true
Demonstrating symmetry...
e1.equals(e2): false
e2.equals(e1): false
e1.equals(e3): true
e3.equals(e1): true
e2.equals(e3): false
e3.equals(e2): false
Demonstrating transitivity...
e1.equals(e3): true
e3.equals(e4): true
e1.equals(e4): true
Demonstrating consistency...
e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true
e1.equals(e2): false
e1.equals(e3): true
Demonstrating null check...
e1.equals(null): false
Calling equals() on an array
You can callequals()
on an array object reference, as shown in Listing 9, but you shouldn't. Because equals()
performs reference equality in an array context, and because equals()
cannot be overridden in this context, this capability isn't useful.
Listing 9. An attempt to compare arrays
class EqualityDemo
{
public static void main(String[] args)
{
int x[] = { 1, 2, 3 };
int y[] = { 1, 2, 3 };
System.out.println("x.equals(x): " + x.equals(x));
System.out.println("x.equals(y): " + x.equals(y));
}
}
Listing 9's main()
method declares a pair of arrays with
identical types and contents. It then attempts to compare the first
array with itself and the first array with the second array. However,
because of reference equality, only the array object references are
being compared; the contents are not compared. Therefore, x.equals(x)
returns true (because of reflexivity -- an object is equal to itself), but x.equals(y)
returns false.
Compile Listing 9 (
javac EqualityDemo.java
) and run the application (java EqualityDemo
). You should observe the following output:
x.equals(x): true
x.equals(y): false
Assigning cleanup tasks: finalize()
Suppose you've created aTruck
object and have assigned its reference to Truck
variable t
. Now suppose that this reference is the only reference to that object (that is, you haven't assigned t
's reference to another variable). By assigning null
to t
, as in t = null;
, you remove the only reference to the recently created t
object, and make the object available for garbage collection.
The
finalize()
method lets an object whose class overrides this method, and which is known as a finalizer, perform cleanup tasks (such as releasing operating system resources) when called by the garbage collector. This cleanup activity is known as finalization.
The default
finalize()
method does nothing; it returns when called. You must provide code that performs some kind of cleanup task, and should code finalize()
according to the following pattern:
Because
class someClass { // ... @Override protected void finalize() throws Throwable { try { // Finalize the subclass state. // ... } finally { super.finalize(); } } }
finalize()
can execute arbitrary code, it's capable of throwing an exception. Because all exception classes ultimately derive from Throwable
(in the java.lang
package), Object
declares finalize()
with a throws Throwable
clause appended to its method header.
Finalization for superclasses
It is also possible for a superclass to have afinalize()
method that must be called. In these cases we can use a try
-finally
construct within finalize()
to execute the finalization code. The finally
block ensures that the superclass's finalize()
method will be called, regardless of whether finalize()
throws an exception.
When
finalize()
throws an exception, the exception is
ignored. Finalization of the object terminates, which can leave the
object in a corrupt state. If another thread (i.e., path of execution) tries to use this object, the resulting behavior will be nondeterministic. (See "Java 101: Understanding Java threads" for a complete introduction to threaded programming in Java.)
The
finalize()
method is never called more than once by the JVM for any given object. If you choose to resurrect an object by making it reachable to application code (such as assigning its reference to a static
field), finalize()
will not be called a second time when the object becomes unreachable (i.e., eligible for garbage collection).
Supporting hash-based collections: hashcode()
ThehashCode()
method returns a hash code (the
value returned from a hash -- scrambling -- function) for the object on
which this method is called. This method is used by hash-based collection classes, such as the java.util
package's HashMap
, HashSet
, and Hashtable
classes to ensure that objects are properly stored and retrieved.
You typically override
hashCode()
when also overriding equals()
in your classes, in order to ensure that objects instantiated from
these classes work properly with all hash-based collections. This is a
good habit to get into, even when your objects won't be stored in
hash-based collections.
The JDK documentation for
Object
's hashCode()
method presents a general contract that must be followed by an overriding hashCode()
method:
- Whenever
hashCode()
is invoked on the same object more than once during an execution of a Java application,hashCode()
must consistently return the same integer, provided no information used inequals()
comparisons on the object is modified. However, this integer doesn't need to remain consistent from one execution of an application to another. - When two objects are equal according to the overriding
equals()
method, callinghashCode()
on each of the two objects must produce the same integer result. - When two objects are unequal according to the overriding
equals()
method, the integers returned from callinghashCode()
on these objects can be identical. However, havinghashCode()
return distinct values for unequal objects may improve hashtable performance.
hashCode()
. I'll also present a recipe for writing a good hashCode()
method and demonstrate this method with various hash-based collections.
String representation and debugging: toString()
ThetoString()
method returns a string representation of
the object on which this method is called. The returned string is useful
for debugging purposes. Consider Listing 10.
Sim
Listing 10. Returning a default string representation
class Employee
{
private String name;
private int age;
public Employee(String name, int age)
{
this.name = name;
this.age = age;
}
}
Listing 10 presents an Employee
class that doesn't override toString()
. When this method isn't overridden, the string representation is returned in the format classname@hashcode
, where hashcode
is shown in hexadecimal notation.
Suppose you were to execute the following code fragment, which instantiates
Employee
, calls the object's toString()
method, and outputs the returned string:
Employee e = new Employee("Jane Doe", 21);
System.out.println(e.toString());
You might observe the following output:
Employee@1c7b0f4d
Listing 11 augments this class with an overriding toString()
method.
Listing 11. Returning a non-default string representation
class Employee
{
private String name;
private int age;
public Employee(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return name + ": " + age;
}
}
toString()
method returns a string
consisting of a colon-separated name and age. Executing the previous
code fragment would result in the following output:
Jane Doe: 21
Waiting and notification: wait() and notify()
Object
's three wait()
methods and its notify()
and notifyAll()
methods are used by threads to coordinate their actions. For example,
one thread might produce an item that another thread consumes. The
producing thread should not produce an item before the previously
produced item is consumed; instead, it should wait until it's notified
that the item was consumed. Similarly, the consuming thread should not
attempt to consume a non-existent item; instead, it should wait until
it's notified that the item is produced. The methods wait()
and notify()
support this coordination.
No comments:
Post a Comment