CMPS 144
Code Reuse via Inheritance and Generics
Case Study: Sorting

Consider the following Java class.

/* StringSorterNaive.java
** An instance of this class has methods by which to sort an array (or 
** a segment thereof) of Strings into ascending lexicographic order,
** using the classic Selection Sort algorithm.
*/
public class StringSorterNaive {

   /* pre: a[i] != null for all i in [0..a.length)
   ** post: The elements in a[] have been permuted so that they are 
   **       in ascending order.
   */
   public void sort(String[] a) { sort(a, 0, a.length); }

   /* pre: 0 <= low <= high <= a.length && 
   **      a[i] != null for all i in [low..high)
   ** post: The elements in a[low..high) have been permuted so that
   **       they are in ascending order.
   */
   public void sort(String[] a, int low, int high) { 

      for (int i = low; i != high; i=i+1) {
         int locOfMin = locationOfMin(a, i, high);
         swap(a,i,locOfMin);
      }
   }

   /* pre: 0 <= low < high <= b.length
   ** post: value returned identifies a location within the array
   **       segment b[low..high) containing the minimum element
   **       in that segment.
   */
   private int locationOfMin(String[] b, int low, int high) {
      int locOfMinSoFar = low;
      for (int j = low+1;  j != high;  j = j+1) {
         if (isLessThan(b[j], b[locOfMinSoFar])) {
            locOfMinSoFar = j;
         }
      }
      return locOfMinSoFar;
   }

   /* Returns true iff s1 < s2 with respect to lexicographic order
   ** (which is consistent with String's compareTo() method).
   */
   private boolean isLessThan(String s1, String s2) {
      return s1.compareTo(s2) < 0;
   }

