Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Ordered Linked-Lists

Java Programming, Lecture Notes # 75, Revised 01/16/98.

Preface

This lesson was originally written on December 18, 1997 using the JDK 1.1.3 download and documentation package.

Students in Prof. Baldwin's Intermediate Java Programming classes will be responsible for understanding all of the material in this lesson.

Introduction

A previous lesson developed a general-purpose linked-list class, from which a queue class and a stack class were subclassed. That lesson promised a subsequent lesson in which the linked-list class would be expanded to include the ability to create and maintain ordered lists.

The watchword these days is reuse, don't reinvent. Therefore, this lesson is not provided to encourage you to reinvent data structures that can be found in class libraries. Rather, this lesson is provided primarily as a review of much of what you should have learned in the Introductory Java Programming course at ACC.

If you are enrolled in Prof. Baldwin's Intermediate Java Programming course and have difficulty with any of the material in this lesson, you probably are not well-prepared for the Intermediate course.

While the program in this lesson does review much of what you should have learned in the Introductory course, there is much more that you should have learned that it doesn't review (Java arrays for example). Therefore, a complete understanding of the material in this lesson does not provide assurance that you learned everything that you should have learned in the Introductory course. In other words, an understanding of the material in this lesson is a "necessary but not sufficient" indicator of your readiness for the Intermediate course.

Ordered List

An ordered list is one in which the data is maintained in some specified order: numeric, alphabetic, alphanumeric, etc., and can be stored and retrieved on the basis of some key value.

Because of its poor random-access capabilities, a linked-list is not a particularly good underlying structure for an ordered list. However, the development of an ordered list using a linked-list does provide some good illustrations of important Java programming concepts (such as the use of interface types). Therefore, we will develop an ordered list using an upgraded version of our general-purpose linked list class (named RawList) that we developed in an earlier lesson.

Sample Program

This program upgrades the general-purpose linked-list that was developed in an earlier lesson by adding an interface and several new methods. Only the new material will be reviewed here. You are referred to the earlier lesson for a review of the original material.

This upgraded general-purpose linked-list is then subclassed to provide the ability to create objects that will manage an ordered list.

The data structures so produced operate with objects of the generic type Object and therefore, can be used to accommodate any type of Java object so long as that object is instantiated from a class that implements the required interface.

Note that the program in the previous lesson did not require implementation of an interface. This new requirement for an interface comes about as a result of the need to perform comparisons between objects to determine which is greater, less than, equal to, etc. The interface is used to require the class from which data objects are instantiated to provide methods by which objects of the class can compare themselves against other objects of the same class.

As I mentioned in the earlier lesson, in developing this program, I didn't give much thought to access control: public, private, protected, and package. It is not likely that you would want to use this code for any serious purpose, but if you do, you will need to review and probably upgrade the access control specifiers that were used.

This program did not receive the kind of exhaustive testing that should be applied to a program of this complexity, so if you do elect to use it for any serious purpose, you should test it thoroughly before using it.

The testing that was performed was performed using JDK 1.1.3 under Win95.

Interesting Code Fragments

As is often the case, we will break the program up into a set of interesting code fragments and discuss those fragments individually. A listing of the complete program is provided at the end of the lesson so that you can see all of the interesting code fragments in context.

The purpose of this program is to upgrade the program named List02 to add the capability to create and maintain ordered lists. The upgraded version of the program is named List03.

In addition to methods carried forward from the earlier version of the program, methods are added to the RawList class to support the insertion and removal of nodes interior to the list on the basis of the value of the data object encapsulated in the node.

Insertion is done on an ascending alphanumeric basis.

The insertion of nodes having duplicate data values is not allowed. An attempt to insert a duplicate data object causes an exception to be thrown.

The RawList class is then subclassed to provide a specialized class for instantiating objects of type OrderedList.

The prior ability to create Stack and Queue objects was not removed from the program. In all cases, the data structures so produced operate with objects of the generic type Object, and can accommodate objects of any class that implements the interface named List03A.

For an understanding of how this degree of generality is accomplished, it is suggested that you review Lesson 46 in the Introductory tutorial which is a lesson on interfaces.

A test program is provided that tests only the OrderedList capability of the program. The output from running this program is shown in the comments of the complete program listing at the end of the lesson.

As mentioned above, this data structure can accommodate any object that is instantiated from a class that implements the interface named List03A. The first interesting code fragment is the definition of that interface.
 
public interface List03A{
  boolean LTE(Object inObject);
  boolean LT(Object inObject);
  boolean GTE(Object inObject);
  boolean EQ(Object inObject);   
}//end List03A
As you can see, this interface declares four methods that must be defined by any class that implements the interface. The purpose of these methods is to compare two objects of the interface type to determine which is greater, equal, less than, etc.

The names of the methods are indicative of the type of comparison required. For example the method named GTE should compare two objects to determine if the object on which the method is invoked is greater than or equal to the object passed as a parameter. (This is the point where C++ programmers will be wishing for overloaded operators which are not supported by Java).

