Implementing The java.lang.Comparable Interface


Overriding java.lang.Object Methods

The Object class sits at the root of all Java reference type classes. The majority of its methods are meant to be overridden in derived classes. This is especially true if objects of the derived class type will be used in the scenarios listed in table 23-1. The Object methods you should know how to properly override include toString(), equals(), hashCode(), clone(), and finalize(). I discuss these methods in detail below.

Overriding toString()

The toString() method returns a string representation of the object in question. It is generally considered to be good programming practice to override the toString() method in all classes. Doing so can significantly aid debugging efforts. Detailed object information can help you pinpoint problem objects more quickly than if you relied on the default toString() method behavior provided by the Object class.

What, exactly, constitutes a satisfactory toString() method for a particular class is completely subjective. A good toString() method for the Person class might be one that returns a string representation of all its fields as is shown in example 23.2.

Example 23.2: Person.toString()

image from book
 1     public String toString(){ 2         return this.getFullName() + " " + gender + " " + birthday.get(Calendar.DATE) + "/" 3                + birthday.get(Calendar.MONTH) + "/" + birthday.get(Calendar.YEAR); 4       }
image from book

In this example, the birthday field is used to extract the day (DATE), month, and year of the person’s birth. This information is concatenated with the string provided by the Person.getFullName() method.

Overriding equals() And hashCode()

The equals() and hashCode() methods are usually overridden together since the behavior of one has implications for the behavior of the other.

equals() Method

The equals() method is overridden when objects of a particular class support the notion of being logically equal to each other. The equals() method must satisfy a set of five behaviors (equivalence relation) as specified in the Java API documentation. Table 23-3 lists the equals() method’s equivalence relation behaviors.

Table 23-3: equals() Method Equivalence Relation

The equals() method must be...

Description

Reflexive

For any non-null reference value x, x.equals(x) should return true.

Symmetric

For any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

Transitive

For any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

Consistent

For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

...and lastly...

For any non-null reference value x, x.equals(null) should return false.

Overriding the equals() method and getting the equivalence relation right requires some thought and work. The best place to start is to determine whether or not the equals() method must be overridden in the first place. Joshua Bloch, in his book Effective Java Programming Language Guide, suggests applying four criterion to make this determination. If any of these criterion apply you may not need to override the equals() method. Bloch’s equals() Method Criterion are listed in table 23-3.

Table 23-4: Bloch’s equals() Method Criteria

Criterion Applies? Yes/No

Criterion

 

Each instance of the class is inherently unique.

 

You don’t care if the class provides a logical equality test.

 

The superclass already overrides equals() and the inherited behavior is appropriate for this class.

 

The class is private or package-private and you are reasonably sure that its equals() method will never be invoked.

The Person class fails Bloch’s test on all four counts. Each instance of Person is not inherently unique, I care that it provides a logical equality test, and although its direct base class (java.lang.Object) does provide a default implementation for equals(), its default behavior is not appropriate for my purposes. Lastly, it’s a public class and the equals() method will most likely be invoked at some point in the future. Therefore it’s a good idea to override equals() in Person and ensure the correct behavior.

Overriding equals() In The Person Class

What does it mean for one instance of Person to be logically equal to another instance? The meaning of logical equality is entirely at the mercy of design. Is it enough to simply compare birthdays, names, and genders?

Consider also for a moment whether or not the Person class contains enough attributes to make valid logical comparisons. Although in real life each person is unique, in an application, an instance of a Person object is made logically unique by the values of its attributes. (That is, two distinct Person objects might have identical names, birthdays, and genders. These two objects would be logically equal if only their names, birthdays, and genders were used to make the comparison, but would this scratch the itch?) While you chew on that for a while I will proceed to override the equals() method in the Person class using the existing Person class attributes. Example 23.3 gives the code for the Person class’s equals() method.

Example 23.3: Person.equals()

image from book
 1     public boolean equals(Object o){ 2       if(o == null) return false; 3       boolean is_equal = false; 4       if(o instanceof Person){ 5         if(this.first_name.equals(((Person)o).first_name) && 6                               this.middle_name.equals(((Person)o).middle_name) && 7              this.last_name.equals(((Person)o).last_name) && this.gender.equals(((Person)o).gender) && 8              (this.birthday.get(Calendar.YEAR) == ((Person)o).birthday.get(Calendar.YEAR)) && 9              (this.birthday.get(Calendar.MONTH) == ((Person)o).birthday.get(Calendar.MONTH)) && 10             (this.birthday.get(Calendar.DATE) == ((Person)o).birthday.get(Calendar.DATE)) ){ 11               is_equal = true; 12             } 13        } 14        return is_equal; 15      }
image from book

