import java.*: Interfaces and Inner Classes

A Java interface is a weak substitute for multiple inheritance in C++, but it still manages to do a lot of what needs doing.


January 01, 2000
URL:http://www.drdobbs.com/cpp/import-java-interfaces-and-inner-classes/184401177

January 2000/import java.*


One of the key advances in software engineering over the past few decades has been the practice of separating interface from implementation. If a client depends only on an interface, then the implementation can vary with little disruption for the client. That's the theory, anyway. I suppose the truth of that statement depends on how you define "little disruption." In this article I talk about how Java supports interfaces as first-class entities to greatly enhance modular programming.

Separation Anxiety

I don't think you and I will ever see the day when the stack data structure is no longer used as a sample program, whether as a model for how to define a class or for whatever other purpose. That being the case, why fight it? In Figure 1 I define a FixedStack class, which uses an array to hold Objects. The interface consists of the public methods: push, pop, top, size, and a constructor, which in this case defines the stack's capacity. Since exceptions are the best way to signal errors, I define StackError as an exception class (see Figure 2). The test program in Figure 3 makes sure everything works as advertised.

Is this the best way to implement a stack? Probably not, although if you know the upper bound on the number of elements it's very efficient. But it's more likely that you'll need a stack that can grow arbitrarily large, like the DynamicStack class in Figure 4, which uses a Vector to store its objects. Java.util.Vector.addElement does not specify that it throws any exceptions, therefore neither does DynamicStack.push, and the test program in Figure 5 doesn't test for it. Other than that, the two classes look pretty much the same to the user.

With things as I've defined them so far, the only way I can use a stack is to choose an implementation, because concrete classes such as FixedStack and DynamicStack come with both interface and implementation as a unit. In other words, I really haven't separated the interface from the implementation — I've just made the details of the implementation inaccessible to clients. I should be able to make the test function StackTest.doTest totally independent of whatever implementation it receives. But how?

The first thing that comes to mind, especially to C++ programmers, is to define an abstract class Stack, say, that serves as a base class for both implementation classes:

abstract class Stack
{
   public abstract void
   push(Object o)
      throws StackException;

   public abstract Object pop()
      throws StackException;

   public abstract Object top()
      throws StackException;

   public abstract int size();
}

I can now ignore the implementation classes within StackTest.doTest by having it process a Stack object, and pass either a FixedStack or DynamicTest as circumstances dictate.

I need the exception specification on Stack.push, by the way, because FixedStack needs it, even though DynamicStack doesn't. An overridden method in a subclass can specify a subset of the exceptions specified in the parent method (even an empty subset), since it still fulfills the contract of the parent method.

I see 1 1/2 problems with this approach. The "1/2" is that abstract classes are capable of holding implementation, yet my Stack class has none. This may not seem like a "problem," but in Java, abstract classes are intended to hold implementation shared by all subclasses. Of course, the reason I have no details in Stack is because there's nothing to share between FixedStack and DynamicStack. But it would feel so much better if there were some way to capture the shared design (i.e., the interface declarations) without inheritance, because inheritance is all about implementation.

There is a way, or I wouldn't be writing this article. But wait. I said "1 1/2 problems." What's the other issue? Well, Java supports only single inheritance; that is, it lets you extend only one class. Sorry, that's the way it is. But it's not far-fetched to want to implement multiple interfaces. Suppose, for example, that I want my stacks to be persistent, by implementing the following interface:

abstract class Persistent
{
   public abstract Object
   read(InputStream i)
      throws IOException;
   public abstract void
   write(OutputStream o)
      throws IOException;
}

If inheritance is the only game in town, then I have to extend both Stack and Persistent, but Java won't let me. That's a problem.

Interfaces as Types

So obviously I need a separate mechanism to specify interfaces, and that's why I invited you here today. An interface in Java just specifies method declarations (and some other optional things mentioned later), without function bodies, like this:

interface Stack
{
   void push(Object o)
      throws StackException;