In particular, this program depends on methods defined in the class of the object being maintained in the ordered list to compare two objects and report back which is greater, less than, etc..

As mentioned earlier, a more-detailed discussion of this kind of operation was provided in Lesson 46 on interfaces. If you have any confusion or questions in this regard, you should refer back to that lesson.

The next interesting code fragment shows the definition of the test class that was used to test this program. In particular note how this class implements the required interface.

This set of data structures is designed to accommodate objects of any class that implements the interface named List03A. This class named TestClass implements that interface. Therefore, objects of this class are appropriate for use with this set of data structures.

If you modify the definition of this class, you may need to redefine the methods in the new class to make them appropriate for your new class.

Note that the comparison methods in this class receive a parameter which is an object of type Object and then cast that object to type TestClass in order to be able to access the instance variable in the object. This methodology is explained more fully in Lesson 46 on Interfaces.

An object of this class contains only a single instance variable that is an object of type String, but there is no reason that it could not contain many instance variables such as first name, last name, social security number, age, weight, height, marital status, etc.
 
class TestClass implements List03A{
  String data;
  
  TestClass(String data){//constructor
    this.data = data;
  }//end constructor
    
  //Compare for less than or equal
  public boolean LTE(Object inObject){
    int result = 
           this.data.compareTo(((TestClass)inObject).data);
    if(result <= 0) return true;//is LTE
    else return false;//is not LTE
  }//end LTE
  
  //...       
}//end TestClass
However, whatever the class contains in terms of instance variables, the designer of the class must determine how two objects of the class are to be compared in each required method.

For example, if the class contained both the last name and the age, it might be appropriate to use the last name variable to determine which is greater, and it might be just as appropriate to use the age variable for that purpose. It might be appropriate to combine two or more instance variables such as last name and first name for that purpose.

The designer of the class must decide. The linked-list program depends on the methods of the class to provide that kind of information whenever it is needed.

In this case, for the sake of simplicity, we use the single String data member to compare objects of the class, and we use the compareTo() method of the String class to assist us in that regard. Only one of the four comparison methods is shown here because they are all very similar.

The class also contains an overridden toString() method, but it is not shown here because there is nothing new about it.

That brings us to the new method that inserts a new node into the linked-list on the basis of the value of the data object encapsulated into the node. As mentioned earlier, nodes are inserted into the list in ascending order.

This is a large method so we will discuss it in parts. The first interesting part is the method signature and the initial test to determine if the list is empty. If it is determined that the list is empty, the node being inserted becomes the first node in the list.

Note that this method receives a new data object as type Object. Note also that it throws an exception of type Excep.
 
  void inByValue(Object dataObj) throws Excep{
    if(isEmpty()) //List is empty, make this the first node
      firstNode = lastNode = getNode(dataObj);
This method does not allow the insertion of duplicate data objects, and throws an exception if an attempt is made to insert a data object that matches a data object already encapsulated in one of the nodes.

The next interesting code fragment throws an exception if the new node matches the first or last nodes in the list.

Note the requirement to cast the incoming object of type Object to the type of the interface in order to make it possible to access the method named EQ which is declared in the interface and is an instance method of the incoming object.

You should recall that when the exception object is thrown, execution of the code in the method is terminated immediately and the system starts searching for an exception handler whose type matches the type of the exception. Control is transferred directly to that exception handler, bypassing any remaining code in the method.
 
    else if(((List03A)dataObj).EQ(firstNode.dataObj)) 
        throw new Excep("Duplicate key value: " + dataObj);
    else if(((List03A)dataObj).EQ(lastNode.dataObj)) 
        throw new Excep("Duplicate key value: " + dataObj);
Next we need to handle the two cases where the new data object is less than the first data object in the list or greater than the last data object in the list. If either of these two cases is true, we encapsulate the new data object in a node and attach the node to the front or the back of the list.

Also note that even though we used the GTE method to test for greater than the last object in the list, the object cannot possibly be equal to the last object because we eliminated that possibility earlier. If you go back and review the interface definition, you will note that it doesn't include a declaration for a greater than (GT) method. So, we saved a little programming effort here, but that may have been false economy since it definitely makes the code less self-documenting and possibly more difficult to maintain.
 
    else if(((List03A)dataObj).LT(firstNode.dataObj)) 
      this.toFront(dataObj);
      
    else if(((List03A)dataObj).GTE(lastNode.dataObj))
      this.toBack(dataObj);
Having reached the point where we were not able to attach the new data object to either end of the list, we must encapsulate the new data object in a node object and search the interior of the list to find the spot where it fits on the basis of its value. Having found that spot, we must rewire the references so as to insert it at that spot.

Along the way, we need to be watching out for duplicate values and throw an exception if a duplicate if found.

As in previous methods that needed to traverse the list, we will use a while loop to traverse the list until the appropriate spot is found. We will then insert it, rewiring the reference variables as required.

Recall that the occurrence of a null reference to the next node indicates that the end of the list has been reached. Since we have already tested to see if we could hook it onto either end, we should find an appropriate place to insert it before reaching the end of the list.

After encapsulating the new data object in a node object and saving a reference to the node object in newPtr, we drop into our while loop.

During each iteration of the loop, we first test for duplicate and throw an exception of a duplicate is discovered.

Then we test to see if we have found the spot to insert the new node so that it will be greater than one node and less than the next node. Again note that even though we are using GTE, it cannot be equal because we eliminated that possibility when we tested for the duplicate.

Once we find the correct spot, we rewire the reference variables and break out of the loop. By now you should have no difficulty understanding the rewiring process.
 
    else {
      Node newPtr = getNode(dataObj);//encapsulate object
      Node currentRefToNode = firstNode;//temp reference

      while(currentRefToNode.nextNode != null){    
        if((((List03A)dataObj).EQ(//test for duplicate
            currentRefToNode.nextNode.dataObj)))
              throw new Excep("Duplicate key value: " 
                  + dataObj);

        if((((List03A)dataObj).GTE(//is this the spot?
            currentRefToNode.dataObj)) && 
                (((List03A)dataObj).LT(
                    currentRefToNode.nextNode.dataObj)))
        {//Found the spot.  Rewire the reference variables
          newPtr.nextNode = currentRefToNode.nextNode;
          currentRefToNode.nextNode = newPtr;
          break; //terminate loop
        }//end if
        //Didn't find the spot.  Update reference variable
        // and go back to the top of the while loop.
        else currentRefToNode = currentRefToNode.nextNode;
      }//end while loop
    }//end else have to find a spot and insert in middle
  }//end inByValue()
