Team LiB
Previous Section Next Section

3.8. Important Methods of java.lang.Object

As we've noted, all classes extend, directly or indirectly, java.lang.Object. This class defines several important methods that you should consider overriding in every class you write. Example 3-6 shows a class that overrides these methods. The sections that follow the example document the default implementation of each method and explain why you might want to override it. You may also find it helpful to look up Object in the reference section for an API listing.

Some of the syntax in Example 3-6 may be unfamiliar to you. The example uses two Java 5.0 features. First, it implements a parameterized, or generic, version of the Comparable interface. Second, the example uses the @Override annotation to emphasize (and have the compiler verify) that certain methods override Object. Parameterized types and annotations are covered in Chapter 4.

Example 3-6. A class that overrides important Object methods
// This class represents a circle with immutable position and radius.
public class Circle implements Comparable<Circle> {
    // These fields hold the coordinates of the center and the radius.
    // They are private for data encapsulation and final for immutability
    private final int x, y, r;

    // The basic constructor: initialize the fields to specified values
    public Circle(int x, int y, int r) {
        if (r < 0) throw new IllegalArgumentException("negative radius");
        this.x = x; this.y = y; this.r = r;
    }

    // This is a "copy constructor"--a useful alternative to clone()
    public Circle(Circle original) {
        x = original.x;   // Just copy the fields from the original
        y = original.y;
        r = original.r;
    }

    // Public accessor methods for the private fields.
    // These are part of data encapsulation.
    public int getX() { return x; }
    public int getY() { return y; }
    public int getR() { return r; }

    // Return a string representation
    @Override public String toString() {
        return String.format("center=(%d,%d); radius=%d", x, y, r);
    }

    // Test for equality with another object
    @Override public boolean equals(Object o) {
        if (o == this) return true;               // Identical references?
        if (!(o instanceof Circle)) return false; // Correct type and non-null?
        Circle that = (Circle) o;                 // Cast to our type
        if (this.x == that.x && this.y == that.y && this.r == that.r)
            return true;                          // If all fields match
        else
            return false;                         // If fields differ
    }

    // A hash code allows an object to be used in a hash table.
    // Equal objects must have equal hash codes.  Unequal objects are allowed
    // to have equal hash codes as well, but we try to avoid that.
    // We must override this method since we also override equals().
    @Override public int hashCode() {
        int result = 17;          // This hash code algorithm from the book
        result = 37*result + x;   // _Effective Java_, by Joshua Bloch
        result = 37*result + y;
        result = 37*result + r;
        return result;
    }

    // This method is defined by the Comparable interface.
    // Compare this Circle to that Circle.  Return a value < 0 if this < that.
    // Return 0 if this == that. Return a value > 0 if this > that.
    // Circles are ordered top to bottom, left to right, and then by radius
    public int compareTo(Circle that) {
        long result = that.y - this.y;  // Smaller circles have bigger y values
        if (result == 0) result = this.x - that.x;  // If same compare l-to-r
        if (result == 0) result = this.r - that.r;  // If same compare radius

        // We have to use a long value for subtraction because the differences
        // between a large positive and large negative value could overflow
        // an int. But we can't return the long, so return its sign as an int.
        return Long.signum(result);  // new in Java 5.0
    }
}

3.8.1. toString()

The purpose of the toString( ) method is to return a textual representation of an object. The method is invoked automatically on objects during string concatenation and by methods such as System.out.println( ). Giving objects a textual representation can be quite helpful for debugging or logging output, and a well-crafted toString() method can even help with tasks such as report generation.

The version of toString() inherited from Object returns a string that includes the name of the class of the object as well as a hexadecimal representation of the hashCode() value of the object (discussed later in this chapter). This default implementation provides basic type and identity information for an object but is not usually very useful. The toString( ) method in Example 3-6 instead returns a human-readable string that includes the value of each of the fields of the Circle class.

3.8.2. equals( )

The = = operator tests two references to see if they refer to the same object. If you want to test whether two distinct objects are equal to one another, you must use the equals() method instead. Any class can define its own notion of equality by overriding equals(). The Object.equals( ) method simply uses the == operator: this default method considers two objects equal only if they are actually the very same object.

The equals( ) method in Example 3-6 considers two distinct Circle objects to be equal if their fields are all equal. Note that it first does a quick identity test with = = as an optimization and then checks the type of the other object with instanceof: a Circle can be equal only to another Circle, and it is not acceptable for an equals() method to throw a ClassCastException. Note that the instanceof test also rules out null arguments: instanceof always evaluates to false if its left-hand operand is null.

