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.
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()
).
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.)
class SSNum { … public int getArea() {…} public int getGroup() {…} public int getSerial() {…} public String toString() {…} … }
String
, complete with the separating '-'s.
toString
.
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; }
Integer.valueOf
is an acceptable alternative to parseInt
Integer
, the latter an int
, but with autoboxing, the
result is basically the same (at least here)
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()); } }
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; }
toString
toString
has to transform the internal representation into a String
toString
which takes the internal representation and transforms it into
a string suitable for human viewing.
StringSSNum
. When we shift to an int
-based
implementation, that name would no longer be appropriate.
StringSSNum
, the app would read:
StringSSNum sSNum = new StringSSNum("123-45-6789");would have to be changed to:
IntSSNum sSNum = new IntSSNum("123-45-6789");(note that I've left the variable name the same 'vanilla' name (
ssNum
); changing it from stringSSNum
to intSSNum
just introduces even more name changing.)
convertToInt
method in our StringSSNum
implementation:
int convertToInt() {…}
StringSSNum
may have thought it a good idea to have such a method; and
furthermore, made heavy use of this method
IntSSNum
) — which justifiably would not have
such a method, the application code would break wherever that method was called.
IntSSNum
) but that then involves making changes
to our tested class.
asInt
would be less
egregious, and could be argued to be appropriate under all implementations.
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(); }
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;
}
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()); } }
StringSSNum
vs IntSSNum
). This is used in
IntSSNum intSSNum
).
new StringSSNum(…);
).
stringSSNum
vs intSSNum
)
ssNum
in both apps.
ssNum
.
IntSSNum
one must write new IntSSNum
,
(clearly one wouldn't write new StringSSNum
and you can't create an SSNum
(it's an interface, not a concrete class).
newIntSSNum
or new String SSNum
), we pass the object in as a parameter,
allowing the caller to be responsible for which to use.
SSNum
by virtue of their
having implemented the SSNum
interface.
SSNum
app, we are also making sure we are only using those methods
of the interface, i.e., we are indeed 'programming to the interface'.
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")); } }
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(); //--------- } }
UnboundedCounter
as well as the UpperBoundedCounter
can be sent as arguments to the Counter
para,eter of doIt
because they both implement the Counter
interface and the thus considered
Counter
objects
UnboundedCounter
is-a Counter
(same for UpperBoundedCounter
).
UpperBoundCounter
's getLimit
method in doIt
as it is not a method of the interface.
Counter
reference back to a UpperBoundedCounter
. This would then permit
getLimit
to be called.
static void doIt(Counter c) { … UpperBounddedCounter ubc = (UpperBoundedCounter)c; System.out.println(ubc.getLimit(); … }
UpperBoundedCounter
argument, it would cause a ClassCastException
when doIt
was invoked with the UnboundedCounter
argument.
UpperBoundedCounter
objects were sent to doIt
; however it requires an explicit cast to force the programmer to pay attention to the fact
that this may fail.
Counter / \ UnboundedCounter UpperBoundedCounterthen we have to following:
UnbounddedCounter
and UpperBoundedCounter
can always be upcast to Counter
and this requires
no explicit cast:
UnboundedCounter uc = new UnboundedCounter(); Counter c = uc; // no cast, will always work
UpperBoundedCounter ubc = new UpperBoundedCounter(10); Counter c = ubc; // no cast, will always work
UnbounddedCounter
and UpperBoundedCounter
can never be cast to each other
UnboundedCounter uc = new UnboundedCounter(); UpperBoundedCounter ubc = uc; // compiler error
UpperBoundedCounter ubc = new UpperBoundedCounter(10); UnboundedCounter uc = new ubc; // compiler error
Counter
can be downcast to both UnbounddedCounter
and UpperBoundedCounter
; however an explicit cast is required as
a ClassCastException
may occur (if the reference is not to an object of the type being cast to).
Counter c = new UnboundedCounter(); UnboundedCounter uc = (UnboundedCounter)c; // ok &helip;c
referenced anUnboundedCounter
object
Counter c = new UnboundedCounter(); UpperBoundedCounter ubc = (UpperBoundedCounter)c; //ClassCastException
; attempting to cast a reference to anUnboundedCounter
object to aUpperBounderCounter
reference
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}
SortableInt
objects are Sortable
objects as well, but not all
Sortable
objects are SortableInt
objects (some of them could be SortableStrings
)
doIt
compareTo
uses arr[i]
as the receiver and arr[i+1] as the argument (sent to parameter other
).
The parameter is clearly declared in the compareTo
method as Sortable
; the question is which compareTo
method is called?
- The answer is one of the core properties of object-oriented programming and directly related to our discussion of the hierarchy, up/downcasting, and reference
variables vs objects.
- Recall that references variables and their types are specified at compile-time when they are declared; objects (and their types
are created at run time, as a result of
new
.
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
Collection
Interfaceinterface 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); } } }
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).