If we didn't find the spot on the current iteration, we need to update currentRefToNode, go back to the top of the loop, and try again.

There should be no circumstance where the while loop in the above method would terminate naturally.

Before entering the loop, we confirmed that the value of the data object is bracketed by the values of the data objects encapsulated in the first and last nodes in the list. That means that we should either find a duplicate (in which case we throw an exception) or should find a spot to insert the new node (in which case we break out of the loop).

This finishes our discussion of the method that is used to insert data objects by value into the linked list.

The next interesting code fragment is the method that is used to remove a data object from the linked list by matching the value of a key data object with the value of a data object already encapsulated in a node in the list.

Obviously this method may fail to find a match in which case we will throw an exception.

Again, this is a fairly long method so we will discuss it in parts. The first interesting part is the signature and the test for an empty list. As usual, we will throw an exception if the list is empty.

To be repetitive, note that this method receives a data object as the generic type Object.
 
  Object removeByValue(Object dataObj)  throws Excep{
    if(this.isEmpty()) //List is empty
      throw new Excep("Empty List in removeByValue");
As usual, if the list is not empty, the next code fragment deals with the trivial cases of the key value matching the value of the data object encapsulated in either the first or the last node. By now you should have code of this variety memorized.

It will be useful to point out that this code fragment makes use of methods that were developed in the previous lesson (fetchFromFront and fetchFromBack) so if you just tuned in at this point, you may need to go back and review that lesson.
 
    else{  //Test for a match on the front node
    if(((List03A)dataObj).EQ(firstNode.dataObj)){
      return fetchFromFront();//found match on front node
    //Test for match on back node      
    }else if(((List03A)dataObj).EQ(lastNode.dataObj)){
      return fetchFromBack();//found match on back node
Later in the discussion, we will indicate that our program would probably be more efficient if we were to test at this point to determine if the value of the data object is bracketed by the values of the data objects encapsulated by the first and last nodes, and to throw an exception if it is not bracketed. This would probably eliminate a lot of needless searching, but we didn't recognize that need and didn't implement that capability when we wrote the program.

If we don't find a match at the front or the back, we will search for a match interior to the list. As usual, we will use a while loop to traverse the list searching for a match. This time, however, we could hit the end of the list indicated by lastNode if no match is found. (This possibility would have been eliminated if we had done the bracket thing described above.)

By now you should have no difficulty understanding how this code works.

There are two different ways that we can decide that there is no match.

If we pass the appropriate point in the list based on value and haven't found a match, we will throw an exception. This prevents us from searching the entire list when we can make an early determination that a match doesn't exist.

Also, if the value is not bracketed by the values of the end points of the list, we could hit the end of the list without finding a match, which will also cause us to throw an exception.

A more efficient approach would be to test for the bracket condition before beginning the search of the interior of the list and throwing an exception if the value were not bracketed by the end points of the list.
 
        Node currentRefToNode = firstNode,tempRefToNode;
        
        while( currentRefToNode.nextNode != lastNode){
          //test to see if value of dataObj has been passed
          if(((List03A)dataObj).
                              LT(currentRefToNode.dataObj))
            //Can't find a match, throw exception
            throw new Excep(
                        "No match found in removeByValue");
  
          //Test to see if the dataObj in the next node 
          // matches.  If so, delete next node.
          if(((List03A)dataObj).
                    EQ(currentRefToNode.nextNode.dataObj)){
            //Save ref to node being removed
            tempRefToNode = currentRefToNode.nextNode;
            //Wire around the next node to remove it.
            currentRefToNode.nextNode = 
                    ((currentRefToNode.nextNode).nextNode);
            //Return data object from saved node
            return tempRefToNode.dataObj;
          }//end if where match was found
          
          //Adjust references to move to next node
          currentRefToNode = currentRefToNode.nextNode; 
          //Test to see if the next node is the last node. 
          if( currentRefToNode.nextNode == lastNode ){
            throw new Excep(
                        "No match found in removeByValue");
          }}}}//end else
    throw new Excep(
           "Should never get this far before returning\n");
  }//end function
