5.6. 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). You are encouraged to take a look at the source code for both Fancy and SuperFancy found in the cs1302-interfaces folder you downloaded earlier in the chapter.

Test Yourself

If adding the interface relationship requires us to write more code, where does the benefit to all of this occur? If you aren't sure, take a quick look back at the payment processor example earlier in the chapter and see if you can come up with the reason.

Test Yourself Solution (View after answering the question above)

The benefit comes when we use these classes in a calling method or a driver class. In the payment processor example, we were able to reduce the number of methods required in the driver program after adding the interface relationship.

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.

With that in mind, let's go back to our Styleable example:

  1. 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 chapter. 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.

  2. 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:

    Listing 5.10 Compatible interface reference assignment
     1package cs1302.interfaces;
     2
     3import cs1302.interfaces.contract.Styleable;
     4import cs1302.interfaces.impl.Fancy;
     5
     6public class Driver {
     7
     8    public static void main(String[] args) {
     9        Styleable s = new Fancy("some message");
    10    } // main
    11
    12} // Driver
    
    Code visualization diagram for java/interfaces/using-an-interface:67 (end of main)

    Compatible interface reference assignment [code listing]

    Listing 5.11 This code will not compile since you can't instantiate interfaces.
    6Styleable s = new Styleable();
    

    Compiler error (expected)

    cs1302/interfaces/contract/Driver.java:6: error: Styleable is abstract; cannot be instantiated
            Styleable s = new Styleable();
                          ^
    1 error
  3. 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 the SuperFancy class and therefore is part of a SuperFancy object, it would not be available via a Styleable variable. The following two code snippets illustrate this difference:

    Listing 5.12 You can call getAbout on a SuperFancy object if you have a SuperFancy variable.
    SuperFancy sf = new SuperFancy("some fancier message?");
    sf.style();                    // OK
    sf.unstyle();                  // OK
    String about = sf.getAbout();  // OK -- variable type is SuperFancy
    
    Listing 5.13 You cannot call getAbout if you have a Styleable variable. Only methods in the Styleable interface are available.
    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 types Fancy or SuperFancy 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 a SuperFancy object. Because of this, it can't say with certainty whether or not the object has a getAbout method as that method is only available to objects of type SuperFancy.

    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 like equals and toString.

  4. 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 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:

     1package cs1302.interfaces;
     2
     3import cs1302.interfaces.contract.Styleable;
     4import cs1302.interfaces.impl.Fancy;
     5import cs1302.interfaces.impl.SuperFancy;
     6
     7public class Driver {
     8
     9    public static void main(String[] args) {
    10        Styleable s;
    11
    12        s = new Fancy("some fancy message");
    13        s.style();
    14        System.out.println(s); // invoke toString() method
    15
    16        s = new SuperFancy("some fancier message?");
    17        s.style();
    18        System.out.println(s); // invoke toString() method
    19    } // main
    20
    21} // Driver
    
     1package cs1302.interfaces.contract;
     2
     3/**
     4 * Represents the interface for an object that can be styled and unstyled.
     5 */
     6public interface Styleable {
     7
     8    /**
     9     * Styles the object.
    10     */
    11    public void style();
    12
    13    /**
    14     * Unstyles the object.
    15     */
    16    public void unstyle();
    17
    18} // Styleable
    
     1package cs1302.interfaces.impl;
     2
     3import cs1302.interfaces.contract.Styleable;
     4
     5public class Fancy implements Styleable {
     6
     7    private String message;
     8    private boolean styled;
     9
    10    public Fancy(String msg) {
    11        message = msg;
    12        styled = false;
    13    } // Fancy
    14
    15    @Override
    16    public void style() {
    17        styled = true;
    18    } // style
    19
    20    @Override
    21    public void unstyle() {
    22        styled = false;
    23    } // unstyle
    24
    25    public String toString() {
    26        String content;
    27        if (styled) {
    28            content = "*** " + message + " ***";
    29        } else {
    30            content = message;
    31        } // if
    32        return String.format("Fancy(%s)", content);
    33    } // toString
    34
    35} // Fancy
    
     1package cs1302.interfaces.impl;
     2
     3import cs1302.interfaces.contract.Styleable;
     4
     5public class SuperFancy implements Styleable {
     6
     7    private String message;
     8    private boolean styled;
     9
    10    public SuperFancy(String msg) {
    11        message = msg;
    12        styled = false;
    13    } // SuperFancy
    14
    15    @Override
    16    public void style() {
    17        styled = true;
    18    } // style
    19
    20    @Override
    21    public void unstyle() {
    22        styled = false;
    23    } // unstyle
    24
    25    public String getAbout() {
    26        return "A styled SuperFancy object contains alternating characters.";
    27    } // getAbout
    28
    29    public String toString() {
    30        String content = "";
    31        if (styled) {
    32            for (int i = 0; i < message.length(); i++) {
    33                if (i % 2 == 0) {
    34                    content += Character.toUpperCase(message.charAt(i));
    35                } else {
    36                    content += Character.toLowerCase(message.charAt(i));
    37                } // if
    38            } // for
    39            content = "*** " + content + " ***";
    40        } else {
    41            content = message;
    42        } // if
    43        return String.format("Super Fancy(%s)", content);
    44    } // toString
    45
    46} // SuperFancy
    
    Code visualization diagram for java/interfaces/using-an-interface:164 (end of main)

    Calling the style method on different object types with a single variable [code listing]

    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 the Fancy class version of the method, because that's the type of object being referred to. When s.style() is called the second time, it invokes the SuperFancy version of the method for a similar reason. The same thing happens with the implicit call to toString() when printing the objects.

  5. 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 in cs1302.interfaces.StyleDriver:

     1package cs1302.interfaces;
     2
     3import cs1302.interfaces.contract.Styleable;
     4import cs1302.interfaces.impl.Fancy;
     5import cs1302.interfaces.impl.SuperFancy;
     6
     7public class StyleDriver {
     8
     9    public static void test(String testName, Styleable s) {
    10        System.out.printf("# %s Test\n", testName);
    11        System.out.println(s);
    12        s.style();
    13        System.out.println(s);
    14        s.unstyle();
    15        System.out.println(s);
    16    } // test
    17
    18    public static void main(String[] args) {
    19        Styleable message;
    20
    21        message = new Fancy("Hello, world...");
    22        test("Fancy", message);
    23
    24        message = new SuperFancy("Hello, world...");
    25        test("Super Fancy", message);
    26    } // main
    27
    28} // StyleDriver
    
     1package cs1302.interfaces.contract;
     2
     3/**
     4 * Represents the interface for an object that can be styled and unstyled.
     5 */
     6public interface Styleable {
     7
     8    /**
     9     * Styles the object.
    10     */
    11    public void style();
    12
    13    /**
    14     * Unstyles the object.
    15     */
    16    public void unstyle();
    17
    18} // Styleable
    
     1package cs1302.interfaces.impl;
     2
     3import cs1302.interfaces.contract.Styleable;
     4
     5public class Fancy implements Styleable {
     6
     7    private String message;
     8    private boolean styled;
     9
    10    public Fancy(String msg) {
    11        message = msg;
    12        styled = false;
    13    } // Fancy
    14
    15    @Override
    16    public void style() {
    17        styled = true;
    18    } // style
    19
    20    @Override
    21    public void unstyle() {
    22        styled = false;
    23    } // unstyle
    24
    25    public String toString() {
    26        String content;
    27        if (styled) {
    28            content = "*** " + message + " ***";
    29        } else {
    30            content = message;
    31        } // if
    32        return String.format("Fancy(%s)", content);
    33    } // toString
    34
    35} // Fancy
    
     1package cs1302.interfaces.impl;
     2
     3import cs1302.interfaces.contract.Styleable;
     4
     5public class SuperFancy implements Styleable {
     6
     7    private String message;
     8    private boolean styled;
     9
    10    public SuperFancy(String msg) {
    11        message = msg;
    12        styled = false;
    13    } // SuperFancy
    14
    15    @Override
    16    public void style() {
    17        styled = true;
    18    } // style
    19
    20    @Override
    21    public void unstyle() {
    22        styled = false;
    23    } // unstyle
    24
    25    public String getAbout() {
    26        return "A styled SuperFancy object contains alternating characters.";
    27    } // getAbout
    28
    29    public String toString() {
    30        String content = "";
    31        if (styled) {
    32            for (int i = 0; i < message.length(); i++) {
    33                if (i % 2 == 0) {
    34                    content += Character.toUpperCase(message.charAt(i));
    35                } else {
    36                    content += Character.toLowerCase(message.charAt(i));
    37                } // if
    38            } // for
    39            content = "*** " + content + " ***";
    40        } else {
    41            content = message;
    42        } // if
    43        return String.format("Super Fancy(%s)", content);
    44    } // toString
    45
    46} // SuperFancy
    
    Code visualization diagram for java/interfaces/using-an-interface:209 (end of main)

    Polymorphic Method Execution in StyleDriver.java [code listing]

    Here, we create a single method that works for objects of any implementing class. Take a look at the main method in StyleDriver and notice how we are able to call the test method by passing in a reference to a Fancy object or a SuperFancy 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:

    1. src/cs1302/interfaces/contract/Styleable.java

    2. src/cs1302/interfaces/impl/Fancy.java

    3. src/cs1302/interfaces/impl/SuperFancy.java

    4. 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)

