Module 7: Java Objects, Part III: Advanced Topics


A sorted linked list

 

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:

 

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:

 

Note:

  • We could force other methods to be defined as well. For example, we can force subclasses of ComparableObject to implement toString():
    
    abstract class ComparableObject {
        public abstract String toString ();
        public abstract int compare (ComparableObject c);
    }
    

  • Note: the Java library contains an interface called Comparable for comparisons.
    ⇒ we will study interfaces later.
 


Sorted list - providing an Enumeration

 

Next, we will provide an Enumeration for our sorted list.

What does this mean?

  • The idea is, any data structure ought to provide a mechanism for "walking through" its contents.

  • Recall, that an Enumeration is an object itself, provided for enumerating the contents of generic objects:

  • The idea is, you as a data structure designer, return an Enumeration object that will allow a user (another programmer) to retrieve user-inserted contents one by one.

  • One way to do this is to implement methods like
    
         public void startEnumeration()
         public boolean hasMoreElements()
         public Object nextElement()
    
    for your container (Example of container: linked list).

  • However, if a user wishes to keep their code independent of changes to containers and container methods, an Enumeration is a good option.

  • An Enumeration interface is defined in the Java-library and has the following abstract methods:
    
         public hasMoreElements()
         public nextElement()
    
    This way, any container can implement an Enumeration and return an instance to the caller.

  • Suppose LinkedList has a method called
    
    class LinkedList {
    
        // ... 
    
        public Enumeration getEnumeration ()
        {
            // ... 
        }
    }
    
    that returns an Enumeration instance.

  • Then, a user can enumerate elements of the list as follows:
    
        LinkedList L = new LinkedList ();
    
        // ... add data ...
        Enumeration e = L.getEnumeration();
        while (e.hasMoreElements()) {
            Person p = (Person) e.nextElement();
            System.out.println (p);
        }
    

  • Thus, an Enumeration object is a sort of "helper" object associated with the larger LinkedList object.
 

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:

  • Create a special-purpose object that subclasses Enumeration and also has access to LinkedList internals.
    (See below).

  • Have LinkedList itself subclass Enumeration and return an instance.
    (See further below).

  • Use a so-called anonymous class.
    (Later in the course).
 

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?
 


Implementing the Enumeration interface in the list

 

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?

  • An interface is an "advertisement" of capability, a promise to implement methods defined in the interface.

  • Alternatively, an interface is a purely abstract class.
    (All methods are abstract).

  • The idea is, a class can implement an interface by implementing all the methods advertised in the interface.

  • Thus, if LinkedList implements the Enumeration interface, in effect, LinkedList is saying "I can do everything an Enumeration is supposed to".

  • A class can only inherit from one parent (superclass), but can implement any number of interfaces.

  • Note: Java does not allow you to inherit from more than one class. (C++ does allow this, a feature called multiple inheritance).
 

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:

  • The reserved word " implements" is used as in:
    
    class LinkedList implements Enumeration {
        // ...
    }
    
  • LinkedList now must implement the methods advertised in the interface:
    
        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.

  • A method to return an Enumeration to the user was required:
    
        public Enumeration getEnumeration ()
        {
            enumPtr = front;
            return this;      // Using the "this" reserved word.
        }
    
  • The this reserved word refers to the current instance:
    • Since each LinkedList will be different, each enumeration will be different.
    • The Enumeration instance must correspond to the LinkedList instance.

    • But, since LinkedList is itself an Enumeration, its instance will do.

    Note: this is really a pointer to the current instance.

 


Multiple interfaces

 