As was the case in the earlier lesson where we needed to remove the last node in the list, in this case we need to be looking ahead to see if the key value matches the data object encapsulated in the next node. Since this list is a one-way street, we can't go backwards, so we need to recognize the match before we are standing on the node to be removed.

Once we determine that the next node is a match, we save a reference to that node in tempRefToNode for use later in our return statement. Then we wire around the node to remove it from the list and return a reference to the data object encapsulated in the node.

If the current iteration doesn't produce a match, we update the reference named currentRefToNode so as to move us to the next node on the next iteration.

However, before returning to the top of the while loop for the next iteration, we need to test to see if the next node is the last node, and if so we conclude that there is no match and throw an exception.

Finally, as a safety valve, we throw an exception if control ever reaches the end of the method without either returning the matching data object, or throwing an exception, because this should never happen.

And that concludes the discussion of the method to remove a node based on matching a key value.

The next interesting code fragment is the definition of the class used to subclass the RawList class in such a way as to create and maintain a list that keeps the data objects in ascending alphanumeric order in the list.

This is accomplished by using the methods of the RawList class to insert new nodes by the value of the data encapsulated in the node and removing nodes by value.
 
class OrderedList extends RawList{
  
  public void add(Object obj) throws Excep{
    //add a new object to the list by value
    this.inByValue(obj);
  }//end add()
    
  public Object remove(Object obj) throws Excep{
    //remove and return an object from the list by value
    return this.removeByValue(obj);
  }//end remove    
    
  public void printOrderedList(){
    this.printRawList();//use the existing print capability
  }//end printOrderedList()
    
  public boolean isOrderedListEmpty(){
    return this.isEmpty();//use the existing empty test
  }//end isOrderedListEmpty    
  
}//end OrderedList
As we saw in the previous lesson where we created the queue and stack classes, once we have the properly designed RawList class, extending that class to produce our OrderedList class is so simple that it is almost trivial.

This class makes it possible to add and remove objects from the list based on their value, and also to print the list and to determine if it is empty. Except for the class header and the method signatures, only four lines of code were required to accomplish this.

We will note, however, that a significant programming effort was required to upgrade the RawList class to make this possible.

Test Program

A method named test() is provided with this program to test the operation of the OrderedList class.You can view that code, along with the output produced by invoking the test method, in the program listing in the next section.

Program Listing

A complete listing of the program follows so that you can view the entire program in context.
 
/*File List03.java
Copyright 1997, R.G.Baldwin

The purpose of this program is to upgrade the program named
List02.java to add the capability to create and maintain
ordered lists.

In addition to methods carried forward from the earlier
version of the program, methods are added to the RawList 
class to support the insertion and removal of nodes 
interior to the list on the basis of the value of the data
object encapsulated in the node. 

Insertion is done on an ascending alphanumeric basis. The 
insertion of nodes having duplicate data values is not 
allowed.  An attempt to insert a duplicate value causes
an exception to be thrown.

The RawList class is then subclassed to provide a 
specialized data structures for creating Ordered List 
objects.

The prior ability to create Stack and Queue objects was
not removed from the program.
  
In all cases, the data structures so produced operate
with objects of the generic type Object, and can 
accommodate objects of any class that implements the
interface named List03A.

For an understanding of how this degree of generality is 
accomplished, it is suggested that you review Lesson #46 
which is a lesson on interfaces.

The test program provided in this program tests only the
Ordered List capability of the program.  The output from 
running this program is shown below:
//------------------------------------------

Test the ordered list
Put some data objects in an ordered list
Try to put duplicate object in ordered list
Exception: Duplicate key value: cd
The ordered list contains:
ab
cd
ef
gh
ij
kk
kl

Remove some objects from ordered list
Removed: kl
Removed: ab
Removed: gh
The ordered list now contains:
cd
ef
ij
kk
Try to remove object with no match, cf
Exception: No match found in removeByValue
Try to remove another object with no match, kj
Exception: No match found in removeByValue
End of test
//------------------------------------------
        
This program was tested using JDK 1.1.3 under Win95.
**********************************************************/