   Object pop()
      throws StackException;

   Object top()
      throws StackException;

   int size();
}

All methods declared in an interface are abstract and public by default, so you don't have to explicitly declare them as such if you don't want to (most people don't). To specify that FixedStack implements the Stack interface, all you have to do is say so in the class definition, as in:

class FixedStack implements Stack
{
   // implementation unchanged
   // from Figure 1 ...
}

Of course, I have to actually implement the Stack methods inside of FixedStack, else the compiler will reprimand me severely. Now I can implement StackTest.doTest in terms of the Stack interface (see Figure 6). As long as the object passed to doTest implements Stack, everything works fine. If you want, you can even get the actual implementation type with the getClass method, which returns an object of type Class. I used Class.getname to print the dynamic type of the object passed to doTest in Figure 6.

And what if you want a class to implement multiple interfaces? No problem. Here's a persistent DynamicStack:

class DynamicStack
   implements Stack, Persistent
{
   // implementation as in Figure 4
   // PLUS need to implement read
   // and write
}

So Java supports multiple implementation of interfaces, but only single inheritance of implementation. Interfaces can extend any number of other interfaces, however. You could, for example combine Stack and Persistent into a single interface:

interface PersistentStack  extends Stack, Persistent {}
class DynamicStack  implements PersistentStack {...}

You can mimic interfaces in C++ by defining them as classes that only have pure virtual functions with no function bodies. But just as classes are an improvement over C structs, it's nice to have explicit language support for interfaces. And I have yet to meet a C++-programmer-turned-Java-programmer who misses the complexities of virtual base classes (viz., dominance, initialization through most-derived constructor, etc.) that multiple inheritance brings in C++!

Marker Interfaces and Cloning

Although you'll rarely want to do it in your own programs, you can define an interface that has no methods. Whatever for, you ask? Mostly to "color" a class. One such interface is java.lang.Cloneable. As you know, all classes implicitly inherit from the class java.lang.Object, which has the following method, among others:

protected Object clone() {...}

This method returns a bitwise copy (aka "shallow copy") of the dynamic type of its object. If you want to allow clients to clone objects of a class, you should adhere to the following convention in your class definition:

1. Override Object.clone, make it public, have it call super.clone, and

2. Specify that your class implements Cloneable.

If you don't follow step 1, clients will get an access violation when calling clone, since the one inherited from Object is protected. Even though you override clone properly, however, when Object.clone executes (which it will if you follow this convention), it verifies that your class has "implemented" Cloneable; if it hasn't, Object.clone throws a CloneNotSupportedException. The Person class in Figure 7 illustrates the correct way to override Object.clone, namely:

1. Call super.clone to get the new object. This guarantees that Object.clone is called first to make the shallow copy.

2. Do whatever it takes to make a "deep copy," if necessary.

3. Return the finished object.

4. Put all the above in a try block that catches a CloneNotSupportedException and throws an InternalError.

Since I want Person objects to have their own copies of their fields, I explicitly copy the name field for the clone, because Object.clone just copies the name field's string handle. Object.clone is sufficient for the primitive fields, of course. Since Object.clone can throw a CloneNotSupportedException, I must catch it, but since I've implemented Cloneable, that should only happen if the virtual machine has had a major brain cramp, in which case I'll give up and throw an InternalError.

If you're going to allow clones of a particular class of objects, you'll probably be comparing those objects too, and maybe even printing them. If so, you'll want to override the appropriate methods inherited from Object as I did in the Person class. The test program in Figure 8 verifies that I got a separate object after cloning, but that it is equal to the first.

What if your class has another superclass besides Object? Following the above convention for cloning takes care of things automatically. To see that this is so, look at the code in Figure 9, where I define a class Sub that extends a class Super, and each class holds its own String variable. When cloning a Sub object, the following events occur:

1. Sub.clone calls Super.clone.

2. Super.clone in turn calls Object.clone, which returns a new Sub object (as an Object, of course, which is why a cast is needed).

3. This new Sub object contains copies of the object handles s1 and s2 (i.e., Sub is a shallow copy).

4. Super.clone then takes care of cloning its data (s1).

5. Finally, Sub.clone does likewise for s2.

So when Sub.clone returns, you have a deep copy in sub2 in SuperClone.main. Note that since no exceptions should occur, I left off the exception specification for Super.clone, which means that I don't even need a try block in Sub.clone. Sub doesn't need to explicitly implement Cloneable either, since it extends a Cloneable class.

In case you are wondering, all arrays implicitly implement Cloneable, since there is no way for you to do it. When you clone an array, however, you get a shallow copy of the array's elements, which the following excerpt illustrates.

class ArrayClone {
public static void  main(String[] args){
   Integer[] a1 =  {new Integer(1)};
   Integer[] a2 =  (Integer[]) a1.clone();
   System.out.println(a1 != a2);
   System.out.println
   (a1[0] == a2[0]);
   }
}
/* Output:
true 
true // shallow copy! 
*/

Nested Classes

I mentioned earlier that interfaces could contain more than just method declarations. They can also hold constants, which are implicitly static and final, and also class definitions. Class definitions? Yep. Usually you would define Stack, StackException, and all implementations of Stack as non-nested, top-level classes in the same package; but just to show how to nest a class in an interface, in Figure 10 I define StackException inside Stack. To make this work I have to replace all occurrences of StackException with Stack.StackException in the definitions of StackTest, FixedStack, and DynamicStack (not shown). The expression Stack.StackException suggests that StackException is a static class within Stack, since a type name, not an object name, precedes the dot operator. Indeed, any classes defined within an interface are implicitly static and public, and they behave just like public nested classes in C++: they're just types nested within another scope.

There are actually four types of nested classes in Java:

1) static nested classes, as explained above

