The Object class is the ultimate ancestor-every class in Java extends Object. However, you never have to write:

class Employee extends Object

The ultimate superclass Object is taken for granted if no superclass is explicitly mentioned. Because every class in Java extends Object, it is important to be familiar with the services provided by the Object class. We will go over the basic ones in this chapter and refer you to later chapters or to the on-line documentation for what is not covered here. (Several methods of Object come up only when dealing with threads-see Volume 2 for more on threads.) You can use a variable of type Object to refer to objects of any type:

Object obj = new Employee("Harry Hacker", 35000);

Of course, a variable of type Object is only useful as a generic holder for arbitrary values. To do anything specific with the value, you need to have some knowledge about the original type and then apply a cast:

Employee e = (Employee)obj;

Java graphics cplus_icon

In C++, there is no cosmic root class. Of course, in C++, every pointer can be converted to a void* pointer. Java programmers often use Object references for generic programming, to implement data structures and algorithms that support a variety of data types. In C++, templates are commonly used for generic programming. But Java has no templates, so Java programmers often have to give up compile-time typing and make do with code that manipulates Object references.

The equals and toString methods

The equals method in the Object class tests whether or not one object is equal to another. The equals method, as implemented in the Object class, determines whether or not two objects point to the same area of memory. This is not a useful test. If you want to test objects for equality, you will need to override equals for a more meaningful comparison. For example,

class Employee
{ // . . .
 public boolean equals(Object otherObject)
 {
 // a quick test to see if the objects are identical
 if (this == otherObject) return true;
 // must return false if the explicit parameter is null
 if (otherObject == null) return false;
 // if the classes don't match, they can't be equal
 if (getClass() != otherObject.getClass())
 return false;
 // now we know otherObject is a non-null Employee
 Employee other = (Employee)otherObject;
 // test whether the fields have identical values
 return name.equals(other.name)
 && salary == other.salary
 && hireDay.equals(other.hireDay);
 }
}

The getClass method returns the class of an object-we will discuss this method in detail later in this chapter. For two objects to be equal, they must first be objects of the same class.Java graphics notes_icon

How should the equals method behave if the implicit and explicit parameters don't belong to the same class? Unfortunately, different programmers take different actions in this case. We recommend that equals should return false if the classes don't match exactly. But many programmers use a test:

if (!(otherObject instanceof Employee)) return false;

This leaves open the possibility that otherObject can belong to a subclass. Other programmers use no test at all. Then the equals method throws an exception if otherObject cannot be cast to an Employee object. Technically speaking, both of these approaches are wrong. Here is why. The Java Language Specification requires that the equals method has the following properties:

  1. It is reflexive: for any non-null reference x, x.equals(x) should return true.
  2. It is symmetric: for any references x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  3. It is transitive: for any references x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  4. It is consistent: If the objects to which x and y refer haven't changed, then repeated calls to x.equals(y) return the same value.
  5. For any non-null reference x, x.equals(null) should return false.

Rule 5 mandates that you include the test

if (otherObject == null) return false;

in your equals method. What is less obvious is that Rule 2 requires you to test for class equality. Consider a call

e.equals(m)

where e is an Employee object and m is a Manager object, both of which happen to have the same name, salary, and hire date. If you don't check that the class of m is the same as the class of e, this call returns true. But that means that the reverse call

m.equals(e)

also needs to return true-Rule 2 does not allow it to return false, or to throw an exception. Unfortunately, the Java Language Specification does a poor job of explaining this consequence, and the majority of programmers seem to be unaware of it. The standard Java library contains over 150 implementations of equals methods, with a mishmash of using instanceof, calling getClass, catching a ClassCastException, or doing nothing at all. Only a tiny minority of implementations fulfills Rule 2. You can do better, by following our recipe for the perfect equals method.Here is a recipe for writing the perfect equals method:

  1. Call the explicit parameter otherObject-later, you need to cast it to another variable that you should call other.
  2. Test whether this happens to be identical to otherObject:
    if (this == otherObject) return true;
    

    This is just an optimization. In practice, this is a common case. It is much cheaper to check for identity than to compare the fields.

  3. Test whether otherObject is null and return false if it is. This test is required.
    if (otherObject == null) return false;
    
  4. Test whether this and otherObject belong to the same class. This test is required by the "symmetry rule".
    if (getClass() != otherObject.getClass()) return false;
    
  5. Cast otherObject to a variable of your class type:
    ClassName other = (ClassName)otherObject
    
  6. Now compare all fields. Use == for primitive type fields, equals for object fields. Return true if all fields match, false otherwise.
    return field1 == other.field1
     && field2.equals(other.field2)
     && . . .;
    

