CISC 3115
Modern Programming Techniques
Lecture 6
Interfaces


If it looks like a duck, walks like a duck, and sounds like a duck … All surgeons are doctors, but not all doctors are surgeons

Interfaces: Overview

Interface

General Usage

A point where two systems, subjects, organizations, etc. meet and interact.

Computing

Interface (computing): In computing, an interface is a shared boundary across which two or more separate components of a computer system exchange information. The exchange can be between software, computer hardware, peripheral devices, humans, and combinations of these.

In Object-Oriented Programming (OOP)

Interface (object-oriented programming)

In object-oriented programming, an interface is a data type that acts as an abstraction of a class. It describes a set of method signatures, the implementations of which may be provided by multiple classes that are otherwise not necessarily related to each other. A class which provides the methods listed in a protocol is said to implement the interface.[1]

If objects are fully encapsulated then the interface is the only way in which they may be accessed by other objects. For example, in Java, the Comparable interface specifies a method compareTo() which implementing classes must implement. This means that a sorting method, for example, can sort a collection of any objects of types which implement the Comparable interface, without having to know anything about the inner nature of the class (except that two of these objects can be compared by means of compareTo()).

In Java

Interface (Java)

An interface in the Java programming language is an abstract type that is used to declare a behavior that classes must implement. Interfaces are declared using the interface keyword, and may only contain method signature and constant declarations (variable declarations that are declared to be both static and final). All methods of an Interface do not contain implementation (method bodies) as of all versions below Java 8.

Interfaces cannot be instantiated, but rather are implemented. A class that implements an interface must implement all of the methods described in the interface, (or be an abstract class). Object references in Java may be specified to be of an interface type; in each case, they must either be null, or be bound to an object that implements the interface.

(One benefit of using interfaces is that they simulate multiple inheritance. All classes in Java must have exactly one base class, the only exception being java.lang.Object (the root class of the Java type system); multiple inheritance of classes is not allowed. However, an interface may inherit multiple interfaces and a class may implement multiple interfaces.)

Change of Representation: Social Security Number

Modelling Social Security Numbers

Let us introduce a class that represents social security numbers.

Specification of Behavior

class SSNum {
	…

	public int getArea() {…}
	public int getGroup() {…}
	public int getSerial() {…}

	public String toString() {…}
	…
}
Notes

Choosing the Internal Representation

We decide to represent the ssnum as a String, complete with the separating '-'s.
class SSNum {
	SSNum(String ssnum) {this.ssnum = ssnum;}

	public int getArea() {return Integer.parseInt(ssnum.substring(0, 3));}
	public int getGroup() {return Integer.parseInt(ssnum.substring(4, 6));}
	public int getSerial() {return Integer.parseInt(ssnum.substring(7));}

	public String toString() {return ssnum;}
	
	private String ssnum;
}
Notes

Coding a Demo App for our Class

class SSNumApp {
	public static void main(String [] args) {
		SSNum sSNum = new SSNum("123-45-6789");

		System.out.println(sSNum);
		System.out.println(sSNum.getArea());
		System.out.println(sSNum.getGroup());
		System.out.println(sSNum.getSerial());
	}
}

A Change of Implementation (Representation)

We decide we'd like to change our implementation (i.e., internal representation) to an int (e.g., it takes up less space than the 11-byte string).
class SSNum {
	SSNum(String ssnum) {
		this.ssnum = Integer.parseInt(ssnum.substring(0, 3)) * 1000000  + Integer.parseInt(ssnum.substring(4, 6)) * 10000 + Integer.parseInt(ssnum.substring(7));}

	public int getArea() {return ssnum / 1000000;}
	public int getGroup() {return (ssnum / 10000) % 100;}
	public int getSerial() {return ssnum % 10000;}

	public String toString() {return String.format("%03d-%02d-%04d", getArea(), getGroup(), getSerial());}
	
	private int ssnum;
}
Notes

The Demo App Remains Unchanged

Some Issues

The following may not be compelling arguments just yet, but they help set the stage for what is coming. The upshot is that in situations where there is a clear set of behavior, and potentially more than one implementation, we should strive to limit ourselves to the common behavior.

Interfaces

The following is a standard approach to coding robust and flexible software, and, in particular the sort of coding we will be doing in this course.

Java's interface construct allows us to specify a set of behavior in the form of method specifications. The interface forms a data type, i.e., a specification of a set of values and the valid operations on those values. Any class that implements the interface is said to belong to the data type of the interface, i.e., an object of the implementation class is-a object of the interface type as well, and can appear in any context interface reference variables appear. Here is an interface for our social security number:

