Recall our previous example of a linked list that stored any Object: (source file)
class ListItem { Object data = null; ListItem next = null; // ... } // The linked list class. class LinkedList { ListItem front = null; // Data. ListItem rear = null; int numItems = 0; // Instance method to add a data item. public void addData (Object obj) { // ... } public void printList () { // ... } } // End of class "LinkedList" // An object to use in the list: class Person { String name; String ssn; // ... } // Test class. class TestList4 { public static void main (String[] argv) { // Create a new list object. LinkedList L = new LinkedList (); // Create Person instances and add to list. L.addData (new Person ("Rogue", "1111-12-1212")); L.addData (new Person ("Storm", "222-23-2323")); L.addData (new Person ("Black Widow", "333-34-3434")); L.addData (new Person ("Jean Grey", "888-89-8989")); // Print contents. L.printList(); } }
Suppose we want to create a linked list that is sorted:
class ComparableObject { public int compare (ComparableObject c) { // List objects should overide this function. } }
Here the comparison returns
class LinkedList { //... // Instance method to add a data item. public void addData (ComparableObject obj) { // Walk through list and find right // place to insert obj. } }
class Person extends ComparableObject { String name; String ssn; // ... public int compare (ComparableObject obj) { Person p = (Person) ComparableObject obj; // ... compare p.name and name ... } }
abstract class ComparableObject { public abstract int compare (ComparableObject c); }
Declaring a method
About abstract classes:
With this idea, let's write the code for the sorted list: (source file)
abstract class ComparableObject { public abstract int compare (ComparableObject c); } class ListItem { ComparableObject data = null; ListItem next = null; // Constructor. public ListItem (ComparableObject obj) { data = obj; next = null; } // Accessor. public ComparableObject getData () { return data; } } // Linked list class - also a dynamic class. class LinkedList { ListItem front = null; ListItem rear = null; int numItems = 0; // Current number of items. // Instance method to add a data item. public void addData (ComparableObject obj) { if (front == null) { front = new ListItem (obj); rear = front; } else { // Find the right place. ListItem tempPtr=front, prevPtr=front; boolean found = false; while ( (!found) && (tempPtr != null) ) { if (tempPtr.data.compare(obj) > 0) { // Note use of // compare method found = true; break; } prevPtr = tempPtr; tempPtr = tempPtr.next; } // Now insert. if (!found) { // Insert at rear. rear.next = new ListItem (obj); rear = rear.next; } else if (tempPtr == front) { // Insert in front. ListItem Lptr = new ListItem (obj); Lptr.next = front; front = Lptr; } else { // Insert in the middle. ListItem Lptr = new ListItem (obj); prevPtr.next = Lptr; Lptr.next = tempPtr; } } numItems++; } public void printList () { ListItem listPtr = front; System.out.println ("List: (" + numItems + " items)"); int i = 1; while (listPtr != null) { System.out.println ("Item# " + i + ": " + listPtr.getData()); // Must implement toString() i++; listPtr = listPtr.next; } } } // End of class "LinkedList" // An object to use in the list: class Person extends ComparableObject { String name; String ssn; // Constructor. public Person (String nameInit, String ssnInit) { name = nameInit; ssn = ssnInit; } // Override toString() public String toString () { return "Person: name=" + name + ", ssn=" + ssn; } // Must implement compare public int compare (ComparableObject obj) { Person p = (Person) obj; return name.compareTo (p.name); } } // End of class "Person" // Test class. public class SortedList { public static void main (String[] argv) { // Create an instance of the list. LinkedList L = new LinkedList (); // Insert data. L.addData (new Person ("Rogue", "1111-12-1212")); L.addData (new Person ("Storm", "222-23-2323")); L.addData (new Person ("Black Widow", "333-34-3434")); L.addData (new Person ("Jean Grey", "888-89-8989")); // Print contents. L.printList(); } }
Exercise 7.1: Download and examine SortedList.java. Draw a complete memory picture after the first three Person instances have been inserted. Then, trace through what happens on the stack all the way from main() to when the fourth Person. instance in added to the list.
Note:
class ListItem { ComparableObject data; = null; ListItem next; // ... }
if (tempPtr.data.compare(obj) > 0) { // ... }
class Person extends ComparableObject { // ... }
class Person extends ComparableObject { // ... public int compare (ComparableObject obj) { Person p = (Person) obj; return name.compareTo (p.name); } }
Note that we end up using the
compareTo()
method of
String.
The output in this case is in sorted order, even though the
elements were not inserted in this order:
public final class String {
// ...
public int compareTo (String s)
{
// ...
}
}
// Create an instance of the list.
LinkedList L = new LinkedList ();
// Insert data.
L.addData (new Person ("Rogue", "1111-12-1212"));
L.addData (new Person ("Storm", "222-23-2323"));
L.addData (new Person ("Black Widow", "333-34-3434"));
L.addData (new Person ("Jean Grey", "888-89-8989"));
// Print contents.
L.printList();
Item# 1: Person: name=Black Widow, ssn=333-34-3434
Item# 2: Person: name=Jean Grey, ssn=888-89-8989
Item# 3: Person: name=Rogue, ssn=1111-12-1212
Item# 4: Person: name=Storm, ssn=222-23-2323
Note:
abstract class ComparableObject { public abstract String toString (); public abstract int compare (ComparableObject c); }
Next, we will provide an Enumeration for our sorted list.
What does this mean?
public void startEnumeration() public boolean hasMoreElements() public Object nextElement()for your container (Example of container: linked list).
public hasMoreElements() public nextElement()This way, any container can implement an Enumeration and return an instance to the caller.
class LinkedList { // ... public Enumeration getEnumeration () { // ... } }that returns an Enumeration instance.
LinkedList L = new LinkedList (); // ... add data ... Enumeration e = L.getEnumeration(); while (e.hasMoreElements()) { Person p = (Person) e.nextElement(); System.out.println (p); }
How can we create an Enumeration object for our LinkedList?
The problem is, the Enumeration object should have access to the internals of our list to be able to "walk" along the list.
There are three ways to do this:
First, let us try the approach of creating a special Enumeration object: (source file)
// java.util has Enumeration import java.util.*; // An Enumeration object for LinkedList. // Since it is in this package and LinkedList // internals are not private, the LinkedList // variables are accessible. class ListEnumerator implements Enumeration { ListItem enumPtr; // Constructor. public ListEnumerator (ListItem front) { enumPtr = front; } // Must implement this method. public boolean hasMoreElements () { if (enumPtr == null) return false; else return true; } // Must implement this method. public Object nextElement() { // "data" and "next" are accessible. Object obj = enumPtr.data; enumPtr = enumPtr.next; return obj; } } abstract class ComparableObject { // ... } class ListItem { // ... } class LinkedList { // ... // This is needed to return an Enumeration // instance to the user. public Enumeration getEnumeration () { return new ListEnumerator (front); } } // End of class "LinkedList" class Person extends ComparableObject { // ... } // Test class. public class SortedList3 { public static void main (String[] argv) { // Create a new list object. LinkedList L = new LinkedList (); // Add data. L.addData (new Person ("Rogue", "1111-12-1212")); L.addData (new Person ("Storm", "222-23-2323")); L.addData (new Person ("Black Widow", "333-34-3434")); L.addData (new Person ("Jean Grey", "888-89-8989")); // Print contents via an Enumeration. Enumeration e = L.getEnumeration(); while (e.hasMoreElements()) { Person p = (Person) e.nextElement(); System.out.println (p); } } } // End of class "SortedList3"
Exercise 7.2: Download and examine the above program. Draw a complete memory picture at the beginning of the while-loop in main(). What is the variable e pointing to?
The library package java.util defines Enumeration as follows:
interface Enumeration { public abstract boolean hasMoreElements(); public abstract Object nextElement(); }
So, what exactly is an interface?
We will now make LinkedList implement the Enumeration interface: (source file)
// LinkedList now implements Enumeration itself. class LinkedList implements Enumeration { // Note reserved word "implements" // ... data ... // Instance method to add a data item. public void addData (ComparableObject obj) { // ... } public void printList () { // ... } // Pointer needed during enumeration. ListItem enumPtr; // Must implement this method. public boolean hasMoreElements () { if (enumPtr == null) return false; else return true; } // Must implement this method. public Object nextElement() { Object obj = enumPtr.data; enumPtr = enumPtr.next; return obj; } // This is needed to return an Enumeration // instance to the user. public Enumeration getEnumeration () { enumPtr = front; return this; // Using the "this" reserved word. } } // End of class "LinkedList"
Exercise 7.3: Download and examine the above program. Draw a complete memory picture at the beginning of the while-loop in main(). What is the variable e pointing to?
Note:
class LinkedList implements Enumeration { // ... }
public boolean hasMoreElements () { if (enumPtr == null) return false; else return true; } public Object nextElement() { Object obj = enumPtr.data; enumPtr = enumPtr.next; return obj; }
Note that since the methods are inside LinkedList, list pointers are accessible and could have been made private.
public Enumeration getEnumeration () { enumPtr = front; return this; // Using the "this" reserved word. }
Note: this is really a pointer to the current instance.
Suppose we want our LinkedList class to enumerate its contents in both sorted and reverse-sorted order:
interface TwoWayEnumeration { public abstract void setForward (); public abstract void setBackward (); public abstract boolean hasMoreElements(); public abstract Object nextElement(); }
class LinkedList implements TwoWayEnumeration { // ... // Use f=true for forward direction, false otherwise. public TwoWayEnumeration getEnumeration (boolean f) { // ... } }
// Get a 2-way enumeration. TwoWayEnumeration e2 = L.getEnumeration (false); while (e.hasMoreElements()) { Person p = (Person) e.nextElement(); System.out.println (p); }
Suppose we make
LinkedList
itself implement the interface.
(Note:
LinkedList
already implements
Enumeration).
Can a class implement multiple interfaces?
Yes.
For example: (source file)
class LinkedList implements Enumeration, TwoWayEnumeration { // ... ListItem enumPtr; boolean forward = true; // Method from TwoWayEnumeration interface. public void setForward () { forward = true; enumPtr = front; } // Method from TwoWayEnumeration interface. public void setBackward () { forward = false; enumPtr = rear; } // Method in both interfaces. public boolean hasMoreElements () { if (enumPtr == null) return false; else return true; } // Method in both interfaces. public Object nextElement() { Object obj = enumPtr.data; if (forward) enumPtr = enumPtr.next; else enumPtr = enumPtr.prev; return obj; } // Return an Enumeration instance. public Enumeration getEnumeration () { setForward(); return this; } // Return a TwoWayEnumeration instance. public TwoWayEnumeration getEnumeration (boolean f) { forward = f; if (forward) setForward(); else setBackward(); return this; } } // End of class "LinkedList"
Note:
public interface Enumeration { public abstract boolean hasMoreElements(); public abstract Object nextElement(); }and
public interface TwoWayEnumeration { public abstract void setForward (); public abstract void setBackward (); public abstract boolean hasMoreElements(); public abstract Object nextElement(); }
public Enumeration getEnumeration () { setForward(); return this; } // Return a TwoWayEnumeration instance. public TwoWayEnumeration getEnumeration (boolean f) { forward = f; if (forward) setForward(); else setBackward(); return this; }
In the previous example hasMoreElements() and
nextElement() were specified in both interfaces.Java makes it possible to extend interfaces: (source file)
interface TwoWayEnumeration extends Enumeration { public abstract void setForward (); public abstract void setBackward (); // abstract methods hasMoreElements() and // nextElement() are inherited. }
Exercise 7.4:
Use the following template
to add yet another interface to the
LinkedList
class. This interface is defined as:
interface MinMax extends TwoWayEnumeration {
public abstract Object minElement();
public abstract Object maxElement();
}
Thus, a structure's min and max elements are returned
by methods. Your implementation will be tested in the main function already
in the template.
Where do the interfaces live in memory? Draw a picture to explain.
abstract class A { public static void print() { System.out.println ("A"); } } public class TestAbstract { public static void main (String[] argv) { A.print(); } }
Consider this example:
import java.util.*; class ObjX { String name; public ObjX (String s) { name = s; } public void print () { System.out.println ("ObjX: " + name); } } class ObjY { ObjX x; public ObjY (ObjX objx) { x = objx; } public ObjX getX () { return x; } } public class ChainedRefs { public static void main (String[] argv) { // Traditional instantiation: ObjX x = new ObjX ("Big X"); ObjY y = new ObjY (x); // Traditional access: x = y.getX (); x.print (); // Using chaining to instantiate: y = new ObjY (new ObjX ("Lil' X")); // Using chained references to access: y.getX().print(); // Another example: ArrayListyList = new ArrayList (); yList.add (y); yList.add (new ObjY (new ObjX ("X Jr."))); // Chained access: yList.get(1).getX().print(); } }
Note:
y.getX().print();Here:
y.getX().print();returns an ObjX instance (a pointer to that instance). It is the print() method of this particular ObjX instance that then gets called:
y.getX.print();
yList.get(1).getX().print();
yList.get(1).getX().print();
yList.get(1).getX().print();
yList.get(1).getX().print();
ObjY y1 = yList.get(1); ObjX x1 = y1.getX(); x1.print();
Exercise 7.5: Download, compile and execute the above program: ChainedRefs.java. You will notice that an ObjZ is defined. Make an instance of this object and then print the ObjX instance using chained references. All you need are two statements, one to make an instance of the object, the other to print.
The wrong way to copy an object is to use assignment between object variables: (source file)
public static void main (String[] argv) { // Create a new list object. LinkedList L = new LinkedList (); // Add data. L.addData (new Person ("Rogue", "1111-12-1212")); L.addData (new Person ("Storm", "222-23-2323")); L.addData (new Person ("Black Widow", "333-34-3434")); L.addData (new Person ("Jean Grey", "888-89-8989")); // Pointer copy! LinkedList L2 = L; L2.addData (new Person ("Indiana Jones", "888-87-8787")); // What gets printed out? L.printList(); }
In this case, L and L2 point to the same instance.
Let us add a copy() method to LinkedList so that
LinkedList L2 = L.copy();makes a truly separate copy.
Consider this code for method copy() in LinkedList: (source file)
class LinkedList implements Enumeration { // ... public LinkedList copy () { LinkedList L = new LinkedList (); L.front = front; L.rear = rear; L.numItems = numItems; return L; } } // End of class "LinkedList"
What gets printed out when copy is used as follows?
LinkedList L = new LinkedList (); // Insert data. L.addData (new Person ("Rogue", "1111-12-1212")); L.addData (new Person ("Storm", "222-23-2323")); L.addData (new Person ("Black Widow", "333-34-3434")); L.addData (new Person ("Jean Grey", "888-89-8989")); LinkedList L2 = L.copy(); L2.addData (new Person ("Xena", "888-87-8787")); L.printList();
Exercise 7.6: Download, compile and execute the above program. Draw a complete memory picture to show why this doesn't work.
Consider this improvement to copy(): (source file)
// Copy now creates a complete new list. public LinkedList copy () { LinkedList L = new LinkedList (); ListItem tempPtr = front; while (tempPtr != null) { L.addData (tempPtr.data); tempPtr = tempPtr.next; } return L; }
Does this work?
Consider the following changes to the code:
class Person extends ComparableObject { // ... public void blankOut () { name = ""; } }
This simply erases the name field of a Person instance.
class LinkedList implements Enumeration { // ... public ComparableObject remove (ComparableObject obj) { ListItem tempPtr = front, prevPtr = front; boolean found = false; while ( (!found) && (tempPtr != null) ) { if (tempPtr.data.compare(obj) == 0) { found = true; break; } prevPtr = tempPtr; tempPtr = tempPtr.next; } // If found, remove it. if (found) { if (tempPtr == front) front = tempPtr.next; else prevPtr.next = tempPtr.next; System.out.println ("Removed: " + tempPtr.data); return tempPtr.data; } else { System.out.println ("Remove: not found"); return null; } } } // End of class "LinkedList"
Person p2 = (Person) L.remove (p); p2.blankOut(); L.printList(); L2.printList ();
Exercise 7.7: The complete source with the above changes is available here. Compile and execute to see what happens. What do you observe? Write code to fix the problem.
Object defines a method called clone():
public class Object { // ... protected Object clone () { } }
Observe that the method is protected:
class ObjA { int x = 1; public void print() { System.out.println ("x=" + x); } // Inherits clone() } public class TestSimpleClone { public static void main (String[] argv) { ObjA a = new ObjA(); // Note: need to cast from Object ObjA a2 = (ObjA) a.clone(); // Compiler error. } }
To perform a bitwise copy, we need to call the clone() method of Object.
How do we call a superclass method? Like this:
super.clone();
Here the reserved word super is used with the dot operator to invoke a superclass method.
However, this alone is not sufficient. The method clone() in Object throws an exception, so it must be caught.
Here's a next attempt: (source file)
class ObjA { // Data. int x = 1; int[] A = {2}; // A print method. public void print() { System.out.println ("x=" + x + ", A[0]=" + A[0]); } // Overrides Object's clone() public Object clone () { // Must catch exception. try { return super.clone(); } catch (CloneNotSupportedException e) { System.out.println (e); return null; } } } public class TestCloning { public static void main (String[] argv) { ObjA a = new ObjA(); ObjA a2 = (ObjA) a.clone(); a.print(); a2.print(); } }
Unfortunately, the exception is thrown, and results in a runtime error.
Java requires that we implement the (empty) Cloneable interface to make it work correctly.
Here's the modified version: (source)
class ObjA implements Cloneable { // Data. int x = 1; int[] A = {2}; // A print method. public void print() { System.out.println ("x=" + x + ", A[0]=" + A[0]); } // Overrides Object's clone() public Object clone () { // Must catch exception. try { return super.clone(); } catch (CloneNotSupportedException e) { System.out.println (e); return this; } } public void zeroOut () { x = 0; A[0] = 0; } } public class TestCloning2 { public static void main (String[] argv) { ObjA a = new ObjA(); ObjA a2 = (ObjA) a.clone(); a.print(); a2.print(); // Print addresses: System.out.println (a); System.out.println (a2); // Consider this experiment: a.zeroOut(); a.print(); a2.print(); } }
Note:
public interface Cloneable { }
It has no methods defined!
When run, the output produced is:
x=1, A[0]=2 x=1, A[0]=2 ObjA@1dc6074e // Addresses are different ⇒ different objects ObjA@1dc60750 x=0, A[0]=0 x=1, A[0]=0 // a2's array gets zeroed out!
Note:
To fix the problem with the array, we need to create a true copy: (source file)
public Object clone () { ObjA a = new ObjA(); a.x = x; a.A = new int[1]; a.A[0] = A[0]; return a; }
In this case, the output looks like:
x=1, A[0]=2 x=1, A[0]=2 ObjA@1dc6074e ObjA@1dc60750 x=0, A[0]=0 x=1, A[0]=2 // a2's array is untouched
We will now consider an odd difference between shadowed variables and shadowed (overridden) methods in Java.
First, observe how data and methods are overridden in this example:
class ObjA { int x = 1; public void print() { System.out.println ("A: x=" + x); } } class ObjB extends ObjA { int x = 2; public void print() { System.out.println ("B: x=" + x); } } public class TestShadow { public static void main (String[] argv) { ObjB b = new ObjB(); System.out.println (b.x); // Prints 2. } }
Here,
Interestingly, Java makes it possible to refer to superclass variables:
class ObjA { int x = 1; public void print() { System.out.println ("A: x=" + x); } } class ObjB extends ObjA { int x = 2; public void print() { System.out.println ("B: x=" + x); } } class ObjC extends ObjB { int x = 3; public void print() { System.out.println ("C: x=" + x); } public void testData () { System.out.println (this.x); System.out.println ( ((ObjB) this).x); System.out.println ( ((ObjA) this).x); } } public class TestShadow { public static void main (String[] argv) { // Only one instance created. ObjC c = new ObjC(); // Data shadows. System.out.println (c.x); ObjB b = (ObjB) c; System.out.println (b.x); ObjA a = (ObjA) c; System.out.println (a.x); c.testData(); // Only one instance - a check. System.out.println (c); System.out.println (b); System.out.println (a); } }
The output generated is:
3 2 1 3 2 1 ObjC@1dc60750 ObjC@1dc60750 ObjC@1dc60750
Note:
However, the same is not true for methods. For example, consider:
public class TestShadow { public static void main (String[] argv) { c.print(); b.print(); a.print(); } }
The output in this case is:
C: x=3 C: x=3 C: x=3
Note:
Observe:
Sometimes, when an object instance is no longer required, it is important to perform some "clean-up" work before it disappears:
For example, suppose we are handling several files simultaneously:
Java lets a programmer define a method called finalize() for such cleaning-up:
public class Object { // ... protected void finalize () { } }
Sometimes, a static object needs to be initialized reliably, that is, without having to depend on a user calling an init() method.
For example:
class StaticObj { static int A[]; public static void printA () { for (int i=0; i < A.length; i++) System.out.println (A[i]); } } public class TestStatic { public static void main (String[] argv) { StaticObj.printA(); } }
class StaticObj { static int A[]; public static void init() { A = new int[2]; A[0] = 10; A[1] = 20; } public static void printA () { for (int i=0; i < A.length; i++) System.out.println (A[i]); } }
Java allows a static initializer that will be called only once at runtime, when a class is loaded: (source file)
class StaticObj { static int A[]; // Static initializer: note the unusual syntax. static { A = new int[2]; A[0] = 10; A[1] = 20; } public static void printA () { for (int i=0; i < A.length; i++) System.out.println (A[i]); } } public class TestStatic { public static void main (String[] argv) { StaticObj.printA(); } }
Note:
Is there anything analogous for dynamic classes?
In Java, the reserved word this refers to the current instance, when used inside an instance.
Note: this is really a pointer. For example: (source file)
class ObjA { int x; void selfExamine () { System.out.println (this); } } public class TestThis { public static void main (String[] argv) { ObjA a = new ObjA (); System.out.println (a); a.selfExamine(); } }
The output is:
ObjA@1dc6074e ObjA@1dc6074e
Thus, this points to the same block of heap memory that a points to.
Here are some uses of this:
class ObjB { int x; public ObjB (int x) { this.x // Refers to instance variable. = x; // Refers to parameter. } }
class ObjC { void assign (ObjC a) { } }
Here, we want the current instance to be assigned the input parameter instance. This could be used as follows:
ObjC c = new ObjC (); // ... ObjC c2 = new ObjC (); // ... c.assign (c2); // Now c is identical to c2 in content.
Consider what happens when an instance is called with itself as parameter:
c.assign (c);
In this case, the assignment will be repeated
unnecessarily.
For a complex data structure, this can be a waste, e.g.,
class ObjC { // A complex data structure. String[] names; // A constructor. public ObjC (String[] names) { this.names = new String[names.length]; for (int i=0; i < names.length; i++) this.names[i] = names[i]; } // The assign method. void assign (ObjC a) { this.names = new String[a.names.length]; for (int i=0; i < a.names.length; i++) names[i] = a.names[i]; } }
To avoid re-doing the data structure when passed its own instance, the this pointer is useful:
void assign (ObjC a) { if (this == a) return; this.names = new String[a.names.length]; for (int i=0; i < a.names.length; i++) names[i] = a.names[i]; }
For example, consider the following code:
class Complex { double real; double imag; void setValue (double real, double imag) { this.real = real; this.imag = imag; } public Complex () { setValue (0, 0); } public Complex (double real) { setValue (real, 0); } public Complex (double real, double imag) { setValue (real, imag); } }
Here, the class Complex has three constructors that call a common method.
However, the common method really has the same signature as one of the constructors.
An alternative is to use:
class Complex { double real; double imag; public Complex (double real, double imag) { this.real = real; this.imag = imag; } public Complex () { this (0, 0); } public Complex (double real) { this (real, 0); } }
Here, this (...) is used to refer to a constructor in the same class (with a matching signature).
Note: the above use of
this
as a method call can only occur in thefirst
line in another constructor.