import java.awt.*;
import java.awt.event.*;
import java.util.*;
//=======================================================//

/*This program implements the following kinds of data
structures:  unordered linked-list, ordered linked-list,
stack, and queue.

This set of data structures is designed to accommodate
objects of any class that implements the interface named
List03A.
 
This class named TestClass implements that interface.  
Therefore, objects of this class are appropriate for use 
with this set of data structures.
 
If you modify the definition of this class, you will need 
to redefine the methods in the new class to make them 
appropriate for your new class.  The compare methods are 
required for use by the methods in the data structures 
that need to be able to compare two objects for less 
than, equal, etc.  Generally, these are the methods that
support the ordered list structure.

Note that the comparison methods in this class receive
a parameter which is an object of type Object and then
cast that object to type TestClass in order to be able
to access the instance variable in the object.  This
methodology is explained more fully in Lesson #46 on
Interfaces.
---------------------------------------------------------*/

class TestClass implements List03A{
  //An object of this class contains a single instance
  // variable which is an object of type String.
  String data;
  //-----------------------------------------------------//
  
  TestClass(String data){//constructor
    this.data = data;
  }//end constructor
  //-----------------------------------------------------//
  
  public String toString(){//overridden toString() method
    return (data);
  }//end toString()
  //-----------------------------------------------------//
    
  //Compare for less than or equal
  public boolean LTE(Object inObject){
    int result = 
           this.data.compareTo(((TestClass)inObject).data);
    if(result <= 0) return true;//is LTE
    else return false;//is not LTE
  }//end LTE
  //-----------------------------------------------------//
  
  //Compare for less than
  public boolean LT(Object inObject){
    int result = 
           this.data.compareTo(((TestClass)inObject).data);
    if(result < 0) return true;//is LT
    else return false;//is not LT
  }//end LT
  //-----------------------------------------------------//

  //Compare for greater than or equal
  public boolean GTE(Object inObject){
    int result = 
           this.data.compareTo(((TestClass)inObject).data);
    if(result >= 0) return true;//is GTE
    else return false;//is not GTE
  }//end GTE
  //-----------------------------------------------------//
  
  //Compare for equal
  public boolean EQ(Object inObject){
    int result = 
           this.data.compareTo(((TestClass)inObject).data);
    if(result == 0) return true;//is equal
    else return false;//is not equal
  }//end EQ
       
}//end TestClass
//=======================================================//

/*This class is the controlling class which is used to test
the data structures developed in this lesson.  This
class contains a method named test() which designed to
exercise the capabilities of the unordered linked-list,
stack, queue, and ordered list.
---------------------------------------------------------*/

class List03{//controlling class
  public static void main(String[] args){//main
    List03 obj = new List03();//instantiate this object  
    obj.test();//invoke the method named test()
  }//end main
  //-----------------------------------------------------//
  
  void test(){
    System.out.println("\nTest the ordered list");
    //Instantiate an OrderedList object
    OrderedList myOrderedList = new OrderedList();
    System.out.println(
               "Put some data objects in an ordered list");
    try{
      myOrderedList.add(new TestClass("cd"));
      myOrderedList.add(new TestClass("ij"));
      myOrderedList.add(new TestClass("ab"));
      myOrderedList.add(new TestClass("kl"));
      myOrderedList.add(new TestClass("kk"));
      myOrderedList.add(new TestClass("ef"));
      myOrderedList.add(new TestClass("gh"));
      System.out.println(
            "Try to put duplicate object in ordered list");
      myOrderedList.add(new TestClass("cd"));
    }catch(Excep e){System.out.println("Exception: " + e);}

    System.out.println("The ordered list contains:");
    myOrderedList.printOrderedList();

    System.out.println(
                "\nRemove some objects from ordered list");
    try{
      System.out.println("Removed: " 
              + myOrderedList.remove(new TestClass("kl")));
      System.out.println("Removed: " 
              + myOrderedList.remove(new TestClass("ab")));
      System.out.println("Removed: " 
              + myOrderedList.remove(new TestClass("gh")));
    }catch(Excep e){System.out.println("Exception: " + e);}

    System.out.println("The ordered list now contains:");
    myOrderedList.printOrderedList();
    
    try{
      System.out.println(
                 "Try to remove object with no match, cf");
      System.out.println("Removed: " 
              + myOrderedList.remove(new TestClass("cf")));
    }catch(Excep e){System.out.println("Exception: " + e);}
    
    try{
      System.out.println(
         "Try to remove another object with no match, kj");
      System.out.println("Removed: " 
              + myOrderedList.remove(new TestClass("kj")));
    }catch(Excep e){System.out.println("Exception: " + e);}

    System.out.println("End of test");
  }//end test()
}//end controlling class named class02
//=======================================================//
//=======================================================//

//This is the beginning of the classes that are used to
// instantiate several different kinds of data structures.

//=======================================================//
//=======================================================//