2) non-static nested classes, also called inner classes

3) local classes, which are classes defined within a method, and

4) anonymous local classes, also called anonymous inner classes

Any class or interface type can contain nested class definitions. You always refer to a static nested class with the dot operator like I did above and use it as a normal class. A non-static nested class, or inner class, is special because each of its instances are always attached to an instance of the containing class (see the section "Inner Classes" below). If you know Lisp or Scheme, inner classes are reminiscent of closures and continuations in those languages, respectively. A local class is a class defined within a method, such as

class LocalTest
{
   public static void
   main(String[] args)
   {
      new LocalTest().f();
   }

   void f()
   {
      final int i = 7;

      class Local
      {
         void g()
         {
            System.out.println
               ("i = " + i);
         }
      }

      Local x = new Local();
      x.g();
   }
}

/* Output:
i = 7
*/

A local class instance can access any final local variables defined in its enclosing function. While static nested classes and local classes are occasionally useful in Java, most nested classes tend to be inner classes.

A Pattern Example

To motivate the utility of inner classes, consider the Iterator pattern defined in Design Patterns [1] (often referred to as the "Gang of Four" book, or GOF for short). An iterator is an object that gives sequential access to an ordered collection, such as a list. I'll define a simpler interface for iterators than GOF, as follows:

interface Iterator
{
   boolean hasNext();
   Object next();
}

A collection supports iteration by implementing a method that yields an iterator, such as

public Iterator iterator() {...}

The process of traversing a collection via an iterator looks something like this:

Collection col;
// Populate col, then...
Iterator iter = col.iterator();
while (iter.hasNext())
{
   Object o = iter.next();
   // process o ...
}

Now consider how you would implement an iterator for FixedStack. Such an object will need a handle to the stack it will traverse, and it will need access to the underlying data of the stack. As you can see in Figure 11, I define a class named StackIter to implement an iterator in the same package as FixedStack, but I have to demote the array data from private to package access to make this work.

Inner Classes