Referring to example 23.3 — the Person.equals() method returns false immediately if the incoming reference is null. It then initializes the boolean return variable is_equal to false and then checks to see if the incoming reference is an instance of the Person class. If not the comparison is complete and the method returns false. Otherwise, the field-by-field comparison between objects takes place in the body of the if statement beginning on line 5. If everything checks out the is_equal variable is set to true and the method returns.

Testing Person.toString() and Person.equals() Methods

Now is a good time to test both the toString() and equals() methods. Example 23.4 gives the code for a short test driver program named MainTestApp that creates a few Person objects, calls the toString() method on each, and then runs the equals() method through a complete test of its equivalence relation requirements.

Example 23.4: MainTestApp.java

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         Person p1 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 4         Person p2 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 5         Person p3 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 6         Person p4 = new Person("Steve", "J", "Jones", 1932, 9, 20, Person.MALE); 7 8         System.out.println("P1: " + p1.toString()); 9         System.out.println("P2: " + p2.toString()); 10        System.out.println("P3: " + p3.toString()); 11        System.out.println("P4: " + p4.toString()); 12 13        System.out.println(p1.equals(p1));  // reflexive - should be true 14        System.out.println(p1.equals(p2) && p2.equals(p1)); // symmetric - should be true 15        System.out.println(p1.equals(p2) && p2.equals(p3) && p1.equals(p3)); // transitive - should be true 16        System.out.println(p1.equals(p2)); // consistent - should be true every time this app executes 17        System.out.println(p1.equals(p4)); // consistent - should be false every time this app executes 18        System.out.println(p1.equals(null)); // should always return false 19      } 20    } // end MainTestApp
image from book

Referring to example 23.4 — four Person objects are created and assigned to references p1 through p4. References p1 through p3 are logically equivalent because they are initialized with identical information. (Although they are distinct objects within the Java runtime environment.) Reference p4 is logically different from the rest as you can see. Lines 8 through 11 test the toString() method on each object. Lines 13 through 18 run through the equivalence relation test for reflexivity, symmetry, etc. Figure 23-1 shows the results of running this program.

image from book
Figure 23-1: Results of Running Example 23.4

hashCode() Method

Now that the Person.equals() method works properly attention can be turned to the implementation of its hashCode() method.

The hashCode() method returns an integer which is referred to as the object’s hash value. The default implementation of hashCode() found in the Object class will, in most cases, return a unique hash value for each distinct object even if they are logically equivalent. In most cases this default behavior will be acceptable, however, if you intend to use a class of objects as keys to hashtables or other hash-based data structures, then you must override the hashCode() method and obey the general contract as specified in the Java API documentation. The general contract for the hashCode() is given in table 23-5.

Table 23-5: The hashCode() General Contract

Check

Criterion

 

The hashCode() method must consistently return the same integer when invoked on the same object more than once during an execution of a Java application provided no information used in equals() comparisons on the object is modified. This integer need not remain constant from one execution of an application to another execution of the same application.

 

The hashCode() method must produce the same results when called on two objects if they are equal according to the equals() method.

 

The hashCode() method is not required to return distinct integer results for logically unequal objects, however, failure to do so may result in degraded hashtable performance.

As you can see from table 23-5 there is a close relationship between the equals() and hashCode() methods. It is recommended that any fields used in the equals() method comparison be used to calculate an object’s hash code. Remember, the primary goal when implementing a hashCode() method is to have it return the same value consistently for logically equal objects. It would also be nice if the hashCode() method returned distinct hash code values for logically unequal objects but according to the general contract this is not a strict requirement.

Before actually implementing the Person.hashCode() method I want to provide you with two hash code generation algorithms.

Bloch’s hash Code Generation Algorithm

Joshua Bloch, in his book Effective Java Programming Language Guide , provides the following algorithm for calculating a hash code:

  1. Start by storing a constant, nonzero value in an int variable called result. (Josh used the value 17)

  2. For each significant field f in your object (each field involved in the equals() comparison) do the following:

    1. Compute an int hash code c for the field:

      1. If the field is boolean compute: (f?0:1)

      2. If the field is a byte, char, short, or int, compute: (int)f

      3. If the field is a long compute: (int)(f^(f >>> 32))

      4. If the field is a float compute: Float.floatToIntBits(f)

      5. If the field is a double compute: Double.doubleToLongBits(f), and then hash the resulting long according to step 2.a.iii.

      6. If the field is an object reference and this class’s equals() method compares the field by recursively invoking equals(), recursively invoke hashCode() on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode() on the canonical representation. If the value of the field is null, return 0.

      7. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values in step 2.b

    1. Combine the hash code c computed in step a into result as follows:

       result = 37*result + c;

  1. Return result.

  2. If equal object instances do not have equal hash codes fix the problem!