//This is a new exception class that is used to instantiate
// exception objects for a variety of different exceptional
// conditions within the data structure methods.

class Excep extends Exception{
  String diagnosticData;//put diagnostic data here
  
  Excep(){//NoArg constructor
    diagnosticData = "No diagnostic data provided";
  }//end NoArg constructor

  Excep(String diagnosticData){//parameterized constructor
    this.diagnosticData = diagnosticData;
  }//end NoArg constructor
  
  public String toString(){//override toString()
    return diagnosticData;
  }//end overridden toString()      
}//end class Excep
//=======================================================//

//This class is used to instantiate a node in the data
// structure.  It contains an embedded object of whatever
// type is passed in as a parameter.  The test class
// provide with this program uses objects of the class
// namedTestClass.

class Node{
  Object dataObj;  //data object is stored here
  Node nextNode;//reference to the next node in the RawList
  //-----------------------------------------------------//
  
  public Node(Object dataObj){//constructor
    this.dataObj = dataObj;//store incoming dataObj
  }//end constructor
}//end class Node
//=======================================================//

//Begin definition of the class used to create
//and maintain a raw list
class RawList{
  private Node firstNode;  //reference to first node
  private Node lastNode;   //reference to last node

  //-----------------------------------------------------//
  
  //Function to allocate memory and return a reference 
  // variable for a new node.
  private Node getNode( Object dataObj)  {
    //get reference variable to new memory
    Node newNode = new Node(dataObj);
    return newNode;
  }//end getNode()
  //-----------------------------------------------------//
  
  //Method to determine if The structure is empty
  boolean isEmpty(){
    return firstNode == null;//return true if empty
  }//end isEmpty()
  //-----------------------------------------------------//
  
  //Attach a new node to the front of the RawList
  void toFront(Object dataObj){
    //Encapsulate the incoming object in an object of type
    // node and assign it to a local reference variable.
    Node newNode = this.getNode(dataObj);
  
    //Now attach the new node to the front of the list  
    if(this.isEmpty()) //RawList is empty
      firstNode = lastNode = newNode;
    else{ //RawList is not empty
      newNode.nextNode = firstNode;
      firstNode = newNode;
    }//end if
  }//end toFront()
  //-----------------------------------------------------//
  
  //Attach a new node to the back of the RawList
  void toBack(Object dataObj){
    //Encapsulate the incoming object in an object of type
    // node and assign it to a local reference variable.
    Node newNode = this.getNode(dataObj);
  
    //Now attach the new node to the back of the list  
    if(this.isEmpty()) //RawList is empty
      firstNode = lastNode = newNode;
    else { //RawList is not empty
      lastNode.nextNode = newNode;
      lastNode = newNode;
    }//end if
  }//end toBack()
  //-----------------------------------------------------//
  
  //This method inserts a new node into the linked-list
  // on the basis of its value.  Nodes are assembled into
  // the list in ascending order.
    
  void inByValue(Object dataObj) throws Excep{
    if(isEmpty()) //List is empty, make this the first node
      firstNode = lastNode = getNode(dataObj);

    //Throw an exception if the new node matches the first
    // or last nodes in the list.  Duplicate values are
    // not allowed in the list.  Note the requirement to
    // cast the incoming object of type Object to the
    // type of the interface in order to make it possible
    // to access the method named EQ which is declared in
    // the interface and is an instance method of the 
    // incoming object
    else if(((List03A)dataObj).EQ(firstNode.dataObj)) 
        throw new Excep("Duplicate key value: " + dataObj);
    else if(((List03A)dataObj).EQ(lastNode.dataObj)) 
        throw new Excep("Duplicate key value: " + dataObj);
    
    //If smaller than data in first node, attach to front.
    // Note that this process uses the existing method
    // to encapsulate the data object in a node object
    // attach it to the front of the list.
    else if(((List03A)dataObj).LT(firstNode.dataObj)) 
      this.toFront(dataObj);
      
    //if greater than or equal (can't possibly be equal
    // because that possibility was eliminated above) to
    // dataObj in last node, attach at the back.
    else if(((List03A)dataObj).GTE(lastNode.dataObj))
      this.toBack(dataObj);

    //Didn't attach to either end.  Must encapsulate the
    // incoming data object in a node object, find a spot 
    // and insert the node somewhere in the middle of the 
    // list.
    else {
      //Encapsulate the incoming object in an object of 
      // type node and assign it to a local reference 
      // variable.      
      Node newPtr = getNode(dataObj);
      //Create a temp reference and initialize it to 
      // reference the front of the list
      Node currentRefToNode = firstNode;

      //At this point, we have established that there is
      // a spot somewhere in the list where the node should
      // be inserted unless the data object is a duplicate
      // of an existing data object in the list.  Use a 
      // while loop to traverse the list until that spot 
      // is found.  Then insert it, rewiring the reference
      // variables as required. The occurrence of a null 
      // reference to the next node indicates that the end
      // of the list has been reached.  Since we have 
      // already tested to see if we could hook it onto
      // either end, we should find an appropriate place
      // to insert it before reaching the end of the list.
      while(currentRefToNode.nextNode != null){
        //Test for duplicate values and throw an exception
        // if a duplicate is discovered.      
        if((((List03A)dataObj).EQ(
            currentRefToNode.nextNode.dataObj)))
              throw new Excep("Duplicate key value: " 
                  + dataObj);
        //Test to see if this is the spot to insert the
        // new node so that it will be greater than one
        // node and less than the next node.  Again note
        // that even though we are using GTE, it cannot
        // be equal because we eliminated that possibility
        // above.                  
        if((((List03A)dataObj).GTE(
            currentRefToNode.dataObj)) && 
                (((List03A)dataObj).LT(
                    currentRefToNode.nextNode.dataObj)))
        {//Found the spot.  Rewire the reference variables
          newPtr.nextNode = currentRefToNode.nextNode;
          currentRefToNode.nextNode = newPtr;
          break; //terminate loop
        }//end if
        //Didn't find the spot.  Update reference variable
        // and go back to the top of the while loop.
        else currentRefToNode = currentRefToNode.nextNode;
      }//end while loop
    }//end else have to find a spot and insert in middle
  }//end inByValue()
  //-----------------------------------------------------//