Inner classes offer a cleaner, safer way to implement iterators. In Figure 12 I define StackIter within FixedStack as a private inner class (note no use of static). There is no reason to give it any more than private access, since such an object will be returned as an Iterator. When FixedStack.iterator returns a StackIter object, that object receives an implicit reference to the FixedStack object that created it. That way, when I use the size and data fields in the StackIter methods, the stack I'm accessing is well defined. In other words, the statement:

return pos < size;

is the same as

return pos < StackIter.this.size;

where StackIter.this is the hidden reference to the enclosing stack. And since StackIter is a member class of FixedStack, it has access rights to FixedStack's private members, so I can restore data to private access.

Since StackIter is only used in one place, its name is of little utility, so I can use an anonymous inner class instead. To do this, move the declaration of StackIter inside the body of iterator as follows:

public Iterator iterator()
{
   return new Iterator()
   {
      private int pos = 0;
      public boolean hasNext()
      {
         return pos < size;
      }
      public Object next()
      {
         return hasNext() ?
            data[pos++] : null;
      }
   };
}

You can always recognize an anonymous inner class as a block following a new expression. In this case the compiler sees that the anonymous class implements the Iterator interface. Anonymous classes can also extend other classes as well as implement interfaces.

Conclusion

Using interfaces as types facilitates a true separation of interface from implementation, giving you much more flexibility in designing modular software. As a plus you can implement multiple interfaces at will without the headaches of multiple implementation inheritance. And inner classes greatly simplify the process of implementing an interface (or specializing a superclass) while maintaining a maximum of type safety.

Reference

[1] Gamma et al. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). ISBN: 0201633612.

Chuck Allison is Consulting Editor and a columnist with CUJ. He is the owner of Fresh Sources, a company specializing in object-oriented software development, training, and mentoring. He has been a contributing member of J16, the C++ Standards Committee, since 1991, and is the author of C and C++ Code Capsules: A Guide for Practitioners, Prentice-Hall, 1998. You can email Chuck at [email protected].

January 2000/import java.*/Figure 1

Figure 1: The FixedStack class

class FixedStack
{
    private int capacity;
    private int size;
    private Object[] data;

    public FixedStack(int cap)
    {
        data = new Object[cap];
        capacity = cap;
        size = 0;
    }

    public void push(Object o)
    throws StackException
    {
        if (size == capacity)
            throw new StackException
                          ("overflow");
        data[size++] = o;
    }

    public Object pop()
    throws StackException
    {
        if (size <= 0)
            throw new StackException
                          ("underflow");
        return data[--size];
    }

    public Object top()
    throws StackException
    {
        if (size <= 0)
            throw new StackException
                          ("underflow");
        return data[size-1];
    }

    public int size()
    {
        return this.size;
    }
}
January 2000/import java.*/Figure 10

Figure 10: Nests StackException within the Stack Interface

interface Stack
{
    void push(Object o)
        throws StackException;

    Object pop()
        throws StackException;

    Object top()
        throws StackException;

    int size();

    // This class is static:
    class StackException extends Exception
    {
        StackException
        {}
        StackException(String msg)
        {
            super(msg);
        }
    }
}
January 2000/import java.*/Figure 11

Figure 11: Implements an iterator for FixedStack

class StackIter implements Iterator
{
    FixedStack s;
    private int pos = 0;
    
    StackIter(FixedStack s)
    {
        this.s = s;
    }
    public boolean hasNext()
    {
        return pos < s.size();
    }
    public Object next()
    {
        return hasNext() ?
                  s.data[pos++] : null;
    }
}

class FixedStack implements Stack
{
    private int capacity;
    private int size;
    Object[] data;

    public Iterator iterator()
    {
        return new StackIter(this);
    }
    // Other methods unchanged ...
}
January 2000/import java.*/Figure 12

Figure 12: Implements an iterator via an inner class

class FixedStack implements Stack
{
    private int capacity;
    private int size;
    private Object[] data;

