Java has two mechanisms for defining a type that has multiple different implementations.
The first mechanism is abstract classes. An abstract class serves as the superclass for the multiple implementations (the subclasses). The subclasses inherit from (extend) the abstract superclass.
The second mechanism is interfaces. Interfaces are different from inheritance. An interface defines the methods that a class must implement.
Both abstract classes and interfaces allow a client to write code based on a single API.
An interface is a group of one or more methods that a class promises
to implement. For example, objects that have a natural order (numbers,
strings, dates, etc) usually promise to implement the Comparable
interface:
// does String s come before t (dictionary order)?
if (s.compareTo(t) < 0)
{
// do something
}
// is Fraction f smaller than g?
if (f.compareTo(g) < 0)
{
// do something
}
// is Date d before e?
if (d.compareTo(e) < 0)
{
// do something
}
public interface Comparable<T> { int compareTo(T other); }
The Comparable
interface says that any class that
implements the interface must provide a method named compareTo
that returns a negative integer, zero, or a positive integer as this
object is less than, equal to, or greater than the specified object.
An interface is not a class; you cannot create an instance of an interface. Instead, you must find a class that implements the interface and create an instance of that class.
You can tell if a class implements an interface based on its API:
public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence
Notice that unlike inheritance, a class may implement multiple interfaces.
Even though Comparable
is not a class, it is still a valid
type; thus, a String
is-a Comparable
. You can
even create a reference to a Comparable
object:
public class Interface { public static void main(String[] args) { Comparable<String> s = "hello"; System.out.println(s.compareTo("goodbye")); } }
This is an odd example, but you will see many more examples in Chapter 10.
You have already seen the Iterable
interface; most collection-like classes implement
this interface.
public interface Iterable<T> { Iterator<T> iterator(); }
The Iterable
interface is what allows you to write for-each loops:
// gc is a GlobalCredit for (CreditCard cc : gc) { output.println(cc.getNumber()); }
// pf is a Portfolio for (Investment inv : pf) { output.println(inv.getStock()); }
// stu is a Student for (String course : stu) { output.println(stu.getCourseGrade(course)); }
Fun fact: Iterator
is itself an interface.
In Chapter 9 we studied three different collections:
Portfolio
: index-based
collection of Investment
s
GlobalCredit
: collection of
unique CreditCard
s
Student
: collection of String
s
representing course names that map
to grades
Collections are used often when writing code. It would be very tedious and error prone if a client had to create their own collection classes. Java provides a set of software components that are used to represent and manipulate collections of any object type. The Java Collection Framework is made up of:
The official tutorial for Java collections is http://download.oracle.com/javase/tutorial/collections/index.html .
The UML diagram for the Collection
interface
hierarchy is shown below:
The Collections Framework also includes a hierarchy of interfaces that are
not rooted at Collection
; nevertheless, the Map
interface is an important part of the Collections Framework.
Collection
Interface
A Collection
represents a group of objects where
each object is called an element of the collection. The interface
defines the most general operations that a client can ask
a collection to perform:
public interface Collection<E> extends Iterable<E> { // Basic operations int size(); boolean isEmpty(); boolean contains(Object element); boolean add(E element); //optional boolean remove(Object element); //optional Iterator<E> iterator(); // Bulk operations boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); //optional boolean removeAll(Collection<?> c); //optional boolean retainAll(Collection<?> c); //optional void clear(); //optional // Array operations Object[] toArray(); <T> T[] toArray(T[] a); }
Collection
Interface
public interface Collection<E> extends Iterable<E>
{
// Basic operations
int size();
boolean isEmpty();
boolean contains(Object element);
boolean add(E element); //optional
boolean remove(Object element); //optional
Iterator<E> iterator();
// Bulk operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c); //optional
boolean removeAll(Collection<?> c); //optional
boolean retainAll(Collection<?> c); //optional
void clear(); //optional
// Array operations
Object[] toArray();
<T> T[] toArray(T[] a);
}
Collection
Interface
public interface Collection<E> extends Iterable<E>
{
// Basic operations
int size();
boolean isEmpty();
boolean contains(Object element);
boolean add(E element); //optional
boolean remove(Object element); //optional
Iterator<E> iterator();
// Bulk operations
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c); //optional
boolean removeAll(Collection<?> c); //optional
boolean retainAll(Collection<?> c); //optional
void clear(); //optional
// Array operations
Object[] toArray();
<T> T[] toArray(T[] a);
}
Recall that GlobalCredit
was a collection of credit cards
and Portfolio
was a collection of investments. In general,
a collection needs to be able to hold elements of some type E
.
How might we create a collection that can hold any type E
?
We know that every class has Object
at the root of its
inheritance hierarchy, so a possible solution is to create a collection
that holds Object
references (because every
class type is substitutable for Object
).
There are two significant problems
with the collection of Object
approach.
The first problem is that every class is substitutable for
Object
. This means that a client can put anything into a
collection that holds Object
references. If the client
creates a collection of String
s there is nothing preventing
the client from adding a Fraction
to the collection.
The second problem is that such a collection will always return a
reference to an Object
whenever the client retrieves an element
from the collection. This means that the client must always try to cast
the type of the retrieved element to do anything remotely useful.
The designer of the Java language solved the problem by creating a mechanism called generics that allows the client to specify the type of element to use. Suppose you wanted to create a collection of strings:
Collection<String> someStrings = new ArrayList<String>(); // add a string someStrings.add("Hey this works!");
Here's how you read the notation:
Collection<String> |
Collection of String |
ArrayList<String> |
ArrayList of String |
List<String> |
List of String |
You can only use generics if the class or interface is declared as
a generic interface (ie. don't try this with Fraction
or
String
).