  //This method is used to remove a node based on a match
  // with a key value.  
  Object removeByValue(Object dataObj)  throws Excep{
    if(this.isEmpty()) //List is empty
      throw new Excep("Empty List in removeByValue");
    //List is not empty
    else{  //Test for a match on the front node
    if(((List03A)dataObj).EQ(firstNode.dataObj)){
      return fetchFromFront();//found match on front node
    //Test for match on back node      
    }else if(((List03A)dataObj).EQ(lastNode.dataObj)){
      return fetchFromBack();//found match on back node
      //No match on the front or back.  Have to search for
      // a match somewhere in the middle of the list.      
      }  else{//have to find it in the middle of the list
        //declare temp reference variables
        Node currentRefToNode = firstNode,tempRefToNode;
        
        //Use a while loop to traverse the list searching
        // for a match.  This time, we could hit the end
        // of the list indicated by lastNode if no
        // match is found.
        while( currentRefToNode.nextNode != lastNode){
          //test to see if value of dataObj has been passed
          if(((List03A)dataObj).
                              LT(currentRefToNode.dataObj))
            //Can't find a match, throw exception
            throw new Excep(
                        "No match found in removeByValue");
  
          //Test to see if the dataObj in the next node 
          // matches.  If so, delete next node.
          if(((List03A)dataObj).
                    EQ(currentRefToNode.nextNode.dataObj)){
            //Save ref to node being removed
            tempRefToNode = currentRefToNode.nextNode;
            //Adjust ref variable to wire around the next
            // node which is being removed.
            currentRefToNode.nextNode = 
                    ((currentRefToNode.nextNode).nextNode);
            //Return the saved node
            return tempRefToNode.dataObj;
          }//end if where match was found
          
          //Adjust references to move to next node
          currentRefToNode = currentRefToNode.nextNode; 
          //Test to see if the next node is the last node. 
          // If so, this means that we can't find a match 
          // and should throw an exception without letting
          // the while loop terminate.
          if( currentRefToNode.nextNode == lastNode ){
            throw new Excep(
                        "No match found in removeByValue");
          }//end if
        }//end while loop
      }//end else
    }//end else
    throw new Excep(
           "Should never get this far before returning\n");
  }//end function
  //-----------------------------------------------------//
  
  //This method is used to fetch and delete a node from 
  // the front of the RawList.  Note that all objects are
  // treated as objects of the generic type Object.
  Object fetchFromFront() throws Excep{
    if(this.isEmpty()) //RawList is empty
      throw new Excep("Empty list in fetchFromFront");
    else { //RawList is not empty
      //declare and initialize a local reference variable
      Node tempRefToNode = firstNode;
  
      if(firstNode == lastNode)//only one node in the list
        firstNode = lastNode = null; //set both to null
      else//more than one node in the list
        //Wire around the first node and return it
        firstNode = firstNode.nextNode;
      return tempRefToNode.dataObj;  //fetch successful
    }//end else
  }//end fetchFromFront()
  //-----------------------------------------------------//
  
