5.5. Using an Interface¶
In this section, we will motivate why we set up the
interface/implementing class relationship. We often say that the
techniques taught in 1302 allow us to reduce the amount of code we
have to write. However, adding the interface relationship to the
Fancy
and SuperFancy
classes actually requires us to write
more code (in the interface).
So, where is the benefit? Well, the benefit comes when we use these classes in a calling method or a driver class!
A more formal way of thinking about the benefit is that we get type
compatibility between the interface type (Styleable
) and
the implementing class type(s) (Fancy
and SuperFancy
) that
will allow us to use a variable of type Styleable
to refer to
objects of type Fancy
or SuperFancy
. Still not quite clear?
Hang in there, we have some examples.
Leveraging the type compatibility in an interface relationship leads to more elegant code that works with objects of any class that implements the interface. Connecting this to our real-world example from section 1, we could write code that works with all athletes instead of having to write separate (but similar) code for each type of athlete. Would you rather write one method that works for all athletes or write individual methods for basketball players, football players, track athletes, golfers, etc.?
With that in mind, let’s go back to our Styleable
example:
Interfaces are reference types in Java. This means that they can serve as the type for a reference variable. You should be familiar with the use of class names for reference variable types. The code snippet below illustrates both scenarios:
Fancy f; Styleable s;
Remember Reference Types (If you need a refresher)
Remember, a reference type in Java is any type that can serve as the type for a variable that refers to an object. Such a variable is known as a reference variable. We will elaborate on this terminology in the context of interfaces a little more later in this tutorial. If you are unfamiliar with these terms in general, please review the Reference Variables Chapter. You are encouraged to ask questions about any parts that you find confusing.
Reference variables are called as such because they refer to objects. However, you can only create objects from classes (not interfaces)! Therefore, what can a
Styleable
variable refer to? The answer is that a variable with an interface as its type can refer to an object of any class that implements that interface. The code snippet below illustrates this:Styleable s = new Fancy("some message");
Styleable s = new Styleable();
When an object is referred to via a reference variable with an interface type, the only methods then can be called using that variable are the ones declared in the interface, regardless of whether the object’s class declared other methods. For example, even though the
getAbout()
method is declared in theSuperFancy
class and therefore is part of aSuperFancy
object, it would not be available via aStyleable
variable. The following two code snippets illustrate this difference:SuperFancy sf = new SuperFancy("some fancier message?"); sf.style(); // OK sf.unstyle(); // OK String about = sf.getAbout(); // OK -- variable type is SuperFancy
Styleable s = new SuperFancy("some fancier message?"); s.style(); // OK s.unstyle(); // OK String about = s.getAbout(); // NOT OK! -- variable type is Styleable
Test Yourself
Why can’t we call
getAbout
in the second example above? You may not be confident in your answer at this point, but take a guess. Write down your thoughts in your notes.Test Yourself Solution (View after answering the question above)
To answer this question, we have to understand the difference between the variable and the object and the types of objects that a variable can refer to. In this case, the variable is of type
Styleable
so that variable can refer to objects of typesFancy
orSuperFancy
since those are the two implementing classes.Java only considers the type of the variable when determining which methods can be called, so it doesn’t know whether the variable refers to a
Fancy
object or aSuperFancy
object. Because of this, it can’t say with certainty whether or not the object has agetAbout
method as that method is only available to objects of typeSuperFancy
.Note
The only other methods available via a reference variable with an interface type are the methods listed in the java.lang.Object class, which are common to all objects in Java. We will come back to the
Object
class in a future tutorial or reading, but it includes methods likeequals
andtoString
.You are probably wondering why the previous example is useful. In general, you should try to be specific with the types you use for variables when possible. However, the ability to assign object references to variables with interface types leads to a powerful programming technique known as polymorphism. Polymorphism is a fancy word (might be fun to style it in a super fancy way!), derived from the Greek words poly and morphus, which roughly translates to many bodies. Polymorphism leverages our ability to have a variable appear to take on many forms (or bodies) depending on the object it refers to.
Consider the following code snippet:
Styleable s; s = new Fancy("some fancy message"); s.style(); System.out.println(s); // invoke toString() method s = new SuperFancy("some fancier message?"); s.style() System.out.println(s); // invoke toString() method
Notice how we were able to refer to two different objects using the same variable! When
s.style()
is called the first time, it invokes theFancy
class version of the method, because that’s the type of object being referred to. Whens.style()
is called the second time, it invokes theSuperFancy
version of the method for a similar reason. The exact same thing happens with the implicit call totoString()
when printing the objects.The real benefit of polymorphism is that it enables us to write code using the interface type instead of having to write the same code for different types of compatible objects. The fact that variable
s
in the code above can refer to objects of any implementing class type, enables us to write the following method incs1302.interfaces.StyleDriver
:public static void test(String testName, Styleable s) { System.out.printf("# %s Test\n", testName); System.out.println(s); s.style(); System.out.println(s); s.unstyle(); System.out.println(s); } // test
Here, we create a single method that works for objects of any implementing class. Take a look at the
main
method inStyleDriver
and notice how we are able to call thetest
method by passing in a reference to aFancy
object or aSuperFancy
object!Now, Imagine that we have an interface with dozens (or hundreds!) of implementing classes. We could write 1 method that would work with all of those objects instead of having to create dozens (or hundreds) of separate methods where each method has almost identical code.
Another way to view this benefit is that the real savings comes when you use an interface - not when you are creating the interface relationship with the implementing classes.
Test Yourself
Write what you expect the output to be from the execution of
StyleDriver
. Then, compile and run the starter code provided in this tutorial. Since there are multiple dependencies, the order of compilation matters:src/cs1302/interfaces/contract/Styleable.java
src/cs1302/interfaces/impl/Fancy.java
src/cs1302/interfaces/impl/SuperFancy.java
src/cs1302/interfaces/StyleDriver.java
Remember, you may need to specify the classpath in addition to the destination when using
javac
to compile Java code that depends on other Java code. If you need a refresher on this subject, then refer to the Java Packages Tutorial.Walkthrough Video (if you get stuck compiling / running)
Rapid Fire Review
What does the interface/implementing class relationship provide in Java?
The ability to write more complex code.
Type compatibility between the interface type and the implementing class type.
A way to create objects from interfaces.
A method to declare variables without assigning them a type.
What can a variable of an interface type refer to in Java?
Any object of a class that implements the interface.
Any object, regardless of class.
Only objects of the same interface type.
An instance of the interface itself.
What methods can be called on an object using a reference variable with an interface type?
All methods declared in the class of the object.
Only the methods declared in the interface.
Methods declared in both the interface and the class.
Only the methods overridden from the superclass.
Which of the following code snippets will compile?
Styleable s = new Styleable();
Styleable s = new Fancy("some message");
Fancy f = new Styleable();
SuperFancy sf = new Styleable();
SuperFancy f = new Fancy("some message");
Why might using an interface lead to more elegant code?
It allows the creation of objects from the interface.
It requires writing more code for each class.
It enables writing code that works with all classes that implement the interface.
It restricts the use of methods to only those declared in the interface.
Test Yourself (Another Interface Example)
Imagine we have the following classes in a program:
An interface named
Movable
containing the following entities:Methods:
void move()
void stop()
A class named
Car
that implementsMovable
containing the following entities:Instance Variables:
String made
;Methods:
void move()
void stop()
String getMake()
void setMake(String make)
A class named
Dog
that implementsMovable
containing the following entities:Instance Variables:
String name
Methods:
void move()
void stop()
String getName()
void setName(String name)
Which of the following will compile?
Car myCar = new Car(); myCar.setMake("Tesla"); Movable movableVehicle = new Car(); Movable movableVehicle2 = myCar; movableVehicle.move(); movableVehicle.stop();
Car myCar = new Car(); myCar.setMake("Tesla"); Movable movableVehicle2 = myCar; movableVehicle2.getMake();
Dog fido = new Dog(); fido.setName("fido"); Movable dog = fido; System.out.println(dog.getName());
Test Yourself Solution (Check after answering the question above)
This will compile. We are referencing objects of type
Car
withMovable
references. Then, we call methodsmove
andstop
which are available in the interface.This will not compile. We attempt to call the
getMake
method on a variable of typeMovable
. However, thegetMake
method is not available in the interface.This will not compile. We attempt to call the
getName
method on a variable of typeMovable
. However, thegetName
method is not available in the interface.