Ashmore’s Hash Code Generation Algorithm

Derek Ashmore, in his book The J2EE Architect’s Handbook: How To Be A Successful Technical Architect For J2EE Applications, recommends the following simplified hash code algorithm:

  1. Concatenate the required fields (those involved in the equals() comparison) into a string.

  2. Call the hashCode() method on that string.

  3. Return the resulting hash code value.

Overriding hashCode() In The Person Class

For our purposes, Ashmore’s algorithm will work fine. Since the Person.toString() method concatenates all the same fields together that are used in the Person.equals() method, it should return the same hash code for logically equal object instances and different hash codes for logically unequal objects. Example 23.5 gives the code for the Person.hashCode() method.

Example 23.5: Person.hashCode()

image from book
 1 public int hashCode(){ 2    return this.toString().hashCode(); 3   }
image from book

Referring to example 23.5 — it’s short, sweet, and to the point. But will it work as expected?

Testing Person.hashCode()

Example 23.6 gives the code for the modified MainTestApp program that tests the hashCode() method on four Person objects. Figure 23-2 shows the results of running this program.

Example 23.6: MainTestApp.java (mod 1)

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         Person p1 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 4         Person p2 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 5         Person p3 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 6         Person p4 = new Person("Steve", "J", "Jones", 1932, 9, 20, Person.MALE); 7 8         System.out.println("P1: " + p1.toString()); 9         System.out.println("P2: " + p2.toString()); 10        System.out.println("P3: " + p3.toString()); 11        System.out.println("P4: " + p4.toString()); 12 13        System.out.println(p1.equals(p1));  // reflexive - should be true 14        System.out.println(p1.equals(p2) && p2.equals(p1)); // symmetric - should be true 15        System.out.println(p1.equals(p2) && p2.equals(p3) && p1.equals(p3)); // transitive - should be true 16        System.out.println(p1.equals(p2)); // consistent - should be true every time this app executes 17        System.out.println(p1.equals(p4)); // consistent - should be false every time this app executes 18        System.out.println(p1.equals(null)); // should always return false 19 20        System.out.println(); 21        System.out.println(p1.hashCode()); 22        System.out.println(p2.hashCode()); 23        System.out.println(p3.hashCode()); 24        System.out.println(p4.hashCode()); 25      } 26    } // end MainTestApp
image from book

image from book
Figure 23-2: Results of Running Example 23.6

Referring to example 23.6 — the hashCode() method is called on each Person reference p1 through p4 on lines 21 through 24. As you can see from figure 23-2, the hash codes for Person references p1 through p3 are the same and the hash code for Person reference p4 is different from the rest.

Overriding clone()

Overriding the clone() method is really a two-part process: 1) you must implement the java.lang.Cloneable interface, and 2) override the Object.clone() method. The Cloneable interface has no methods; it simply serves as a type tag to indicate to clone() methods whether or not this class of objects is or is not cloneable. A class is cloneable only if it implements the Cloneable interface. If a class does not implement the Cloneable interface and the clone() method is called on one of its objects, the clone() method will throw a CloneNotSupportedException.

Shallow Copy vs. Deep Copy

Before you go cloning objects you must be aware of the potential dangers associated with cloning. Specifically, you must understand the difference between a shallow copy vs. a deep copy. A shallow copy is one that simply copies the contents of the cloned object’s reference fields into the reference fields of the new object. This results in the new object’s fields referring to the same objects referred to by the cloned object’s reference fields. Figure 23-3 illustrates the concept of the shallow copy.

image from book
Figure 23-3: Concept of a Shallow Copy

In special cases you may want objects to share their contained instances (containment by reference) but doing so should be the intentional result of your application design, not because of a naive implementation of the clone() method.

A deep copy is different from a shallow copy in that it creates copies of the cloned object’s instance field objects and assigns them to the new object. Figure 23-4 illustrates the concept of a deep copy. Referring to figure 23-4 — before Object A is cloned, its Object_Reference field points to Object B. A deep copy of Object A will result in a new object, Object C, whose Object_Reference field points to its very own copy of Object B, which, in this example, becomes a new object named Object D.

image from book
Figure 23-4: Concept of a Deep Copy

Overriding Person.clone()

Example 23.7 gives the code the Person.clone() method. Since I am not showing the code for the entire Person class just note that for this method to work the Person class must implement the Cloneable interface.

Example 23.7: Person.clone()

image from book
 1     public Object clone() throws CloneNotSupportedException { 2         super.clone(); 3         return new Person(new String(first_name), new String(middle_name), new String(last_name), 4                           birthday.get(Calendar.YEAR), birthday.get(Calendar.MONTH), 5                           birthday.get(Calendar.DATE), new String(gender)); 6       }