  //This method is used to fetch and delete a node from the
  // back of the RawList
  Object fetchFromBack() throws Excep{
    if(this.isEmpty()) //RawList is empty
      throw new Excep("Empty list in fetchFromBack");
    else { //RawList is not empty
      //declare and initialize a local reference variable
      Node tempRefToNode = lastNode;
  
      if(firstNode == lastNode)//only one node in the list
        firstNode = lastNode = null;  //set both to null
      else {//more than one node in the list
        //Declare and initialize another local 
        // reference variable
        Node currentRefToNode = firstNode;
        
        //The list is a one-way street.  The last node can
        // only be removed by starting at the front and
        // walking to the end touching each node along the
        // way.  Use a while loop to traverse the list,
        // stopping at the node immediately before the
        // last one.
        while(currentRefToNode.nextNode != lastNode)
          currentRefToNode = currentRefToNode.nextNode;

        //Cut the last node loose and set the reference
        // to the next node in the new last node to null
        // to indicate the new end of the list.
        lastNode = currentRefToNode;
        currentRefToNode.nextNode = null;
      }//end else
  
      //Return the data object from the saved last node.
      return tempRefToNode.dataObj;  //fetch successful
    }//end else
  }//end fetchFromBack()
  //-----------------------------------------------------//
  
  //This method is used to display the contents of the 
  // RawList object.
  void printRawList(){
    if(this.isEmpty()){
      System.out.println("Empty");
      return;
    }//end if
  
    //Not empty.  Declare and initialize a local 
    // reference variable to the first node in the list
    Node currentRefToNode = firstNode;

    //Use a while loop to traverse the list  displaying
    // the data object in each node along the way.
    while(currentRefToNode != null){
      System.out.println("" + currentRefToNode.dataObj);
      currentRefToNode = currentRefToNode.nextNode;
    }//end while
  }//end printRawList()
}//end class RawList
//=======================================================//

//The above class was used to provide the raw list which
// serves as a superclass for the following specialized
// subclasses.
//=======================================================//

//This class subclasses the class named RawList in such
// a way as to provide queue behavior.  A queue is a 
// first-in/first-out structure.  This can be accomplished
// by entering data into the back of a RawList object and
// removing it from the front of the object.

//As you can see, this is a very simple class.  It simply
// invokes the methods of the RawList class on a selective
// basis.
class MyQueue extends RawList{
  public void enqueue(Object obj){
    this.toBack(obj);//enqueue data to the back of the list
  }//end enqueue()
    
  public Object dequeue() throws Excep{
    //dequeue data from the front of the list
    return this.fetchFromFront();
  }//end dequeue()
    
  public void printQueue(){
    this.printRawList();//use the existing print capability
  }//end printQueue()
    
  public boolean isQueueEmpty(){
    return this.isEmpty();//use the existing empty test
  }//end isQueueEmpty    
}//end class MyQueue
//=======================================================//

//This class is used to subclass the RawList class in
// such a way as to provide stack behavior.  A stack is a
// last-in/first-out structure.  This can be accomplished
// by attaching data to the front of the list and
// removing it from the front of the list.
class MyStack extends RawList{
  public void push(Object obj){
    this.toFront(obj);//attach new data to front of list
  }//end push

  public Object pop() throws Excep{
    //remove new data from the front of the list
    return this.fetchFromFront();
  }//end pop()
    
  public void printStack(){
    this.printRawList();//use existing print capability
  }//end printStack()
    
  public boolean isStackEmpty(){
    return this.isEmpty();//use the existing empty test
  }//end isStackEmpty    
}//end class MyStack
//=======================================================//

//This class is used subclass the RawList class in such
// a way as to to create and maintain a list that
// keeps the data objects in ascending alphanumeric 
// order in the list.  This can be accomplished by using
// the existing methods to insert new nodes by the value 
// of the data encapsulated in the node and removing 
// nodes by value.  
class OrderedList extends RawList{
  
  public void add(Object obj) throws Excep{
    //add a new object to the list by value
    this.inByValue(obj);
  }//end add()
    
  public Object remove(Object obj) throws Excep{
    //remove and return an object from the list by value
    return this.removeByValue(obj);
  }//end remove    
    
  public void printOrderedList(){
    this.printRawList();//use the existing print capability
  }//end printOrderedList()
    
  public boolean isOrderedListEmpty(){
    return this.isEmpty();//use the existing empty test
  }//end isOrderedListEmpty    
  
}//end OrderedList
//=======================================================//

Is A versus Has A

As was the case in the previous lesson on link-lists, stacks, and queues, this program has a major flaw. In particular, it is possible to instantiate an object of the subclass named OrderedList and to use that object to invoke methods of the RawList class that violate the ordered access requirement of the OrderedList class. This is because the OrderedList class inherits the RawList class making the methods of the RawList class available to code within the scope of an object of type OrderedList. Thus, the OrderedList class "is a" type of RawList.

As before, the solution is a simple one: change the relationship of OrderedList and RawList from one of inheritance to one of composition. In other words, change from an "is a" relationship to a "has a" relationship.

What this means is to eliminate the inheritance relationship, and to make an instance variable in the OrderedList class of type RawList. This will hide the methods of the RawList class from other code within the scope of an OrderedList object, but make those methods available to code within the methods of the OrderedList class.

Hopefully you now understand the distinction between an inheritance relationship ("is a") and a composition relationship ("has a"). Correcting the flaw in this program will be left as an exercise for the student.

-end-