Lecture 19

Interfaces

An interface is a special kind of abstract base class (although not really a class)

The general idea with an interface is that it defines a set of operations that instances of classes implementing the interface are capable of performing.

Example: we could have an IDrawable interface for objects that can draw themselves in a window:

import java.awt.Graphics;

public interface IDrawable {
  public void draw(Graphics g);
}

Giving interfaces names that start with the prefix "I" is a common convention.

Note that even though we didn't specify the abstract keyword for the draw method, it is still abstract.

Any number of classes can now implement the IDrawable interface:

In this example, we see that both Face and Photo objects have the capability of drawing themselves in a window.

Note that an interface is not really a class.  In particular, interfaces are not superclasses: a class implementing an interface still has a superclass, either the class is has been declared to extend, or java.lang.Object if it did not explicitly specify a superclass.  In addition, classes may implement any number of interfaces.  For example, let's say a Photo object can not only draw itself in a window, but also save itself to a file.  The file saving capability could be defined as an operation in an ISaveable interface:

Although an interface is not a superclass, it is a supertype, and polymorphism works exactly the same way for interfaces as it does for classes.  Specifically, you can declare a variable to have an interface as its type, and it is legal to assign a reference to any class implementing the interface to that variable.  That means, for example, that parts of the program that are concerned with drawing objects in windows can refer to them using the IDrawable interface, without needing to know what their actual class type is.

When you see the term type being used with respect to objects, you should think of it as meaning either a class or an interface.  An interface is a supertype of a class that implements the interface, and the class is a subtype of the interface.

Standard Collections

Storing and accessing a collection of objects is a very common programming task.

You could use arrays for this purpose, but they are inconvenient if objects need to be removed from the collection, or if the number of objects to be stored is unpredictable.  What we would like is a collection object that will automatically expand and contract as needed as objects are added to and removed from the collection.

Java defines a number of standard collection classes.  They reside in the java.util package.  The java.util.ArrayList is one of the standard collection.  It supports operations (instance methods) to add elements to the collection, remove them from the collection, get the number of elements in the collection, and to get elements back out of the collection.

As the name suggests, the ArrayList class uses an array as its internal storage mechanism.  However, because of encapsulation, clients of ArrayList do not need to know or care about how it manages its storage.

Genericity

One of the main requirements for a collection class is that it should be able to store references to any type of object.  To achieve this goal, a generic type must be used to represent elements (references to objects) in the collection.  Java supports genericity for container elements in two different ways.

The old way is to use java.lang.Object as the generic element type.  This works because every class in Java is a subclass of java.lang.Object: thus, a variable declared to have type Object can store a reference to any kind of object.  The main disadvantage of this approach is that when we retrieve an element from a collection, we almost always have to downcast it before we can do anything useful with it, such as invoke a method on it.  Downcasts are error-prone, as we will see in a moment.

The new way, introduced in Java 1.5, is to define generic container classes to use type parameters.  Type parameters are a better solution because they allow the Java compiler automatically check the type of objects added to a collection, and eliminate the need for the downcast when an object is retrieved from the collection.

Genericity using java.lang.Object

In the old way of doing generics, here are some generic operations supported by the java.util.ArrayList class.

/** Add an object to the collection. */
public boolean add(Object obj) { ... }

/** Return the number of objects in the collection. */
public int size() { ... }

/** Return the object at index i in the collection. */
public Object get(int i) { ... }

/** Remove and return the object at index i in the collection. */
public Object remove(int i) { ... }

Note that whenever the class mentions the type of element to be stored in the collection, it does so using the type java.lang.Object, as highlighted in green.

ArrayListDemo.java shows an example of using an ArrayList to store Strings read from the keyboard.  Note that a downcast is needed when we get items out of the collection.

The problem with using Object as a generic type is that it is easy to introduce bugs resulting from a mismatch between the types of objects put into the collection and the type used in the downcast when items are retrieved from the collection.

For example, imagine that we have a large program, where one part of the program is adding objects to a collection, and another part of the program is removing them from the collection.  For example, the objects added might have nothing in common except that they are instances of some subclass of Object:

ArrayList list = new ArrayList();

...

list.add("Hello"); // add a String
list.add(new Integer(3)); // add an Integer

When the objects are removed from the collection, the person who is writing the code that removes the objects may not correctly understand what type of objects the collection contains.  For example, he or she might think that the collection contains only String objects:

for (int i = 0; i < list.size(); i++) {
  String s = (String) list.get(i);
}

The problem is that some of the objects in the container might not be instances of String (or a subclass of String).  If the object removed from the collection (and returned by the get method) is, for example, an Integer (as in the code above) then the downcast to String will fail with a ClassCastException.  By default, these exceptions will terminate the program, and represent a serious bug in the program.

Genericity using Type Parameters

In the most recent version of Java (1.5, a.k.a. 5.0) a new way to write generic classes, using a language feature called type parameters, has been added.

A type parameter represents the type (class or interface) of the objects that will be stored in a collection.  When you declare a variable whose type is a collection class, or create a new instance of a collection object, you can specify an element type to serve as the value of the type parameter for that particular variable or object.  This informs the compiler of how you are intending to use the collection: what type of objects you plan to put into the collection and take out of the collection.

As an example, here is a snippet showing how the ArrayList class is declared:

public class ArrayList<E> ...

The type parameter, E, is a placeholder represents the type of the elements that will be added to and retrieved from instances of ArrayList.  The methods of ArrayList are declared to use the type parameter whenever they need to refer to the element type.  For example, here are the same 4 methods described above, but using the type parameter E as the element type:

/** Add an object to the collection. */
public boolean add(E obj) { ... }

/** Return the number of objects in the collection. */
public int size() { ... }

/** Return the object at index i in the collection. */
public E get(int i) { ... }

/** Remove and return the object at index i in the collection. */
public E remove(int i) { ... }

When we define a collection variable or create a collection object, we can specify a type (class or interface) as the type value which defines the meaning of E for that particular variable or object.

Example: declaring an ArrayList variable and creating an ArrayList object to store references to String objects:

// Declare a variable to refer to an ArrayList storing Strings
ArrayList<String> list;

// Creating an ArrayList of Strings object and assigning
// it to the variable
list = new ArrayList<String>();

Specifying the element type has two advantages.  First of all, the compiler will not allow us to put any object that is not a String into this array list:

list.add(new Integer(3)); // will not compile!

Secondly, we do not need to perform a downcast when retrieving an element from the collection:

for (int i = 0; i < list.size(); i++) {
  String s = list.get(i); // No downcast needed!
  System.out.println("the string " + s + " has " + s.length() + " characters");
}

Because genericity using type parameters helps prevent bugs by eliminating the need for error-prone downcasts, you should always use type parameters when you use generic classes like collections.