This tutorial introduces the reader to Java interfaces and polymorphism.
This tutorial assumes that the reader has a knowledge of basic Unix commands and experience working with a command-line text editor (e.g. emacs, vi, etc.). The reader should be familiar with compiling and running Java code contained in packages and working with the Javadoc tool.
To get the most out of this tutorial, you should follow along and take notes.
- LO2.e: (Partial) Utilize existing generic methods, interfaces, and classes in a software solution.
- LO3.c: Generate user-facing API documentation for a software solution.
- LO4.b: Utilize interface-based polymorphism in a software solution.
The steps in this tutorial assume that you are logged into the Odin server.
-
Use the following command to download and execute a shell script that retrieves the starter code for this tutorial and places it into a subdirectory called
cs1302-interfaces
:$ curl -s -L https://git.io/fhgce | bash
-
Change into the
cs1302-interfaces
directory that was just created and look around. There should be multiple Java files contained in thesrc
directory. To see a listing of all of the files insrc
, use thefind
command as follows:$ find src
Execute the
cat
command on each.java
file that was listed to view the contents. You will notice that some of them contain interfaces instead of classes. They can be identified by looking for theinterface
keyword instead of theclass
keyword in the overall type declaration specified near the top of the file. We say "type" declaration because a.java
file can be used to declare any kind of reference type in Java, including classes, interfaces, and class-based enumerations. 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 Variable Refresher from last week. You are encouraged to ask questions about any parts that you find confusing.
In its simplest form, a Java interface is a reference type composed of abstract methods and
constants. An abstract method is a non-static method without an implementation (body). Think of
creating a class, adding the method signatures, but not putting any code in the methods. This will seem
strange at first but the benefit should become clear as you work through the example. Constants
are variables (static or not) that are declared using the final
keyword. As of Java 8, the
technical definition for an interface allows it to contain only the following:
abstract methods, constants, private methods, static methods, nested types, and default implementation
methods [2]. In 1302, our interfaces
will mostly contain only abstract methods and constants. Nested types and default methods will not
be covered in this tutorial.
Interfaces are used to specify that a type can do a set of things specified by its abstract methods and constants. An interface serves as a contract for the classes that claim to implement the interface. Multiple classes can implement the same interface, each providing its own implementation of the contracted functionality. For this reason, the documentation for an interface must describe what a method does and not necessarily how it should do it. Such documentation is usually written using Javadoc comments in the interface.
Remember, we said an interface is a contract for the classes that claim to implement it. Take a moment to think about how contracts are used in the real world (forget Java for a minute).
Hopefully, you came up with a definition of the word contract and maybe a few situations where contracts are used.
In the real world, we think of a contract as a formal and binding agreement between two or more parties. Let's use the professional athlete as an example. Athletes sign a contract which is a binding agreement between the athlete and the team/organization that ensures the athlete will compete in his/her sport. The contract states that the athlete must "compete" but it doesn't say specifically how they will compete. The details of exactly how an athlete should compete are usually not mentioned in the contract. The contract simply binds the athlete to compete. The specific details and decisions that occur while the competition is ongoing is up to the athlete. Once the contract is written, it could be signed by athletes in a wide variety of sports. The signer could be a track athlete, a baseball player, a football player, a racecar driver, etc. Again, the signer determines the details of how they will compete. The contract only binds them to the action of competing.
Now, let's tie this back to programming. In the example above, compete
is the abstract method
that would be placed in the Athlete
interface (contract) since all athletes must be able to compete.
The abstract method represents the action that is required of the signer of the contract. In other words,
the method is what the signer is obligated to do when they agree to implement the interface (a.k.a.
sign the contract). The implementation details of the compete
method are not given in the interface
itself but instead, they are written in the implementing class (signer). Again, the implementing class
(contract signer) can be any type of athlete as they are all required to compete.
This may all still seem a bit strange and why we do this in programming may not yet be clear. Hang in there! Let's work through an example in Java. Try to keep this terminology in mind as you work through this tutorial.
-
Interfaces, just like classes, have a fully qualified name. Their source code should be positioned and named within your project the same as with classes. That is, an interface called
cs1302.interfaces.contract.Styleable
has an implied position within the package directories of your source code (cs1302/interfaces/contract
) and should be placed in aStyleable.java
file within the implied directory. The first big syntax difference between a class and an interface is illustrated inStyleable.java
:public interface Styleable {
Note the use of the
interface
keyword instead ofclass
in the type header. In this example,Styleable
is the contract that can be signed by other classes. -
The second big syntax difference involves the inclusion of abstract methods, illustrated by the
style()
method inStyleable.java
.public void style();
Notice that the
style()
method does not contain an implementation. The signature of the method ends with a semicolon. An abstract method must not have an implementation. The following is NOT an abstract method:public void style() { }
While the
{ }
may not do anything, it is, in fact, an implementation that does nothing. Compare that to the actual abstract method signature presented above that ends with a semicolon, thus lacking an implementation.If you open the
Styleable.java
file, you will see that it also contains an abstractunstyle
method. Remember, that the abstract method(s) represent what the signer of the contract must be able to do. If a class implements theStyleable
interface, it is obligated to have a concrete (non-abstract)style
and a concreteunstyle
method. If an implementing class does not have implementations for one or both of these methods, it will not compile.NOTE: In Java, the declaration of an abstract method in the source code for an interface may omit the
public
visibility modifier. Ifpublic
is omitted in this context, the abstract method is still assumed to havepublic
visibility. This behavior is different for classes, a topic that will be covered more in-depth at a later time when the nuances of visibility are presented. -
Generate, host, and view the API documentation website for the starter code provided with this tutorial. Find the
Styleable
interface on the website and compare the documentation provided there with what you see in the Javadoc comments included inStyleable.java
. If you don't remember how to do this, then please refer back to your notes for the Javadoc and API Documentation tutorial.
Remember, the Styleable
interface is just the contract. We also need to have at least one class
that implements (signs) the contract. We will use two different implementing classes to demonstrate
that the implementation specifics are up to the implementing class (not the interface) just as the
specific details regarding how an athlete will compete are up to the athlete (not the contract).
-
In Java, a class can implement an interface using the
implements
keyword, e.g., as seen inFancy.java
:public class Fancy implements Styleable {
In this example,
Fancy
is the implementing class that has signed theStyleable
contract. It signs the contract as soon as you addimplements Styleable
to the class declaration as seen above. Again,Fancy
will not compile unless it has a concrete (non-abstract) implementation of both thestyle
andunstyle
methods.Note: If the interface is not in the same package as the implementing class, then you will need to add an
import
statement or use the fully qualified name of the interface. If more than one interface is being implemented, then they can be written in a comma-separated list following theimplements
keyword. -
As we mentioned before, when a class properly implements an interface in Java, it is required to provide implementations for each of the abstract methods defined in the interface. If you inspect the source code for the
cs1302.interfaces.impl.Fancy
class, you will see the abstract methods fromStyleable
implemented. Notice that the implementations contain method bodies (instead of their signatures ending with a semicolon). A specific example can be seen with theunstyle()
method inFancy.java
:public void unstyle() {
Again, this differs from the abstract method defined in the interface only in so far as it has an implementation. The other aspects of the method signature (return type, visibility, name) are the same.
NOTE: As mentioned earlier, it is syntactically correct for an abstract method in an interface to omit the
public
keyword. Remember, in this context, the method is still assumed to have public visibility. Therefore, the implementation of such a method in an implementing class will need to include thepublic
visibility modifier. -
Compare the Javadoc comments in the source code for the
Styleable
interface with the comments written in the source code for the implementingFancy
class. In some cases, new comments are provided. In others, it appears as though Javadoc comments are omitted. In the latter case, this is actually not true. View the API documentation website for both theStyleable
interface and theFancy
class — bring them up side-by-side, if possible. All of the methods inFancy
are documented, evenstyle()
andunstyle()
which have no Javadoc comments in the source code!This happens because the Javadoc tool has the ability to inherit comments from an interface when omitted or when explicitly requested in the implementing class's Javadoc comment using the
{@inheritDoc}
tag. Pay close attention to the difference between these two scenarios, both in the source code and in the generated API documentation website.Note: The
Fancy
class will not passcheckstyle
unless you add a Javadoc comment to the method containing{@inheritDoc}
. You may need to do this if you need a method to passcheckstyle
when it inherits its comments from an interface or parent class (covered later in the course).
In this section, we will motivate why we set up the interface/implementing class relationship. The main benefit is type compatibility between the interface type and the implementing class type(s). Leveraging this compatibility will lead to more elegant code that works with objects of any class that implements the interface. Connecting this to our real-world example, 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 code 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;
-
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");
-
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.java
class and therefore is part of aSuperFancy
object, it would not be available via anStyleable
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
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 theObject
class in a future tutorial or reading, but it includes methods likeequals
andtoString
. -
You might be wondering why the previous example is useful. If so, that's okay. In general, you should try to be specific with the types you use for variables. 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. -
You may have noticed some repetition in the previous example. Without loss of generality, the two parts of the code snippet that seem to repeat can be refactored into a method. Something similar to this is done in the
main
andtest
methods of thecs1302.interfaces.StyleDriver
class. You should inspect both methods in the source code closely. What should they do? What do they do?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 Part 1 and Java Packages Tutorial Part 2.
Below is a video that demonstrates how to use interfaces to write code involving disparate classes with common functionality that is clean, easier to read, and easier to modify. Here, we use the term disparate to describe classes that are dissimilar in what they describe but still, perhaps, share some common functionality.
https://www.youtube.com/watch?v=kcBV6tlg44I
The source code for the video can be accessed here.
Feedback? Please help us make this better! If you have any feedback or suggestions for this reading or tutorial, then use the link below to reach the feedback form.
Copyright © Michael E. Cotterell, Brad Barnes, and the University of Georgia. This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License to students and the public. The content and opinions expressed on this Web page do not necessarily reflect the views of nor are they endorsed by the University of Georgia or the University System of Georgia. Some of the code examples in this tutorial are inspired by code included with Lewis, DePasquale, and Chase. Java Foundations. Fourth Edition.