Suppose we want our LinkedList class to enumerate its contents in both sorted and reverse-sorted order:

  • We will define a generic interface called TwoWayEnumeration:
    
    interface TwoWayEnumeration {
        public abstract void setForward ();
        public abstract void setBackward ();
        public abstract boolean hasMoreElements();
        public abstract Object nextElement();
    }
    
  • Then, we will make sure that LinkedList returns an instance of this type:
    
    class LinkedList implements TwoWayEnumeration {
    
        // ...
    
        // Use f=true for forward direction, false otherwise.
        public TwoWayEnumeration getEnumeration (boolean f)
        {
            // ...
        }
    }
    
  • Finally, this can be used as follows:
    
        // 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:

  • Here, both interfaces are implemented by LinkedList.

  • Both interfaces have methods with common signatures:
    
    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();
    }
    
  • Thus, the implementation of hasMoreElements() satisfies both interfaces.

  • A method was defined to return an instance for each interface:
    
        public Enumeration getEnumeration ()
        {
            setForward();
            return this;  
        }
    
        // Return a TwoWayEnumeration instance.
        public TwoWayEnumeration getEnumeration (boolean f)
        {
            forward = f;
            if (forward)
                setForward();
            else
                setBackward();
            return this;
        }
    
  • Does it seem like a waste to specify the methods hasMoreElements() and nextElement() in both interfaces?
 


Inheriting from an interface

 

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.
 


Interfaces and abstract classes - a summary

 
  • An abstract class is a class with at least one abstract method.

  • An abstract class must have the reserved word abstract in the class definition.

  • An abstract method is declared with its signature and no body. A semi-colon is required at the end.

  • An abstract class cannot be instantiated. However, it can contain non-abstract static methods that can be called without instantiation, e.g.,
    
    abstract class A {
        public static void print() 
        {
            System.out.println ("A");
        }
    }
    
    public class TestAbstract {
        public static void main (String[] argv)
        {
            A.print();
        }
    }
    
  • Not all methods of an abstract class need be abstract.

  • A subclass of an abstract class inherits all methods of the abstract class.

  • Subclasses can themselves be extended and thus, abstract methods can be passed down the chain of inheritance.

  • However, no abstract methods can be present for a class to be instantiated.

  • Data cannot be abstract.

  • An abstract class with all its methods abstract is really like an interface.

  • So, what is the difference between an interface and an pure abstract class?
    • An abstract class is extended, but an interface is implemented.
    • Since a class can have only one superclass, it is more convenient to define interface's.
    • Multiple interface's can be implemented by a single class.
    • Only static final variables (constants) can be declared in an interface.

  • A public interface must be defined in a file by itself.
 


Chained references

 

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:
	ArrayList yList = new ArrayList ();
	yList.add (y);
	yList.add (new ObjY (new ObjX ("X Jr.")));
	
	// Chained access:
	yList.get(1).getX().print();
    }

}

Note:

  • Let's examine:
    
    	y.getX().print();
        
    Here:
    • One reads left to right.
    • The call getX() in
      
      	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();
          

  • Consider again the last line in main()
    
    	yList.get(1).getX().print();
        
    • Once again, let's read left to right.
    • First, the arraylist's get() method is called with the parameter value 1:
      
      	yList.get(1).getX().print();
          
    • This returns the 2nd element (at position 1), which is an ObjY instance.
    • We don't really retain a reference to this ObjY instance.
    • Instead, we directly call a method in it, the getX() method:
      
      	yList.get(1).getX().print();
          
    • The call of course returns an ObjX instance (by the definition of the method).
    • Again, instead of retaining its reference, we directly call its print() method:
      
      	yList.get(1).getX().print();
          

  • If we had to write this the old way:
    
            ObjY y1 = yList.get(1);
            ObjX x1 = y1.getX();
            x1.print();
        

  • Chained references make for compact code when we have single-use references.
 

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.
 


Copying objects

 

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:

  • In Person, we add the following method:
    
    class Person extends ComparableObject {
    
        // ...
        public void blankOut ()
        {
            name = "";
        }
    
    }
    

    This simply erases the name field of a Person instance.

  • Next, in LinkedList, we add a method to remove a particular element of the list:
    
    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"
    
  • Finally, in main, we add:
    
        Person p2 = (Person) L.remove (p);
        p2.blankOut();
        L.printList();
        L2.printList ();
    
  • Note: the illustrative purpose of blankOut() is to make a deliberate change to something that's already been removed from the list.
 

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.
 


The method clone() in Object

 

Object defines a method called clone():


public class Object {

    // ...
    protected Object clone ()
    {
    }

}