If you define the equals method for a subclass of a class that follows these rules, first call equals on the superclass. If that test doesn't pass, then the objects can't be equal. If the superclass fields are equal, then you are ready to compare the instance fields of the subclass.

class Manager extends Employee
{
 . . .
 public boolean equals(Object otherObject)
 {
 if (!super.equals(otherObject)) return false;
 Manager other = (Manager)otherObject;
 // super.equals checked that this and otherObject
 // belong to the same class
 return bonus == other.bonus;
 }
}

Another important method in Object is the toString method that returns a string that represents the value of this object. Almost any class will override this method to give you a printed representation of the object's current state. Here is a typical example. The toString method of the Point class returns a string like this:

java.awt.Point[x=10,y=20]

Most (but not all) toString methods follow this format: the name of the class, followed by the field values enclosed in square brackets. Here is an implementation of the toString method for the Employee class:

public String toString()
{
 return "Employee[name=" + name
 + ",salary=" + salary
 + ",hireDay=" + hireDay
 + "]";
}

Actually, you can do a little better. Rather than hardwiring the class name into the toString method, call getClass().getName() to obtain a string with the class name.

public String toString()
{
 return getClass().getName()
 + "[name=" + name
 + ",salary=" + salary
 + ",hireDay=" + hireDay
 + "]";
}

Then the toString method also works for subclasses. Of course, the subclass programmer should define its own toString method and add the subclass fields. If the superclass uses getClass().getName(), then the subclass can simply call super.toString(). For example, here is a toString method for the Manager class:

class Manager extends Employee
{
 . . .
 public String toString()
 {
 return super.toString()
 + "[bonus=" + bonus
 + "]";
 }
}

Now a Manager object is printed as:

Manager[name=...,salary=...,hireDay=...][bonus=...]

The toString method is ubiquitous for an important reason: whenever an object is concatenated with a string, using the "+" operator, the Java compiler automatically invokes the toString method to obtain a string representation of the object. For example,

Point p = new Point(10, 20);
String message = "The current position is " + p;
 // automatically invokes p.toString()

Java graphics exclamatory_icon

Instead of writing x.toString(), you can write "" + x. This concatenates the empty string with the string representation of x that is exactly x.toString().If x is any object and you call

System.out.println(x);

then the println method simply calls x.toString() and prints the resulting string. The Object class defines the toString method to print the class name and the memory location of the object. For example, the call

System.out.println(System.out)

produces an output that looks like this:

java.io.PrintStream@2f6684

The reason is that the implementor of the PrintStream class didn't bother to override the toString method. The toString method is a great debugging tool. Many classes in the standard class library define the toString method so that you can get useful debugging information. Some debuggers let you invoke the toString method to display objects. And you can always insert trace messages like this:

System.out.println("Current position = " + position);

Java graphics exclamatory_icon

We strongly recommend that you add a toString method to each class that you write. You, as well as other programmers who use your classes, will be grateful for the debugging support.The program in implements the equals and toString methods for the Employee and Manager classes.