interface SSNum {
	int getArea();
	int getGroup();
	int getSerial();
}
Notes

Implementations of the Interface

We now restate our two implementations in terms of the interface:
class StringSSNum implements SSNum {
	StringSSNum(String ssnum) {this.ssnum = ssnum;}

	public int getArea() {return Integer.parseInt(ssnum.substring(0, 3));}
	public int getGroup() {return Integer.parseInt(ssnum.substring(4, 6));}
	public int getSerial() {return Integer.parseInt(ssnum.substring(7));}

	public String toString() {return ssnum;}
	
	private String ssnum;
}
class IntSSNum implements SSNum {
	IntSSNum(String ssnum) {this.ssnum = Integer.parseInt(ssnum.substring(0, 3)) * 1000000 + 
										Integer.parseInt(ssnum.substring(4, 6)) * 10000 + Integer.parseInt(ssnum.substring(7));}

	public int getArea() {return ssnum / 1000000;}
	public int getGroup() {return (ssnum / 10000) % 100;}
	public int getSerial() {return ssnum % 10000;}

	public String toString() {return String.format("%03d-%02d-%04d", getArea(), getGroup(), getSerial());}
	
	private int ssnum;
}

The Apps

class StringSSNumApp {
	public static void main(String [] args) {
		StringSSNum stringSSNum = new StringSSNum("123-45-6789");

		System.out.println(stringSSNum);
		System.out.println(stringSSNum.getArea());
		System.out.println(stringSSNum.getGroup());
		System.out.println(stringSSNum.getSerial());
	}
}
class IntSSNumApp {
	public static void main(String [] args) {
		IntSSNum intSSNum = new IntSSNum("123-45-6789");

		System.out.println(intSSNum);
		System.out.println(intSSNum.getArea());
		System.out.println(intSSNum.getGroup());
		System.out.println(intSSNum.getSerial());
	}
}
Notes

Programming to the Interface

An interface specifies the required/recommended behavior (set of methods) via which one interacts with any class implementing the interface.

Revisiting our Apps

As we mentioned, the apps for the two ssnum implementations are identical except for the creation of the objects and the variable names.
class SSNumApp {
	public static void demo(SSNum sSNum) {
		System.out.println(sSNum);
		System.out.println(sSNum.getArea());
		System.out.println(sSNum.getGroup());
		System.out.println(sSNum.getSerial());
	}
}
class StringSSNumApp {
        public static void main(String [] args) {
                SSNumApp.demo(new StringSSNum("123-45-6789"));
        }
}
class IntSSNumApp {
        public static void main(String [] args) {
                SSNumApp.demo(new IntSSNum("123-45-6789"));
        }
}

Change of Functionality: Counter

interface  Counter {
	void up();
	void down();
	int getVal();
}

public class UnboundedCounter implements Counter {
	UnboundedCounter() {val = 0;}

	public void up() {val++;}	
	public void down() {val--;}	

	public int getVal() {return val;}

	public String toString() {return "A counter with value " + val;}

	private int val = 0;
}

public class UpperBoundedCounter implements Counter {
	UpperBoundedCounter(int limit) {
		val = 0;
		this.limit = limit;
	} 

	public void up() {if (val < limit) val++;}	
	public void down() {val--;}	

	public int getVal() {return val;}
	int getLimit() {return limit;}

	public String toString() {return "A upper-bounded counter with value " + val + " and limit " + limit;}

	private int val = 0;
	private int limit;
}

public class App {
	public static void main(String [] args) {
		doIt(new UnboundedCounter());
		doIt(new UpperBoundedCounter(10));
	}

	static void doIt(Counter c) {
		System.out.println(c.getClass().getName());

		System.out.println("Initially: " + c);

		for (int i = 1; i <= 20; i++)
			c.up();
		System.out.println("After 20 up's: " + c);

		for (int i = 1; i <= 3; i++)
			c.down();
		System.out.println("After 3 down's: " + c);

		System.out.println();	//---------
	}
}
Notes

Enforcement of Behavior: Sortable/Comparable

interface Sortable {
	int compareTo(Sortable other);
}

class SortableInt implements Sortable {
	SortableInt(int i) {this.i = i;}

	public int compareTo(Sortable other) {
		return Integer.compare(i, ((SortableInt)other).i);
	}