   /* pre: 0 <= i,j < b.length
   ** post: references in b[i] and b[j] have been swapped
   */
   private void swap(String[] b, int i, int j) {
      String temp = b[i];
      b[i] = b[j];
      b[j] = temp;
   }

Here is a simple client program that sorts its command line arguments (which are provided to the main() method via its formal parameter args):

import java.util.Arrays;

public class StringSorterNaiveClient {
   public static void main(String[] args) {
      // Display the command line arguments.
      System.out.println("Original array: " + Arrays.toString(args));

      // Create a StringSorterNaive object and have it sort args[].
      StringSorterNaive sorter = new StringSorterNaive();
      sorter.sort(args);

      // Display the sorted command line arguments.
      System.out.println("After sorting: " + Arrays.toString(args));
   }
}

To clarify, lexicographic order simply generalizes what you already know as "alphabetic order" to account for the presence of non-letter characters in strings. It can be defined as follows:

Lexicographic Order: Let x = x1x2...xm and y = y1y2...yn, where each xi and yj is a character. If x and y are not identical, there are two cases:
  • One of x or y is a proper prefix of the other. In this case, the one that is shorter is deemed to be the lesser string. (E.g., "cat < "cattle".)
  • For some k≥1, x1x2...xk-1 and y1y2...yk-1 are identical, but xk ≠ yk. In this case, the string whose k-th character is less than the other one's k-th character is deemed to be the lesser string. (E.g., "catholic < cattle" because in the first position where they differ, 'h' < 't'.)

By this definition, it is clear that the lexicographic ordering relation upon strings depends upon a more basic ordering relation defined upon individual characters. In Java, that ordering relation is determined by the numeric values that are used for representing values of type char. It turns out that those numbers are in accord with the Unicode/ASCII standard. For example, the numeric code for 'B' is 66 and that for 'a' is 97. It follows that 'B' < 'a'. (Indeed, all upper case letters have smaller numeric codes than the lower case letters, so that 'Z' < 'a'.)

Notice that the only bit of code in StringSorterNaive where Strings are compared is in the isLessThan() method. The algorithm for sorting is quite independent of whatever details are involved in performing such comparisons, a fact that takes on significance below.

Now suppose that we have an application for which it would be useful to sort an array of type String[], but in accord with an ordering relation that differs from lexicographic ordering. For example, perhaps we wish to rearrange an array of strings into ascending length-lexicographic order, defined as follows:

Length-Lexicographic Order: Let x and y be non-identical strings. If they are of different lengths, the shorter of the two is deemed to be the lesser string. (E.g., "zombie" < "aardvark" due to their lengths.) If their lengths are the same, the one that is lesser according to lexicographic order is deemed to be the lesser of the two strings (E.g., "compilation" < "computation" due to 'i' < 'u'.)

What should we do? One option would be to make a new Java class that is a duplicate of StringSorterNaive, except that we would replace the body of the isLessThan() method so that it produces results consistent with length-lexicographic order rather than (plain) lexicographic order.

Of course, there are other ways to order strings that would be useful in various applications. For example, there may be times when we would like to sort a String[] array so that they end up in descending lexicographic (or length-lexicographic) order, rather than ascending. Or perhaps we would like to sort them in accord with an ordering relation that is case insensitive, so that each upper case letter is considered to be equal to its lower case counterpart rather than less than that counterpart. (E.g., 'E' = 'e' rather than 'E' < 'e'.)

This suggests the possibility of having several different Java classes for sorting arrays of strings, all identical to each other except for the bodies of their isLessThan() methods. That would work, but one could reasonably hope for a better approach that would avoid such extensive code duplication across several Java classes.

Is there such an approach? Yes. One is to use inheritance, a feature of object-oriented programming languages (such as Java). Specifically, we can turn StringSorterNaive into an abstract class that leaves its isLessThan() method undefined (in the sense that its body is omitted). The task of fully defining that method is then left to each of its child classes. That is, each child class would provide a method body for isLessThan() implementing whatever criteria it "chooses" to use for comparing two objects of type String. Having also inherited the sort() methods from its parent, instances of each such child class would be capable of sorting a String[] array into an order consistent with its isLessThan() method.

Below is the abstracted version of the class, renamed. Notice the keyword abstract in the class heading. This tells the Java compiler that one or more methods lack bodies. Any such method must be declared to be abstract, as can be seen in the heading of isLessThan(). One subtle point is that an abstract method must be "visible" to its children, which is why isLessThan() is declared to be protected rather than private, as it was in the original version of the class.

/* StringSorter.java
** An instance of a child of this abstract class inherits methods from 
** this class by which to sort an array (or a segment thereof) of Strings 
** into ascending order using the classic Selection Sort algorithm.
** The ordering of strings (which is determined by the criteria used
** in comparing them) is defined by the isLessThan() method, which here
** is abstract (and hence is left to be implemented by each child class).
*/

public abstract class StringSorter {

   /* pre: a[i] != null for all i in [0..a.length)
   ** post: The elements in a[] have been permuted so that they are 
   **       in ascending order with respect to the ordering relation
   **       implemented by the isLessThan() method.
   */
   public void sort(String[] a) { sort(a, 0, a.length); }

   /* pre: 0 <= low <= high <= a.length 
   ** post: The elements in a[low..high) have been permuted so that
   **       they are in ascending order with respect to the ordering
   **       relation implemented by the isLessThan() method.
   */
   public void sort(String[] a, int low, int high)
   { 
      for (int i = low; i != high; i=i+1) {
         int locOfMin = locationOfMin(a, i, high);
         swap(a,i,locOfMin);
      }
   }

   /* pre: 0 <= low < high <= b.length
   ** post: value returned identifies a location within the array
   **       segment b[low..high) containing the minimum element
   **       in that segment.
   */
   private int locationOfMin(String[] b, int low, int high)
   {
      int locOfMinSoFar = low;
      for (int j = low+1;  j != high;  j = j+1) {
         if (isLessThan(b[j], b[locOfMinSoFar])) {
            locOfMinSoFar = j;
         }
      }
      return locOfMinSoFar;
   }

   /* Returns true iff s1 < s2.  It is left to descendant classes to
   ** implement.
   */
   protected abstract boolean isLessThan(String s1, String s2);

   /* pre: 0 <= i,j < b.length
    * post: references in b[i] and b[j] have been swapped
   */
   private void swap(String[] b, int i, int j) {
      String temp = b[i];
      b[i] = b[j];
      b[j] = temp;
   }

}

Here are child classes of StringSorter that, respectively, can sort an array of Strings into ascending lexicographic order and ascending length-lexicographic order. Notice that their headings indicate that they extend their parent class and otherwise contain only a full version of the isLessThan() method.

/* StringSorterLexico.java
** An instance of this class has methods (inherited from its parent)
** by which to sort an array (or a segment thereof) of Strings into 
** ascending order using the classic Selection Sort algorithm.  
** String comparison is carried out using the compareTo() method in  the
** String class (which implements the lexicographic ordering relation).
*/
public class StringSorterLexico extends StringSorter {

   /* Returns true iff s1 < s2 according to the String class's 
   ** compareTo() method, which implements lexicographic ordering.
   */
   @Override
   protected boolean isLessThan(String s1, String s2) {
      return s1.compareTo(s2) < 0;
   }
}

/* StringSorterLenLexico.java
** An instance of this class has methods (inherited from its parent)
** by which to sort an array (or a segment thereof) of Strings into 
** ascending order using the classic Selection Sort algorithm.  
** Strings are ordered based upon the "Length-Lexicographic" criteria,
** which says that shorter strings precede longer strings and, among
** strings of the same length, they are ordered lexicographically.
*/

public class StringSorterLenLexico extends StringSorter {

   /* Returns true iff s1 < s2 according to the Length-Lexicographic
   ** ordering relation, which is to say that either
   ** (1) s1.length() < s2.length() or
   ** (2) s1.length() = s2.length and s1.compareTo(s2) < 0
   */
   @Override
   protected boolean isLessThan(String s1, String s2) {
      final int lengthDiff = s1.length() - s2.length();
      if (lengthDiff < 0) 
         { return true; }       // s1 is shorter than s2, so s1 < s2
      else if (lengthDiff > 0) 
         { return false; }      // s1 is longer than s2, so s1 > s2
      else 
         { return s1.compareTo(s2) < 0; }  // s1 and s2 are of same length, so
   }                                       // revert to lexicographic order
}

What follows is a sample application that sorts the command line arguments provided to it first into lexicographic order and then into length-lexicographic order, using instances of the two classes above, respectively. Notice that the same variable, sorter, which is declared to be of type StringSorter, refers first to an instance of StringSorterLexico and then later to an instance of StringSorterLenLexico. This illustrates what is called polymorphism —another feature of object-orieinted programming languages— which is the ability of a variable declared to be of type A to refer to any object that is an instance of any class that is a descendant of class A.

Consider the client program's first call sorter.sort(). At that moment, sorter refers to an instance of the StringSorterLexico class. The inherited sort() method is thus invoked. During its execution, it calls the isLessThan() method. Because sorter refers to an instance of StringSorterLexico, that class's version of isLessThan() is the one that executes. Later, when sorter refers to an instance of StringSorterLenLexico, execution of the sort() method results in that class's version of isLessThan() being invoked.

What this illustrates is that when a method is called upon an object, the version of the method that executes is the one corresponding to the class of which that object is an instance. This is called dynamic method invocation, yet another feature of OO languages.

import java.util.Arrays;

public class StringSorterClient {
   public static void main(String[] args) {
      // Display the command line arguments.
      System.out.println("Original array: " + Arrays.toString(args));

      // Declare a variable of type StringSorter.  Such a variable can
      // refer to an object of any descendant class of StringSorter.
      StringSorter sorter;

      // Assign to sorter a new StringSorterLexico object and have it 
      // sort args[].
      sorter = new StringSorterLexico();
      sorter.sort(args);

      // Display the sorted command line arguments (which will be
      // in ascending lexicograph order).
      System.out.println("After sorting: " + Arrays.toString(args));

      // Now assign to sorter a new StringSorterLenLexico object and 
      // have it sort args[].
      sorter = new StringSorterLenLexico();
      sorter.sort(args);

      // Display the sorted command line arguments (which will be
      // in ascending length-lexicographic order).
      System.out.println("After sorting: " + Arrays.toString(args));
   }
}


Generalizing to Other Types using Generics

What has been established so far is that, without duplicating the code that implements a sorting algorithm, we can sort arrays of type String[] according to any ordering relation that we invent. For any such ordering relation, all we have to do is to develop a new child class of StringSorter containing a single method, isLessThan(), that compares two String objects and returns a result consistent with the ordering relation that we have in mind.

But what about arrays whose elements are of a type something other than String? Certainly there are any number of other kinds of objects that one might wish to be able to sort, including numbers, calendar dates, points on the plane, employees, etc., etc. Is it possible to develop a single Java class, analogous to StringSorter, that contains sort() methods capable sorting arrays of type X[], where X is any data type? The answer is, yes, almost. The reason for the "almost" is that X is restricted to be any reference type (i.e., any type arising from a Java class), as opposed to one of the eight primitive types (e.g., int, double, boolean).

The best way to generalize the StringSorter class to make it capable of this is to use generics, which were introduced in version 5.0 of Java in the year 2004. Here is the generalized class, renamed appropriately (to omit the word "String"):

/* Sorter.java
** An instance of a child of this abstract class inherits methods from
** this class by which to sort an array (or a segment thereof) into 
** ascending order using the classic Selection Sort algorithm.
** Such a child class would extend this class and in so doing specify a
** concrete data type by which to instantiate the generic type parameter T.
** It would also provide the body of the isLessThan() method, which here
** is abstract.
*/

public abstract class Sorter<T> {

   /* pre: a[i] != null for all i in [0..a.length)
   ** post: The elements in a[] have been permuted so that they are
   **       in ascending order with respect to the ordering relation
   **       implemented by the isLessThan() method.
   */
   public void sort(T[] a) { sort(a, 0, a.length); }

   /* pre: 0 <= low <= high <= a.length
   ** post: The elements in a[low..high) have been permuted so that
   **       they are in ascending order with respect to the ordering
   **       relation implemented by the isLessThan() method.
   */
   public void sort(T[] a, int low, int high)
   {
      for (int i = low; i != high; i=i+1) {
         int locOfMin = locationOfMin(a, i, high);
         swap(a,i,locOfMin);
      }
   }

   /* pre: 0 <= low < high <= b.length
   ** post: value returned identifies a location within the array
   **       segment b[low..high) containing the minimum element
   **       in that segment.
   */
   private int locationOfMin(T[] b, int low, int high)
   {
      int locOfMinSoFar = low;
      for (int j = low+1;  j != high;  j = j+1) {
         if (isLessThan(b[j], b[locOfMinSoFar])) {
            locOfMinSoFar = j;
         }
      }
      return locOfMinSoFar;
   }

   /* Returns true iff x < y.  It is left to descendant classes to
   ** implement.
   */
   protected abstract boolean isLessThan(T x, T y);

   /* pre: 0 <= i,j < b.length
    * post: references in b[i] and b[j] have been swapped
   */
   private void swap(T[] b, int i, int j) {
      T temp = b[i];
      b[i] = b[j];
      b[j] = temp;
   }

}

Notice in the class's heading that its name is augmented by an angle-bracketed generic type parameter, T, and that every occurrence of String in the original class has been replaced by T. The idea is that, whenever this class is referred to in another class (e.g., in the heading of a child class, or in a variable declaration in a client), the generic type parameter must be instantiated by a concrete data type, such as String, or Employee, or Fraction. Conceptually, this gives rise to a new Java class that is identical to Sorter except that every occurrence of T has been replaced by the instantiating data type.

Here is a new version of the StringSorterLexico class, this time having the generic Sorter class as its parent, rather than StringSorter:

/* StringSorterLexico.java
** An instance of this class has methods (inherited from its parent,
** instantiated by the data type String) by which to sort an array 
** (or a segment thereof) into ascending order using the classic 
** Selection Sort algorithm.
** String comparison is carried out using the compareTo() method in
** the String class (which implements lexicographic ordering).
*/
public class StringSorterLexico extends Sorter<String> {

   /* Returns true iff s1 < s2 according to the String class's
   ** compareTo() method, which implements lexicographic ordering.
   */
   @Override
   protected boolean isLessThan(String s1, String s2) {
      return s1.compareTo(s2) < 0;
   }
}

Notice that its heading proclaims that StringSorterLexico is the child of Sorter<String>, which can be understood to be the Java class resulting from replacing every occurrence of generic type parameter T in the body of Sorter by String. (If you were to make such replacements, you would get exactly the body of the class StringSorter!)

How about a child class of Sorter that can sort arrays of type Integer[]? Here's one that defines an ordering on integers based upon absolute value:

/* IntSorterAbsVal.java
** An instance of this class has methods (inherited from its parent,
** instantiated by the data type Integer) by which to sort an array 
** (or a segment thereof) into ascending order using the classic 
** Selection Sort algorithm.
*/
public class IntSorterAbsVal extends Sorter<Integer> {

   /* Returns true iff k is less than m according to this rule:
   ** If k and m have different absolute values, the one having the
   ** smaller absolute value is deemed to be the lesser of the two.
   ** If their absolute values are the same, we revert to the usual
   ** way of comparing numbers (so that, e.g., -5 < 5)..
   */
   @Override
   protected boolean isLessThan(Integer k, Integer m) {
      int absValDiff = Math.abs(k) - Math.abs(m);
      if (absValDiff != 0) { return absValDiff < 0; }
      else { return k < m; }
   }
}

Here is a client program that sorts the command line arguments provided to it into lexicographic order (using an instance of the new version of the class StringSorterLexico), then into length-lexicographic order (using an instance of the new version of StringSorterLenLexico, which is left to the reader to supply). It also sorts the lengths of the command line arguments in accord with the ordering defined by IntSorterAbsVal shown above.

import java.util.Arrays;

public class SorterClient {
   public static void main(String[] args) {
      // Display the command line arguments.
      System.out.println("Original array: " + Arrays.toString(args));

      // Declare a variable of type Sorter<String>.  Such a variable 
      // can refer to an object of any descendant class of Sorter that
      // instantiates it by the data type String.
      Sorter<String> strSorter;

      // Assign to sorter a new StringSorterLexico object and have it
      // sort args[].
      strSorter = new StringSorterLexico();
      strSorter.sort(args);

      // Display the sorted command line arguments (which will be
      // in ascending lexicograph order).
      System.out.println("After sorting into lexicographic order: " + 
                         Arrays.toString(args));

      // Now assign to sorter a new StringSorterLenLexico object and
      // have it sort args[].
      strSorter = new StringSorterLenLexico();
      strSorter.sort(args);

      // Display the sorted command line arguments (which will be
      // in ascending length-lexicographic order).
      System.out.println("After sorting into length-lexicographic order: " + 
                         Arrays.toString(args));

      // Create an array whose values correspond to the lengths of
      // the command line arguments.  
      Integer[] argLengths = new Integer[args.length];
      for (int i = 0; i != argLengths.length; i++) {
         argLengths[i] = args[i].length();
      }

      // Display the array.
      System.out.println("Original argsLength array: " + 
                         Arrays.toString(argLengths));

      // Create an object that can sort an array of type Integer[]
      // according to the absolute values of the elements.
      Sorter<Integer> intSorter = new IntSorterAbsVal();
      intSorter.sort(argLengths);

      // Display the sorted array.
      System.out.println("After sorting argsLength: " + 
                         Arrays.toString(argLengths));
   }
}