image from book

Referring to example 23.7 — the Person.clone() method creates and returns a new, logically equal copy of the existing person object. Before doing so, however, it calls the base class clone() method. Example 23.8 gives the code for the modified version of MainTestApp that runs the Person.clone() method through its paces. Figure 23-5 gives the results of running this program.

Example 23.8: MainTestApp.java (mod 2)

image from book
 1     public class MainTestApp { 2       public static void main(String[] args){ 3         Person p1 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 4         Person p2 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 5         Person p3 = new Person("Rick", "W", "Miller", 1963, 5, 6, Person.MALE); 6         Person p4 = new Person("Steve", "J", "Jones", 1932, 9, 20, Person.MALE); 7 8         System.out.println("P1: " + p1.toString()); 9         System.out.println("P2: " + p2.toString()); 10        System.out.println("P3: " + p3.toString()); 11        System.out.println("P4: " + p4.toString()); 12 13        System.out.println(p1.equals(p1));  // reflexive - should be true 14        System.out.println(p1.equals(p2) && p2.equals(p1)); // symmetric - should be true 15        System.out.println(p1.equals(p2) && p2.equals(p3) && p1.equals(p3)); // transitive - should be true 16        System.out.println(p1.equals(p2)); // consistent - should be true every time this app executes 17        System.out.println(p1.equals(p4)); // consistent - should be false every time this app executes 18        System.out.println(p1.equals(null)); // should always return false 19 20        System.out.println(); 21        System.out.println(p1.hashCode()); 22        System.out.println(p2.hashCode()); 23        System.out.println(p3.hashCode()); 24        System.out.println(p4.hashCode()); 25        System.out.println(); 26 27        try{ 28          Person p5 = (Person)p4.clone(); 29          System.out.println(p4.toString()); 30          System.out.println(p5.toString()); 31          System.out.println(p4 == p5); // is it the same object - should be false 32          System.out.println(p4.equals(p5)); // are they logically equal - should be true 33          System.out.println(p4.hashCode()); 34          System.out.println(p5.hashCode()); // hash codes should be the same 35        }catch(CloneNotSupportedException ignored){ } 36 37      } 38    } // end MainTestApp
image from book

image from book
Figure 23-5: Results of Running Example 23.8

Overriding finalize() — And An Alternative Approach

In this section I just want to reemphasize the purpose of a finalize() method, focus your attention on its limitations, and suggest an alternative approach to the execution of time-sensitive object clean-up operations.

An object’s finalize() method is called by the Java Virtual Machine garbage collector when the object is garbage collected. When an object is released from memory it should also release any system resources it may have reserved. However, there are no guarantees exactly when the finalize() method will get called, only that it will be called eventually. (You saw an example of this behavior in the networked version of RobotRat presented in chapter 20.) Therefore, it is imperative that you do not rely on a finalize() method to tidy up things in a time-sensitive manner.

There is no reason for the Person class to override the finalize() method. However, if you are creating a class that must release resources in a time-sensitive manner then your best bet is to implement an explicit clean-up method. Examples of this type of a method can be found throughout the Java Platform API. (For example, to clean-up a Socket and release its resources you call the Socket.close() method.)

Quick Review

Most of the methods provided by the java.lang.Object class are meant to be overridden. These include the toString(), equals(), hashCode(), clone(), and finalize() methods.

The purpose of the toString() method is to provide a string representation for a class of objects. The contents of this string are dictated by class design requirements. It’s considered good programming practice to always override the toString() method.

The purpose of the equals() method is to perform a logical equality comparison between two objects of the same class. The equals() method implements an equivalence relation specified in the Java Platform API documentation that says the method should be reflexive, symmetric, transitive, consistent, and should return false if a non-null instance object is compared against a null reference. The behavior of the equals() method has ramifications for the behavior of other methods as well.

The purpose of the hashCode() method is to provide an integer value for an object when it is used with hashtable or hash-based data structures. The hashCode() method has a general behavior contract specified in the Java Platform API documentation and its performance is dependent upon the behavior of the equals() method. Namely, fields utilized in the equals() method to perform the logical comparison should be used in the hashCode() method to generate the object’s hash code.

The purpose of the clone() method is to create a logically equal copy of an existing object. The important thing to consider when implementing the clone() method is the undesirable effects of performing a shallow copy of complex class types.

The purpose of the finalize() method is to tie-up loose ends when an object is collected by the Java Virtual Machine (JVM) garbage collector. However, do not depend on finalize() to perform a time-sensitive operation as there are no guarantees when the finalize() method will be called.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net