5.6.1. Polymorphism with Arrays of Interface References

Because an interface defines a reference type, you can also create arrays of interface references. An array whose component type is an interface can store references to instances of any class that implements that interface. This allows programs to manage heterogeneous collections of objects under a single unified type.

Consider creating a Styleable[] array that stores both Fancy and SuperFancy objects. An enhanced for-loop can iterate through each element and invoke interface methods without needing to know the concrete class of each object:

 1package cs1302.interfaces;
 2
 3import cs1302.interfaces.contract.Styleable;
 4import cs1302.interfaces.impl.Fancy;
 5import cs1302.interfaces.impl.SuperFancy;
 6
 7public class Driver {
 8
 9    public static void main(String[] args) {
10        Styleable[] items = new Styleable[] {
11            new Fancy("First"),
12            new SuperFancy("Second")
13        };
14
15        for (Styleable item : items) {
16            item.style();
17            System.out.println(item);
18        } // for
19    } // main
20
21} // Driver
 1package cs1302.interfaces.contract;
 2
 3/**
 4 * Represents the interface for an object that can be styled and unstyled.
 5 */
 6public interface Styleable {
 7
 8    /**
 9     * Styles the object.
10     */
11    public void style();
12
13    /**
14     * Unstyles the object.
15     */
16    public void unstyle();
17
18} // Styleable
 1package cs1302.interfaces.impl;
 2
 3import cs1302.interfaces.contract.Styleable;
 4
 5public class Fancy implements Styleable {
 6
 7    private String message;
 8    private boolean styled;
 9
10    public Fancy(String msg) {
11        message = msg;
12        styled = false;
13    } // Fancy
14
15    @Override
16    public void style() {
17        styled = true;
18    } // style
19
20    @Override
21    public void unstyle() {
22        styled = false;
23    } // unstyle
24
25    public String toString() {
26        String content;
27        if (styled) {
28            content = "*** " + message + " ***";
29        } else {
30            content = message;
31        } // if
32        return String.format("Fancy(%s)", content);
33    } // toString
34
35} // Fancy
 1package cs1302.interfaces.impl;
 2
 3import cs1302.interfaces.contract.Styleable;
 4
 5public class SuperFancy implements Styleable {
 6
 7    private String message;
 8    private boolean styled;
 9
10    public SuperFancy(String msg) {
11        message = msg;
12        styled = false;
13    } // SuperFancy
14
15    @Override
16    public void style() {
17        styled = true;
18    } // style
19
20    @Override
21    public void unstyle() {
22        styled = false;
23    } // unstyle
24
25    public String getAbout() {
26        return "A styled SuperFancy object contains alternating characters.";
27    } // getAbout
28
29    public String toString() {
30        String content = "";
31        if (styled) {
32            for (int i = 0; i < message.length(); i++) {
33                if (i % 2 == 0) {
34                    content += Character.toUpperCase(message.charAt(i));
35                } else {
36                    content += Character.toLowerCase(message.charAt(i));
37                } // if
38            } // for
39            content = "*** " + content + " ***";
40        } else {
41            content = message;
42        } // if
43        return String.format("Super Fancy(%s)", content);
44    } // toString
45
46} // SuperFancy
Code visualization diagram for java/interfaces/using-an-interface:301 (end of main)