Observe that the method is protected:

  • This means that the following will not compile:
    
    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. 
        }
    }
    
  • The method is deliberately defined as protected to force subclasses of Object to implement clone().

  • This way, a class can define a public method called clone() that can be called by the outside world.

  • But then, why not define it as abstract? Two reasons:
    • If it were abstract, every subclass would be forced to implement it, even those that have no interest in cloning.
    • The default implementation has value: Java has defined clone() in Object to perform a bitwise copy of an instance's heap block.
 

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:

  • Java requires that, for a bitwise copy, you must force a class to implement the Cloneable interface.

  • The Cloneable interface is defined as:
    
    public interface Cloneable {
    }
    

    It has no methods defined!

  • The idea is to use the interface to alert the compiler (and runtime system) that bitwise cloning is desired.

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:

  • Now the instance variables a and a2 point to different heap blocks.

  • The clone() method created a bitwise copy.

  • Being a true bitwise copy, the array pointer remained the same.

  • Thus, the change made to a's array in zeroOut() was also reflected in a2's array.

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
 


Other topics: shadowed methods and variables

 

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,

  • ObjB extends ObjA.

  • ObjB overrides ObjA's print() method.

  • The variable x is defined in both classes.
  • The reference b.x refers to the data variable x in ObjB.

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:

  • Casting can be used to refer to superclass data.

  • Casting can be used with the this operator as well.

  • Only one instance is created, as can be seen by the object pointers.

  • A subclass instance also carries with it (in the heap) superclass variables.

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:

  • All method references are to the original instance (of type ObjC).
  • You cannot call the superclass method that was overridden using casting.
    (You can, however, using super)

Observe:

  • Variables are shadowed whereas methods are overridden.

  • Method overriding is desired because that is how polymorphism is featured.

  • Usually, an instance's variables are private and so there is no need to provide polymorphism for data.

  • Where possible, referring to superclass variables via "de-shadowing" should be avoided.
    Instead, it's better to use different variable names.
 


Other topics: the finalize method

 

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:

  • We keep the file information in a linked list.

  • Each list item itself has a FileInputStream instance.

  • Before the list instance dies, we need to close all the files properly.

  • Simply destroying the list instance does nothing to the files.

  • It is better to call a clean-up method in the list instance.

Java lets a programmer define a method called finalize() for such cleaning-up:

  • Actually, Object has such a method defined:
    
    public class Object {
    
        // ...
        protected void finalize ()
        {
        }
    }
    
  • A finalize() method must have exactly the same signature.

  • Thus, defining a finalize() method really overrides Object's finalize() method.

  • It's usually best to use the protected modifier so that it can't be called by a user.

  • Java does not guarantee that finalizers will be called, but does guarantee that a finalizer will be called before the instance gets garbage-collected.

  • You can force finalizers to be executed using the System.runFinalization() method.
 


Other topics: static initializers

 

Sometimes, a static object needs to be initialized reliably, that is, without having to depend on a user calling an init() method.

For example:

  • Suppose we have a static class that uses an array, e.g.,
    
    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();
        }
    
    }
    
  • The array needs to be initialized.

  • The class can provide an init() method, as in:
    
    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]);
        }
    }
    
  • But this relies on the user calling init().

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:

  • A static initializer obviously cannot take any parameters nor return a value.

  • The syntax is a little unusual in that a special name like staticInitializer could have been defined in Object as a static method.

Is there anything analogous for dynamic classes?

  • Dynamic classes use constructors to perform initialization.

  • However, constructors use the name of a class.

  • Later, we will see that it is possible to define a class without a name (an anonymous class).

  • For an anonymous class, a special initializer is required, called an instance initializer, with even stranger syntax.
 


The this operator

 

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:

  • You can avoid creating special parameter identifiers just to distinguish them from instance variables:
    
    class ObjB {
        int x;
        public ObjB (int x)
        {
            this.x       // Refers to instance variable.
                    = x; // Refers to parameter.
        }
    }
    
  • Suppose you have a method that performs assignment in a class that contains a complex data structure, e.g.,
    
    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];
        }
    
  • We have also seen that this is used to return a pointer to the current instance when returning an instance that implements a particular interface, e.g., Enumeration.

  • The this reserved word has another meaning, different from a pointer: it can be used to refer to a constructor.

    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.




© 1998, Rahul Simha (revised 2017)