    private class StackIter implements 
       Iterator
    {
        private int pos = 0;
        public boolean hasNext()
        {
            return pos < size;
        }
        public Object next()
        {
            return hasNext() ?
                      data[pos++] : null;
        }
    }

    public Iterator iterator()
    {
        return new StackIter();
    }

// Other methods unchanged ...
}
  
January 2000/import java.*/Figure 2

Figure 2: The StackException class

class StackException extends Exception
{
    StackException()
    {}
    StackException(String msg)
    {
        super(msg);
    }
}
January 2000/import java.*/Figure 3

Figure 3: A test of FixedStack

class StackTest
{
    public static void main(String[] args)
    {
        FixedStack s = new FixedStack(3);
        doTest(s);
    }

    public static void doTest(FixedStack s)
    {
        try
        {
            System.out.println
                ("Initial size = " +
                  s.size());
            s.push("one");
            s.push(new Integer(2));
            s.push(new Float(3.0));

            s.push("one too many");
        }
        catch(StackException x)
        {
            System.out.println(x);
        }

        try
        {
            System.out.println("Top: " +
               s.top());
            System.out.println
            ("Popping...");

            while (s.size() > 0)
               System.out.println
               (s.pop());
        }
        catch(StackException x)
        {
            throw new InternalError
                         (x.toString());
        }
    }
}

/* Output:
Initial size = 0
StackException: overflow
Top: 3.0
Popping...
3.0
2
one
*/
January 2000/import java.*/Figure 4

Figure 4: The DynamicStack class

import java.util.*;

class DynamicStack
{
    private Vector data;

    public DynamicStack()
    {
        data = new Vector();
    }

    public void push(Object o)
    {
        data.addElement(o);
    }

    public Object pop()
    throws StackException
    {
        if (data.size() == 0)
            throw new StackException
                          ("underflow");
        int where = data.size() - 1;
        Object o = data.elementAt(where);
        data.removeElementAt(where);
        return o;
    }

    public Object top()
    throws StackException
    {
        if (data.size() == 0)
            throw new
               StackException ("underflow");
        return 
           data.elementAt(data.size()-1);
    }

    public int size()
    {
        return data.size();
    }
}
January 2000/import java.*/Figure 5

Figure 5: Tests DynamicStack

class StackTest2
{
    public static void main(String[] args)
    {
        DynamicStack s = new DynamicStack();
        doTest(s);
    }

    public static void doTest(DynamicStack s)
    {
        // No exceptions to check here:
        System.out.println("Initial size = " + s.size());
        s.push("one");
        s.push(new Integer(2));
        s.push(new Float(3.0));
        s.push("one too many");

        try
        {
            System.out.println("Top: " + s.top());
            System.out.println("Popping...");

            while (s.size() > 0)
                System.out.println(s.pop());
        }
        catch(StackException x)
        {
            throw new InternalError(x.toString());
        }
    }
}

/* Output:
Initial size = 0
Top: one too many
Popping...
one too many
3.0
2
one
*/

January 2000/import java.*/Figure 6

Figure 6: Tests an Object that implements the Stack interface

 
class StackTest3
{
    public static void main(String[] args)
    {
        FixedStack s = new FixedStack(3);
        doTest(s);
    }

    public static void doTest(Stack s)
    {
        System.out.println("Implementation: " +
                           s.getClass().getName());
        try
        {
            System.out.println("Initial size = " + s.size());
            s.push("one");
            s.push(new Integer(2));
            s.push(new Float(3.0));

            s.push("one too many");
        }
        catch(StackException x)
        {
            System.out.println(x);
        }

        try
        {
            System.out.println("Top: " + s.top());
            System.out.println("Popping...");

            while (s.size() > 0)
                System.out.println(s.pop());
        }
        catch(StackException x)
        {
            throw new InternalError(x.toString());
        }
    }
}

/* Output:
Implementation: FixedStack
Initial size = 0
StackException: overflow
Top: 3.0
Popping...
3.0
2
one
*/

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.