	public String toString() {return "SortableInt " + i;}

	private int i;
}

class SortableString implements Sortable {
	SortableString(String s) {this.s = s;}

	public int compareTo(Sortable other) {
		return (s).compareTo(((SortableString)other).s);
	}

	public String toString() {return "SortableString " + s;}


	private String s;
}

import java.util.*;

class Sorter {
	public static void main(String [] args) {
		Random r = new Random(12345);

		SortableInt [] sortableInts = new SortableInt[10];

		for (int i = 0; i < sortableInts.length; i++)
			sortableInts[i] = new SortableInt(r.nextInt(1000));

		System.out.println( "sortableInts (before sort): " + toString(sortableInts));
		sort(sortableInts);
		System.out.println( "sortableInts (after sort): " + toString(sortableInts));
		
		System.out.println();

		SortableString [] sortableStrings = new SortableString[8];

		for (int i = 0; i < sortableStrings.length; i++)
			sortableStrings[i] = new SortableString("Str" + r.nextInt(1000));

		System.out.println( "sortableStrings (before sort): " + toString(sortableStrings));
		sort(sortableStrings);
		System.out.println( "sortableStrings (after sort): " + toString(sortableStrings));
	}


	static String toString(Sortable [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}

	static void sort(Sortable [] arr) {
		for (int last = arr.length-1; last >= 0; last--)
			for (int i = 0; i < last; i++)
				if (arr[i].compareTo(arr[i+1]) > 0) {
					Sortable temp = arr[i];
					arr[i] = arr[i+1];
					arr[i+1] = temp;
				}
	}
}

The output:

sortableInts (before sort): {SortableInt 251, SortableInt 80, SortableInt 241, SortableInt 828, SortableInt 55, SortableInt 84, SortableInt 375, SortableInt 802, SortableInt 501, SortableInt 389}
sortableInts (after sort): {SortableInt 55, SortableInt 80, SortableInt 84, SortableInt 241, SortableInt 251, SortableInt 375, SortableInt 389, SortableInt 501, SortableInt 802, SortableInt 828}

sortableStrings (before sort): {SortableString Str517, SortableString Str942, SortableString Str390, SortableString Str806, SortableString Str12, SortableString Str384, SortableString Str787, SortableString Str303}
sortableStrings (after sort): {SortableString Str12, SortableString Str303, SortableString Str384, SortableString Str390, SortableString Str517, SortableString Str787, SortableString Str806, SortableString Str942}
Notes
Before we leave this example, look at the following variation on Sorter:
import java.util.*;

class Sorter2 {
	public static void main(String [] args) {
		Random r = new Random(12345);

		Sortable [] sortables = new Sortable[10];

		for (int i = 0; i < sortables.length; i++)
			sortables[i] = new SortableInt(r.nextInt(1000));

		System.out.println( "sortables (before sort): " + toString(sortables));
		sort(sortables);
		System.out.println( "sortables (after sort): " + toString(sortables));
		
		System.out.println();
	}


	static String toString(Sortable [] arr) {
		String result = "{";
		for (int i = 0; i < arr.length; i++)
			result += arr[i] + (i < arr.length-1 ? ", " : "");
		return result + "}";
	}

	static void sort(Sortable [] arr) {
		for (int last = arr.length-1; last >= 0; last--)
			for (int i = 0; i < last; i++)
				if (arr[i].compareTo(arr[i+1]) > 0) {
					Sortable temp = arr[i];
					arr[i] = arr[i+1];
					arr[i+1] = temp;
				}
	}
}

One final note: the Sortable interface exists in Java as the Comparable interface and is the basis for allowing sorting of classes regardless of their behavior — as long as they implement Comparable

Interface Inheritance: Collection/List

The Collection Interface

interface Collection {
	boolean add(int value);
	boolean remove(int value);
	int size();
	boolean isEmpty();
}

class Set implements Collection {
	public boolean add(int value) {
		if (find(0, size-1, value) != -1) return false;
		values[size] = value;
		size++;
		return true;
	}

	public boolean remove(int value) {
		int pos = find(0, size-1, value);
		if (pos == -1) return false;
		shiftLeft(pos, size-1);
		size--;
		return true;
	}

	public int size() {return size;}
	public boolean isEmpty() {return size() == 0;}

	public String toString()  {
		String result = "{";
		for (int i = 0; i < size; i++)
			result += values[i] + (i < size-1 ? ", " : "");
		return result + "}";
	}