Iterating through an array of mixed Styleable objects [code listing]

Rapid Fire Review
  1. What does the interface/implementing class relationship provide in Java?

    1. The ability to write more complex code.

    2. Type compatibility between the interface type and the implementing class type.

    3. A way to create objects from interfaces.

    4. A method to declare variables without assigning them a type.

  2. What can a variable of an interface type refer to in Java?

    1. Any object of a class that implements the interface.

    2. Any object, regardless of class.

    3. Only objects of the same interface type.

    4. An instance of the interface itself.

  3. What methods can be called on an object using a reference variable with an interface type?

    1. All methods declared in the class of the object.

    2. Only the methods declared in the interface.

    3. Methods declared in both the interface and the class.

    4. Only the methods overridden from the superclass.

  4. Which of the following code snippets will compile?

    1. Styleable s = new Styleable();

    2. Styleable s = new Fancy("some message");

    3. Fancy f = new Styleable();

    4. SuperFancy sf = new Styleable();

    5. SuperFancy f = new Fancy("some message");

  5. Why might using an interface lead to more elegant code?

    1. It allows the creation of objects from the interface.

    2. It requires writing more code for each class.

    3. It enables writing code that works with all classes that implement the interface.

    4. 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:

  1. An interface named Movable containing the following entities:

    Methods: void move() void stop()

  2. A class named Car that implements Movable containing the following entities:

    Instance Variables: String made;

    Methods: void move() void stop() String getMake() void setMake(String make)

  3. A class named Dog that implements Movable 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?

  1. Car myCar = new Car();
    myCar.setMake("Tesla");
    
    Movable movableVehicle = new Car();
    Movable movableVehicle2 = myCar;
    
    movableVehicle.move();
    movableVehicle.stop();
    
  2. Car myCar = new Car();
    myCar.setMake("Tesla");
    
    Movable movableVehicle2 = myCar;
    movableVehicle2.getMake();
    
  3. Dog fido = new Dog();
    fido.setName("fido");
    
    Movable dog = fido;
    System.out.println(dog.getName());
    
Test Yourself Solution (Check after answering the question above)
  1. This will compile. We are referencing objects of type Car with Movable references. Then, we call methods move and stop which are available in the interface.

  2. This will not compile. We attempt to call the getMake method on a variable of type Movable. However, the getMake method is not available in the interface.

  3. This will not compile. We attempt to call the getName method on a variable of type Movable. However, the getName method is not available in the interface.