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:

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

  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:

    This will compile! The variable type and the object type don’t match but they are compatible.
    Styleable s = new Fancy("some message");
    
    This code will not compile since you can’t instantiate interfaces.
    Styleable s = new Styleable();
    
  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:

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

    Calling the style method on different object types with a single variable.
    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 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 exact 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:

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