Example EqualsTest.java

 1. import java.util.*;
 2.
 3. public class EqualsTest
 4. {
 5. public static void main(String[] args)
 6. {
 7. Employee alice1 = new Employee("Alice Adams", 75000,
 8. 1987, 12, 15);
 9. Employee alice2 = alice1;
 10. Employee alice3 = new Employee("Alice Adams", 75000,
 11. 1987, 12, 15);
 12. Employee bob = new Employee("Bob Brandson", 50000,
 13. 1989, 10, 1);
 14.
 15. System.out.println("alice1 == alice2: "
 16. + (alice1 == alice2));
 17.
 18. System.out.println("alice1 == alice3: "
 19. + (alice1 == alice3));
 20.
 21. System.out.println("alice1.equals(alice3): "
 22. + alice1.equals(alice3));
 23.
 24. System.out.println("alice1.equals(bob): "
 25. + alice1.equals(bob));
 26.
 27. System.out.println("bob.toString(): " + bob);
 28.
 29. Manager carl = new Manager("Carl Cracker", 80000,
 30. 1987, 12, 15);
 31. Manager boss = new Manager("Carl Cracker", 80000,
 32. 1987, 12, 15);
 33. boss.setBonus(5000);
 34. System.out.println("boss.toString(): " + boss);
 35. System.out.println("carl.equals(boss): "
 36. + carl.equals(boss));
 37. }
 38. }
 39.
 40. class Employee
 41. {
 42. public Employee(String n, double s,
 43. int year, int month, int day)
 44. {
 45. name = n;
 46. salary = s;
 47. GregorianCalendar calendar
 48. = new GregorianCalendar(year, month - 1, day);
 49. hireDay = calendar.getTime();
 50. }
 51.
 52. public String getName()
 53. {
 54. return name;
 55. }
 56.
 57. public double getSalary()
 58. {
 59. return salary;
 60. }
 61.
 62. public Date getHireDay()
 63. {
 64. return hireDay;
 65. }
 66.
 67. public void raiseSalary(double byPercent)
 68. {
 69. double raise = salary * byPercent / 100;
 70. salary += raise;
 71. }
 72.
 73. public boolean equals(Object otherObject)
 74. {
 75. // a quick test to see if the objects are identical
 76. if (this == otherObject) return true;
 77.
 78. // must return false if the explicit parameter is null
 79. if (otherObject == null) return false;
 80.
 81. // if the classes don't match, they can't be equal
 82. if (getClass() != otherObject.getClass())
 83. return false;
 84.
 85. // now we know otherObject is a non-null Employee
 86. Employee other = (Employee)otherObject;
 87.
 88. // test whether the fields have identical values
 89. return name.equals(other.name)
 90. && salary == other.salary
 91. && hireDay.equals(other.hireDay);
 92. }
 93.
 94. public String toString()
 95. {
 96. return getClass().getName()
 97. + "[name=" + name
 98. + ",salary=" + salary
 99. + ",hireDay=" + hireDay
100. + "]";
101. }
102.
103. private String name;
104. private double salary;
105. private Date hireDay;
106. }
107.
108. class Manager extends Employee
109. {
110. public Manager(String n, double s,
111. int year, int month, int day)
112. {
113. super(n, s, year, month, day);
114. bonus = 0;
115. }
116.
117. public double getSalary()
118. {
119. double baseSalary = super.getSalary();
120. return baseSalary + bonus;
121. }
122.
123. public void setBonus(double b)
124. {
125. bonus = b;
126. }
127.
128. public boolean equals(Object otherObject)
129. {
130. if (!super.equals(otherObject)) return false;
131. Manager other = (Manager)otherObject;
132. // super.equals checked that this and other belong to the
133. // same class
134. return bonus == other.bonus;
135. }
136.
137. public String toString()
138. {
139. return super.toString()
140. + "[bonus=" + bonus
141. + "]";
142. }
143.
144. private double bonus;
145. }

Generic Programming

All values of any class type can be held in variables of type Object. In particular, String values are objects:

Object obj = "Hello"; // OK

However, numbers, characters, and boolean values are not objects.

obj = 5; // ERROR obj = false; // ERROR

You will see later in this chapter how you can turn these types into objects by using wrapper classes such as Integer and Boolean. Furthermore, all array types, no matter whether they are arrays of objects or arrays of primitive types, are class types that derive from Object.

Employee[] staff = new Employee[10];
Object arr = staff; // OK arr = new int[10]; // OK

An array of objects of class type can be converted to an array of any superclass type. For example, an Employee[] array can be passed to a method that expects an Object[] array. That conversion is useful for generic programming. Here is a simple example that illustrates the concept of generic programming. Suppose you want to find the index of an element in an array. This is a generic situation, and by writing the code for objects, you can reuse it for employees, dates, or whatever.

static int find(Object[] a, Object key)
{
 int i;
 for (i = 0; i < a.length; i++)
 if (a[i].equals(key)) return i;
 return -1; // not found
}

For example,

Employee[] staff = new Employee[10];
Employee harry;
. . .
int n = find(staff, harry);

Note that you can only convert an array of objects into an Object[] array. You cannot convert an int[] array into an Object[] array. (However, as previously pointed out, both arrays can be converted to Object.) If you convert an array of objects to an Object[] array, the generic array still remembers its original type at run time. You cannot store a foreign object into the array.

Employee[] staff = new Employee[10];
. . . // fill with Employee objects Object[] arr = staff;
arr[0] = new Date();
 // not legal, but suppose it was for (i = 0; i < n; i++) staff[i].raiseSalary(3);
 // ouch, now the date gets a raise!

Of course, this must be checked at run time. The code above compiles without error-it is legal to store a Date value in arr[0], which has type Object. But when the code executes, the array remembers its original type and monitors the type of all objects that are stored in it. If you store an incompatible type into an array, an exception is thrown.Java graphics cplus_icon

C++ programmers may be surprised that the cast from Employee[] to Object[] is legal. Even if Object was a superclass of Employee in C++, the equivalent cast from Employee** to Object** would not be legal. (Of course, the cast from Employee* to Object* is legal in C++.) There is a security reason behind this restriction. If the cast "Subclass** Screenshot Superclass**" were permitted, you could corrupt the contents of an array. Consider this code:

