Section 12.3 Standardizing Behavior: Interfaces and ADTs
In the previous section, we established the need for a
dynamic collection, specifically a list, that can grow as needed, overcoming the fixed-size limitation of basic arrays. Our goal is to build such a list, which we’ll call
ArrayList<T>.
Now, let’s consider a new challenge. Imagine you and several classmates are all asked to create a dynamic list class. You might create an
add(item) method, but another classmate might name theirs
insertElement(item), and a third might use
append(item). Similarly, methods to check the number of items might be called
getSize(),
count(), or
length(). While each list might work internally, this inconsistency creates a major problem:
how can you write code that reliably uses any of these different list implementations? For instance, if you wanted to write a simple utility method like
printAnyList( /* some list */ ), you wouldn’t know whether to call
.getSize() or
.count() to loop correctly! Code reuse becomes difficult, and combining different list implementations in a larger program turns into a confusing mess.
// --- Problem Illustration: Inconsistent List Implementations ---
// Hypothetical List Class 1
class MyListV1 {
void add(String item) { /* ... */ }
int getSize() { /* ... */ return 0;}
String getAtIndex(int index) { /* ... */ return null; }
// ... other methods ...
}
// Hypothetical List Class 2
class AnotherList {
void insertItem(String data) { /* ... */ }
int count() { /* ... */ return 0;}
String retrieve(int pos) { /* ... */ return null; }
// ... other DIFFERENT methods ...
}
// Now, try writing a reusable method to print ANY list...
class ListUtils {
static void printAnyList( ??? list ) { // What type should the parameter be??
System.out.print("[");
// How many times to loop? Call list.getSize()? or list.count()?
// ERROR: We don't know which method name exists!
int size = ??? ; // Problem 1: Which size method?
for (int i = 0; i < size; i++) {
// How to get the element? list.getAtIndex(i)? or list.retrieve(i)?
// ERROR: We don't know which retrieval method exists!
String element = ??? ; // Problem 2: Which get method?
System.out.print(element);
if (i < size - 1) {
System.out.print(", ");
}
}
System.out.println("]");
// This reusable method is impossible to write reliably without a standard contract!
}
}
This is the problem that
interfaces solve in object-oriented programming (See
Chapter 7,
Chapter 9). An interface acts as a
contract or a blueprint for behavior. It defines a set of
public method signatures (the method name, parameters, and return type) but provides
no implementation for those methods (mostly; default methods are an exception we won’t focus on here). Think of it as a list of required job functions without specifying exactly how to perform them.
// Example structure of an interface definition
public interface SomeContract {
// Method signatures define WHAT implementing classes MUST provide
ReturnType methodName1(ParamType1 param1);
boolean anotherMethod(ParamType2 param2, ParamType3 param3);
// No method bodies { ... } here!
}
When a class declares that it
implements an interface (e.g.,
public class MyList implements SomeContract), it makes a binding promise to the Java compiler: "I guarantee I will provide concrete implementations for
methodName1 and
anotherMethod, exactly as specified in the
SomeContract interface." The compiler enforces this promise rigorously; if the class fails to provide an implementation for any method in the contract, a compile-time error occurs.
This concept of defining behavior separately from implementation is central to the idea of an
Abstract Data Type (ADT). An ADT is a conceptual model that specifies the
operations a data type must support (like
add,
remove,
get,
size for a list) and the expected behavior of those operations, without dictating *how* the data is stored or how the operations are implemented internally. The interface is the Java mechanism we use to formally define the contract specified by an ADT.
For our
ArrayList<T> project, we will work with two ADT interfaces that are provided for you:
-
CollectionADT<T>: This interface defines the most fundamental behaviors common to nearly all collections, such as:
-
Checking if the collection is empty (isEmpty()).
-
Getting the number of items (size()).
-
Checking if an item exists in the collection (contains(T item)).
The
<T> indicates this is a
generic interface (See
Chapter 10), meaning it can define a collection holding any reference type (like
String,
Player, or
Integer).
-
ListADT<T>: This interface
extends CollectionADT<T> (meaning it includes all methods from
CollectionADT) and adds operations specific to ordered lists, where elements have positions (indices). Examples include:
-
Adding an element at a specific index (add(int index, T item)).
-
Removing the element at a specific index (remove(int index)).
-
Getting the element at a specific index (get(int index)).
Connection to the Design Recipe: These interfaces (
CollectionADT<T> and
ListADT<T>) represent our formal specification. They embody parts of
Step 0 (Understand & Restate) by defining the required operations,
Step 1 (Data Definitions) at an abstract level by defining the *concept* of a generic List, and
Step 2 (Method Signature & Purpose) by providing the exact method signatures and intended purpose (which you can explore via the Javadoc comments in the provided interface files once we discuss the project setup). Our task in the subsequent Design Recipe steps will be to fulfill this contract using a dynamic array approach.
You have attempted
of
activities on this page.