Interfaces
Objective #1: Understand how to use interfaces.
- An interface is a list of method signatures that establish behavior that unrelated classes can share. The methods must be public although the keyword public is optional. An interface cannot contain the implementation (i.e. the body inside of the set of curly braces) of any of the methods that it lists.
Here is an example of an interface named Movable. I made up this interface as a simple example; it is not a famous interface provided by Sun or our textbook.
public interface Movable
{
public void move(int amount);
}
The line of code public void move(int amount); is a method signature. Notice the semicolon at the end of the statement. Also notice that there is no body to this move method. In other words, the move method has no implementation. A method is called an abstract method when it has no implementation.
- An interface cannot contain any instance variables. An interface cannot contain any constructors either. An interface may contain constants though. As we have already learned a constant in Java is simply a field with public static final typed at the front of its declaration statement. But since all variables in an interface are automatically considered to be public static final, these keywords should not be typed in a constant's declaration statement. Here is an example of the Movable interface with a constant named SPEED_OF_LIGHT:
public interface Movable
{
static final int SPEED_OF_LIGHT = 3e8;
public void move(int amount);
}
- Classes can "realize" an interface. Realizing an interface is also called implementing an interface. To realize an interface, a class must fully implement each of the methods listed in the interface. An unlimited number of unrelated classes can implement the same interface. Each of these unrelated classes may implement the methods in the shared interface in different ways.
- Use the keyword implements to make a class realize an interface as in
public class Bug implements Movable
where the class Bug realizes the Movable interface
or
public class Bug implements Comparable
where the class Bug realizes the Comparable interface.
- More than one interface can be realized by a class as in
public class Bug implements Movable, Comparable
where both interfaces Movable and Comparable are realized by the Bug class. A comma is used to separate the interfaces.
- An interface can realize several other interfaces. For example, you could have
public interface Locatable implements Viewable, Positionable
- Using interfaces can reduce coupling between classes.
- Example
public interface Movable
{
static final int SPEED_OF_LIGHT = 3e8;
public void move(int amount);
}
public class Bug implements Movable
{
private int myX;
public void move(int amount)
{
myX += amount;
}
public void eat()
{
System.out.println("That tasted good.");
}
}
public class InterfaceDemo
{
public static void main(String[] args)
{
Bug buggy = new Bug();
buggy.move(5);
System.out.println("Bugs cannot move faster than " + Bug.SPEED_OF_LIGHT);
buggy.eat();
Movable bugsy = new Bug();
bugsy.move(5);
bugsy.eat(); // illegal since eat isn't in Movable interface
}
}
Objective #2: Use the Comparable interface and override its compareTo method.
- Sun's interface java.lang.Comparable is
used by Java programmers to be able to compare objects. The Comparable interface
includes only one method and looks like this:
public interface Comparable
{
public int compareTo(Object other);
}
however you would never need to type out this code since it's already compiled and stored as part of the Java language.
- If a class such as Integer, String, or Rectangle implements the Comparable interface then the compareTo method must be implemented in that class. When implementing the compareTo method, a programmer must decide how he/she wants to compare two objects of a given class and what would make one object less than or greater than another object. In general, the compareTo method returns an integer value less than zero if the this object is less than the parameter to the method. It returns an integer value greater than zero if the this object is greater than the parameter. It returns zero if the two are equal.
- For example, if you had a Student class with gpa, last name, and height properties, you could choose either of these properties or a combination of the properties to be the criteria that determines whether one student is considered to be greater than another student. You could override the compareTo method in this manner
public int compareTo(Object explParam)
{
if (this.myGPA > ((Student) explParam).myGPA)
{
return 1;
}
else if (this.myGPA < ((Student) explParam).myGPA)
{
return -1;
}
return 0;
}
or you could override it like this
public int compareTo(Object explParam)
{
return this.myName.compareTo(((Student) explParam).myName);
}
or you could even override it like this
public int compareTo(Object explParam)
{
if (myGPA + myHeight > ((Student) explParam).myGPA + ((Student) explParam).myHeight)
{
return 1;
}
else if (myGPA + myHeight < ((Student) explParam.)myGPA + ((Student) explParam).myHeight)
{
return -1;
}
return 0;
}
Observe that the use of the this operator is optional in the examples above. But it is necessary to cast the explicit parameter named explParam to a Student object since explParam is passed as an Object.
- When overriding the compareTo method, the exact method header
public int compareTo(Object explParam)
must be used since this is the method header found in the Comparable interface. Any parameter name can be substituted for explParam though. When overriding a method from an interface, the method must be public. It cannot be private.
- The Comparable class does not require the equals method to be implemented since the equals method is inherited by all classes from the Object class. But it is a good idea to override the equals method, especially if you are implementing the Comparable class. Here is an example of how you could override the equals method inherited from the Object class:
public boolean equals(Object explParam)
{
return explParam != null && compareTo(explParam) == 0;
}
Objective #3: Use an interface to take advantage of polymorphism.
- Study the following example where the move method is an example of polymorphism since Bug's and Gecko's move differently. Polymorphism is the concept built into Java that allows a method to work differently for different types of objects. Even though the move method is found in Bug and Gecko below and is spelled the same way in both classes, it works differently in each of those two classes. Polymorphism means "many shapes or many forms".
public interface Movable
{
public void move(int amount);
}
public class Bug implements Movable
{
private int myX;
public void move(int amount)
{
myX += amount;
}
public void eat()
{
System.out.println("That tasted good!");
}
}
public class Gecko implements Movable
{
private int myX;
public void move(int amount)
{
myX += 2 * amount;
}
public void swallowWhole()
{
System.out.println("That tasted good!");
}
}
public class PolymorphismDemo
{
public static void main(String[] args)
{
Bug nemo = new Bug();
Gecko gex = new Gecko();
moveAnything(nemo);
moveAnything(gex);
}
public static void moveAnything(Movable animal)
{
if (animal instanceof Bug)
{
animal.move(10);
}
else if (animal instanceof Gecko)
{
animal.move(8);
}
}
}
- You can use the instanceof operator to determine if an object is of a particular type. The instanceof operator is used above to test what kind of object the parameter animal is. In the method header, animal is a Movable reference. But deep down animal is a reference to either a Bug object or a Gecko object. The instanceof operator can be used to determine which kind of object animal really refers to. The instanceof operator is not tested on the AP exam, but it can be useful especially for safe and flexible programming.
Objective #4: Convert an interface reference to a class type.
- Assuming that the Bug class realizes the Comparable interface, the following statementi is legal and compiles
with no errors:
Comparable nemo = new Bug();
But you can also break this statement into two statements by first declaring a reference and then instantiating the object.
Comparable nemo; // declaring nemo as a reference to a Comparable
nemo = new Bug(); // instantiating nemo as a "deep down" Bug object
But the following statements would not compile correctly:
Bug nemo = new Comparable(); // since there is no constructor in the Comparable interface
Comparable nemo = new Comparable(); // since Comparable is an interface & not a class & constructors aren't allowed in interfaces
- The following code segment is legal since you are not instantiating a Comparable interface but only creating the reference and then instantiating
an object later from a class that realizes the interface.
Scanner keyboard = new Scanner(System.in);
int menuChoice = keyboard.nextInt();
Comparable thing;
if (menuChoice == 1)
thing = new Integer(3);
else if (menuChoice == 2)
thing = new Double(4.5);
else if (menuChoice == 3)
thing = "foobar";
or just
if (menuChoice == 1)
Comparable thing = new Integer(3);
else if (menuChoice == 2)
Comparable thing = new Double(4.5);
else if (menuChoice == 3)
Comparable thing = "foobar";
- If you pass a reference to an interface as a parameter
to a method, you can use any method from the interface. But if you want
to use a method that is not in the interface, you must be sure to cast the
reference before using that method.
The following code would cause an error since itemA is
a Comparable reference and the Comparable interface
does not have an implemented move method.
So itemA does not "know" how
to perform the move method.
public static void animateBiggest(Comparable
itemA, Comparable itemB)
{
if (itemA.compareTo(itemB) > 0)
itemA.move(); // error
else
itemB.move(); // error
}
The following code would work since itemA and itemB are
being casted to an actual Bug object that
knows
how
to move itself.
public static void animateBiggest(Comparable itemA,
Comparable
itemB)
{
if (itemA.compareTo(itemB) > 0)
((Bug) itemA).move();
else
((Bug) itemB).move();
}
Notice the extra set of parentheses that are required to make the cast occur before the dot operator. By default, the dot operator has a higher order of precedence in Java.
- Polymorphism is demonstrated in the code segment below. Depending on the value of the variable menuChoice,
one of three different move methods are going to execute. Since the move method is implemented differently in each of the classes Bug, Human, Helicopter,
the move method takes many forms.
public static void playerTurn(Movable object, int menuChoice)
{
if (menuChoice == 1)
((Bug) object).move(90);
else if (menuChoice == 2)
((Human) object).move(90);
else if (menuChoice == 3)
((Helicopter) object).move(90);
}
The compiler can't determine which version of the move method
will execute at compile time since menuChoice depends
on the user's input. So the Java Virtual Machine (JVM) determines which version
executes at run-time. This is called late-binding (also known
as dynamic binding). By the way, early-binding (also known as
static binding) occurs when there are more than one overloaded version of a method
within
a
class
but
the
compiler decides which version of the method will execute by examining the different
parameter lists.
- You can also convert an object into an interface reference as in
String name = "John";
Comparable thing = name;
System.out.println(name.length()); // displays 4
System.out.println(thing.length()); // error since name is a Comparable reference & can't use any String methods
The following statement would be legal though since casting is used to temporarily convert thing to a String
System.out.println(((String) thing).length()); // displays 4
You do not lose any information in the process. That is, the computer does not forget how long the String is.
- You cannot use the following code segment to try to "trick" the compiler into changing an object into another unrelated object that happens to realize the same interface.
String name = "John";
Comparable thing = name;
Integer other = (Integer) thing; // runtime-error exception error
- Note that when casting a double primitive variable to an int, you do lose information (i.e. the decimal places)
double num = 1.234;
int other = (int) num;
System.out.println((double) other); // prints 1 since the .234 are permanently lost
Objective #5: Explain the usefulness of interfaces.
- If it weren't for common, shared interfaces, the World would be a more confusing place. For example, the classes Chevy, Ford, Chrysler, Toyota, Mazda, etc. all share the same
interfaces since they all agree on having the accelerator to the right, the brake on the left, up for turn signal signaling right turn, down for turn signal signaling
left turn, check engine light showing red if something's wrong, headlight switch somewhere to the left of the steering column, steering wheels being round and not a combination of levers, etc. It
would be difficult to drive a rental car that happens to be a Ford, Chevy, or Toyota when your personal car happens to be a Chrysler if all cars didn't share the same basic interface elements.
- If all classes implement the Comparable interface then it makes it easy for client programmers to write and understand code since they know a compareTo method
exists and they can rely on it always returning a positive value in one case, a negative value in other case, or zero in another case. Having interfaces makes it so easy to use other peoples' classes
that you don't waste time reading manuals, documentation, API's, or tracing other people's code. It's like not having to read the car's manual when you want to drive away in the rental car.
- The benefit of interfaces is not that actual code is being reused but that it makes programming easier for future developers. You may not truly understand this benefit until you've
worked on large projects and have more experience writing client code.