Employee** staff; // C++
Object** arr = staff;
 // not legal, but suppose it was arr[0] = new Date();
 // legal, Date also inherits from Object for (i = 0; i < n; i++) staff[i]->raiseSalary(3);
 // ouch, now the date gets a raise!

In Java, this problem is averted by remembering the original type of all arrays and by monitoring all array stores for type compatibility at run time.

java.lang.Object 1.0

Java graphics api_icon Java graphics notes_icon

Cloning an object is important, but it also turns out to be a fairly subtle process filled with potential pitfalls for the unwary. We will have a lot more to say about the clone method in .

java.lang.Class 1.0

Java graphics api_icon

Array Lists

In many coding languages-in particular in C-you have to fix the sizes of all arrays at compile time. Programmers hate this because it forces them into uncomfortable trade-offs. How many employees will be in a department? Surely no more than 100. What if there is a humongous department with 150 employees? Do we want to waste 90 entries for every department with just 10 employees? In Java, the situation is much better. You can set the size of an array at run time.

int actualSize = . . .;
Employee[] staff = new Employee[actualSize];

Of course, this code does not completely solve the problem of dynamically modifying arrays at run time. Once you set the array size, you cannot change it easily. Instead, the easiest way in Java to deal with this common situation is to use another Java class that works much like an array that will shrink and grow automatically. This class is called ArrayList. Thus, in Java, array lists are arraylike objects that can grow and shrink automatically without you needing to write any code.Java graphics notes_icon

In older versions of the Java coding language, programmers used the Vector class for automatically resizing arrays. However, the ArrayList class is more efficient, and you should generally use it instead of vectors. See of Volume 2 for more information about vectors.There is an important difference between an array and an array list. Arrays are a feature of the Java language, and there is an array type T[] for each element type T. However, the ArrayList class is a library class, defined in the java.util package. This is a single "one size fits all" type which holds elements of type Object. In particular, you will need a cast whenever you want to take an item out of an array list. Use the add method to add new elements to an array list. For example, here is how you create an array list and populate it with employee objects:

ArrayList staff = new ArrayList();
staff.add(new Employee(. . .));
staff.add(new Employee(. . .));

The ArrayList class manages an internal array of Object references. Eventually, that array will run out of space. This is where array lists work their magic: If you call add and the internal array is full, the array list automatically creates a bigger array, and automatically copies all the objects from the smaller to the bigger array. If you already know, or have a good guess, how many elements you want to store, then call the ensureCapacity method before filling the array list:

staff.ensureCapacity(100);

That call allocates an internal array of 100 objects. Then you can keep calling add, and no costly relocation takes place. You can also pass an initial capacity to the ArrayList constructor:

ArrayList staff = new ArrayList(100);

Java graphics caution_icon

Allocating an array list as

new ArrayList(100) // capacity is 100

is not the same as allocating a new array as

new Employee[100] // size is 100

There is an important distinction between the capacity of an array list and the size of an array. If you allocate an array with 100 entries, then the array has 100 slots, ready for use. An array list with a capacity of 100 elements has the potential of holding 100 elements (and, in fact, more than 100, at the cost of additional relocations), but at the beginning, even after its initial construction, an array list holds no elements at all.The size method returns the actual number of elements in the array list. For example,

staff.size()

returns the current number of elements in the staff array list. This is the equivalent of

a.length

for an array a. Once you are reasonably sure that the array list is at its permanent size, you can call the trimToSize method. This method adjusts the size of the memory block to use exactly as much storage space as is required to hold the current number of elements. The garbage collector will reclaim any excess memory.Java graphics notes_icon

Once you trim the size of an array list, adding new elements will move the block again, which takes time. You should only use trimToSize when you are sure you won't add any more elements to the array list.Java graphics cplus_icon

The ArrayList class differs in a number of important ways from the C++ vector template. Most noticeably, since vector is a template, only elements of the correct type can be inserted, and no casting is required to retrieve elements from the vector. For example, the compiler will simply refuse to insert a Date object into a vector<Employee>. The C++ vector template overloads the [] operator for convenient element access. Since Java does not have operator overloading, it must use explicit method calls instead. C++ vectors are copied by value. If a and b are two vectors, then the assignment a = b; makes a into a new vector with the same length as b, and all elements are copied from b to a. The same assignment in Java makes both a and b refer to the same array list.

java.util.ArrayList 1.2

Java graphics api_icon