In fact, not only is the hierarchy tree-like, Java provides a universal superclass called Object that is defined to be the root of the entire class hierarchy. Every class that is defined in a Java program implicitly extends the class Object.
The class Object defines some useful methods that are automatically inherited by all classes. These include the following:
boolean equals(Object o) // defaults to pointer equality String toString() // converts the values of the // instance variable to String
The built-in method equals checks whether two variables actually point to the same physical object in memory. This is rather strong and most classes will redefine this to say that two objects are the same if their instance variables are the same.
The toString() method is useful because Java implicitly invokes it if an object is used as part of a string concatenation expression. To print the contents of an object o, it is sufficient to write:
System.out.println(o+"");
Here, o+"" is implicitly converted to o.toString()+"".
Recall that a variable of a higher type can be assigned an object of a lower type (e.g., Employee e = new Manager(...)) This compatibility also holds for arguments to methods. A method that expects an Employee will also accept a Manager.
We can write some ``generic'' programs, as follows:
public int find (Object[] objarr, Object o){ int i; for (i = 0; i < objarr.length(); i++){ if (objarr[i] == o) {return i}; } return (-1); }
As mentioned above, we will probably redefine methods like equals to be less restrictive than just referring to pointer equality. However, we should be careful of the types we use. Suppose we enhance our class Date to say:
boolean equals(Date d){ return ((this.day == d.day) && (this.month == d.month) && (this.year == d.year)); }
Before proceeding, we note a couple of facts about this method. Recall that day, month and year are private fields of class Date. Nevertheless, we are able to refer to these fields explictly for the incoming Date object d. This is a general feature--any Date object can directly read the private variables of any other Date object. Without this facility, it would be tedious, or even impossible, to write such an equals(..) method.
Second, note the use of this to unambiguously refer to the object within which equals is being invoked. The word this can be omitted. If we write, instead,
boolean equals(Date d){ return ((day == d.day) && (month == d.month) && (year == d.year)); }
then, automatically, the unqualified fields day, month and year refer to the fields in the current object. However, we often use this to remove ambiguity.
Now, what happens when we call our generic find with find(datearr,d), where datearr and d are declared as Date[] datearr and Date d?
When we call datearr[i].equals(d) in the generic find, we search for a method with signature
boolean equals(Object o)
because, within find, the parameter to find is an Object. This does not match the revised method equals in Date, so we end up testing pointer equality after all!
To fix this, we have to rewrite the equals method in Date as:
boolean equals(Object d){ if (d instanceof Date){ return ((this.day == d.day) && (this.month == d.month) && (this.year == d.year)); } return(false); }
Notice that this is another use of the predicate instanceof to examine the actual class of d at runtime.
In general, the method used is the closest match. If Employee defines
boolean equals(Employee e)
and Manager extends Employee but does not redefine equals, then in the following code
Manager m1 = new Manager(...); Manager m2 = new Manager(...); ... if (m1.equals(m2)){ ... }
the call m1.equals(m2) refers to the definition in Employee because the call to a non existent method
boolean equals(Manager m)
is compatible with both
boolean equals(Employee e) in Employee
and
boolean equals(Object o) in Object.
However, the definition in Employee is a ``closer'' match than the one in Object.