	private int find(int lo, int hi, int value) {
		if (lo > hi) return -1;
		if (value == values[lo]) return lo;
		return find(lo+1, hi, value);
	}

	private void shiftLeft(int pos, int hi) {
		if (pos > hi) return;
		values[pos] = values[pos+1];
		shiftLeft(pos+1, hi);
	}
	
	private static final int CAPACITY = 100;
	private int [] values = new int[CAPACITY];
	private int size = 0;
}

class Vector implements Collection {
	public boolean add(int value) {
		values[size] = value;
		size++;
		return true;
	}

	public boolean add(int pos, int value) {
		shiftRight(pos, size-1);
		values[pos] = value;
		size++;
		return true;
	}

	public boolean remove(int value) {
		int pos = find(0, size-1, value);
		if (pos == -1) return false;
		shiftLeft(pos, size-1);
		size--;
		return true;
	}

	public int removeAt(int pos) {
		int hold = values[pos];
		shiftLeft(pos, size-1);
		size--;
		return hold;
	}

	public int get(int pos) {
		return values[pos];
	}

	public void set(int pos, int value) {
		values[pos] = value;
	}

	public int size() {return size;}
	public boolean isEmpty() {return size() == 0;}

	public String toString()  {
		String result = "{";
		for (int i = 0; i < size; i++)
			result += values[i] + (i < size-1 ? ", " : "");
		return result + "}";
	}

	private int find(int lo, int hi, int value) {
		if (lo > hi) return -1;
		if (value == values[lo]) return lo;
		return find(lo+1, hi, value);
	}

	private void shiftLeft(int pos, int hi) {
		if (pos > hi) return;
		values[pos] = values[pos+1];
		shiftLeft(pos+1, hi);
	}
	
	private void shiftRight(int pos, int hi) {
		if (hi < pos) return;
		values[hi+1] = values[hi];
		shiftRight(pos, hi-1);
	}
	

	private static final int CAPACITY = 100;
	private int [] values = new int[CAPACITY];
	private int size = 0;
}

import java.util.*;

class CollectionApp {
	public static void main(String [] args) {
		doIt(new Set());
		System.out.println();
		System.out.println();
		doIt(new Vector());
	}

	static void doIt(Collection c) {
		System.out.println("===== " + c.getClass().getName());

		Random r = new Random(12345);

		System.out.println("--- Adding");
		for (int i = 0; i < 20; i++) {
			int num = r.nextInt(10);
			System.out.println(num + " -> " + c.add(num) + " " + c);
		}

		System.out.println();

		System.out.println("--- Removing");
		while (!c.isEmpty()) {
			int num = r.nextInt(10);
			System.out.println(num + " -> " + c.remove(num) + " " + c);
		}
	}
}

The List Interface

interface List extends Collection {
	int get(int index);
	void set(int index, int value);
	boolean add(int index, int value);
	int removeAt(int index);
}

class Vector implements List {
	…
}

import java.util.*;

class ListApp {
	public static void main(String [] args) {
		doIt(new Vector());
	}

	static void doIt(List l) {
		System.out.println("===== " + l.getClass().getName());
		System.out.println();
		System.out.println("Calling CollectionApp.doIt");
		CollectionApp.doIt(l);

		System.out.println();
		System.out.println();
		System.out.println("Perfoming ListApp.doIt");
		Random r = new Random(12345);

		System.out.println("--- Indexed Adding");
		for (int i = 0; i < 20; i++) {
			int pos = r.nextInt(l.size()+1);
			int num = r.nextInt(10);
			System.out.println(num + " @" + pos  + " -> " + l.add(pos, num) + " " + l);
		}

		System.out.println();

		System.out.println("--- Getting");
		for (int i = 0; i < l.size(); i++) 
			System.out.println("@" + i + " -> " + l.get(i));
	

		System.out.println();

		System.out.println("--- Setting");
		for (int i = 0; i < l.size(); i++) {
			int num = r.nextInt(10);
			l.set(i, num);
			System.out.println(num + " @" + i  + " -> " + l);
		}

		System.out.println();

		System.out.println("--- Indexed Removing");
		while (!l.isEmpty()) {
			int pos = r.nextInt(l.size());
			System.out.println("@" + pos + " -> " + l.removeAt(pos) + " " + l);
		}
	}
}

Classes can implement multiple interfaces (just like someone could be a doctor and a lawyer and an EMT, etc … wearing different hats).

Files used in this Lecture