3.8.3. hashCode( )

Whenever you override equals( ), you must also override hashCode( ) . This method returns an integer for use by hash table data structures. It is critical that two objects have the same hash code if they are equal according to the equals() method. It is important (for efficient operation of hash tables) but not required that unequal objects have unequal hash codes, or at least that unequal objects are unlikely to share a hash code. This second criterion can lead to hashCode() methods that involve mildly tricky arithmetic or bit-manipulation.

The Object.hashCode() method works with the Object.equals( ) method and returns a hash code based on object identity rather than object equality. (If you ever need an identity-based hash code, you can access the functionality of Object.hashCode() through the static method System.identityHashCode( ).) When you override equals( ), you must always override hashCode() to guarantee that equal objects have equal hash codes. Since the equals( ) method in Example 3-6 bases object equality on the values of the three fields, the hashCode( ) method computes its hash code based on these three fields as well. It is clear from the code that if two Circle objects have the same field values, they will have the same hash code.

Note that the hashCode( ) method in Example 3-6 does not simply add the three fields and return their sum. Such an implementation would be legal but not efficient because two circles with the same radius but whose X and Y coordinates were reversed would then have the same hash code. The repeated multiplication and addition steps "spread out" the range of hash codes and dramatically reduce the likelihood that two unequal Circle objects have the same code. Effective Java Programming Guide by Joshua Bloch (Addison Wesley) includes a helpful recipe for constructing efficient hashCode() methods like this one.

3.8.4. Comparable.compareTo( )

Example 3-6 includes a compareTo( ) method. This method is defined by the java.lang.Comparable interface rather than by Object. (It actually uses the generics features of Java 5.0 and implements a parameterized version of the interface: Comparable<Circle>, but we can ignore that fact until Chapter 4.) The purpose of Comparable and its compareTo( ) method is to allow instances of a class to be compared to each other in the way that the <, <=, > and >= operators compare numbers. If a class implements Comparable, we can say that one instance is less than, greater than, or equal to another instance. Instances of a Comparable class can be sorted.

Since compareTo( ) is defined by an interface, the Object class does not provide any default implementation. It is up to each individual class to determine whether and how its instances should be ordered and to include a compareTo() method that implements that ordering. The ordering defined by Example 3-6 compares Circle objects as if they were words on a page. Circles are first ordered from top to bottom: circles with larger Y coordinates are less than circles with smaller Y coordinates. If two circles have the same Y coordinate, they are ordered from left to right. A circle with a smaller X coordinate is less than a circle with a larger X coordinate. Finally, if two circles have the same X and Y coordinates, they are compared by radius. The circle with the smaller radius is smaller. Notice that under this ordering, two circles are equal only if all three of their fields are equal. This means that the ordering defined by compareTo() is consistent with the equality defined by equals(). This is very desirable (but not strictly required).

The compareTo( ) method returns an int value that requires further explanation. compareTo() should return a negative number if the this object is less than the object passed to it. It should return 0 if the two objects are equal. And compareTo() should return a positive number if this is greater than the method argument.

3.8.5. clone()

Object defines a method named clone() whose purpose is to return an object with fields set identically to those of the current object. This is an unusual method for two reasons. First, it works only if the class implements the java.lang.Cloneable interface. Cloneable does not define any methods, so implementing it is simply a matter of listing it in the implements clause of the class signature. The other unusual feature of clone() is that it is declared protected (see Section 3.6 earlier in this chapter). This means that subclasses of Object can call and override Object.clone(), but other code cannot call it. Therefore, if you want your object to be cloneable, you must implement Cloneable and override the clone() method, making it public.

The Circle class of Example 3-6 does not implement Cloneable; instead it provides a copy constructor for making copies of Circle objects:

Circle original = new Circle(1, 2, 3);  // regular constructor
Circle copy = new Circle(original);     // copy constructor

It can be difficult to implement clone( ) correctly, and it is usually easier and safer to provide a copy constructor. To make the Circle class cloneable, you would add Cloneable to the implements clause and add the following method to the class body:

@Override public Object clone() {
    try { return super.clone(); }
    catch(CloneNotSupportedException e) { throw new AssertionError(e); }
}

See Effective Java Programming Guide by Joshua Bloch for a detailed discussion of the ins and outs of clone() and Cloneable.

    Team LiB
    Previous Section Next Section