You will build a small application to demonstrate the usage and usefulness of the interface in Java
The first distinction we need to do is between an Interface in Java and a Graphical Interface. Often language shortcut could be tricky, as we will say only Interface, when we meant Graphical Interface, which could lead to some misunderstanding.
In this codelabs, Interface mean a Java Interface.
An interface will define and standardise the ways in which things can interact with each other. In the real world, an interface could be a steering wheel, which is an interface between the wheels turning and the person that decide to turn. The driver has limited control that if they turn the steering wheel in a certain direction, it will turn the wheels in the same direction. The interface defines the what, however, how the car company is implementing (the how) could change from one company to another one. How is the wheels durning in the good direction is not relevant to the driver, all that matters is that the car is turning.
In other words, an interface will force a class that will implements it, this is what you should look like. Classes that implement an interface shall implement all the methods declared in the interface and could have other method(s) specific to their own classes.
Every classes that implement an interface will be able to invoke the method that have been declared in an interface as the class will need to implement the method, however what the method is doing (the body) is specific to the class that implements the method.
To implement an interface, we will use the keyword interface. First, we declare the access modifier of the class and followed by the keyword interface, followed by the name of the class.
We will implement a small interface with only one method in it. This method will return a String and take a String as parameter. Nothing else is defined in this interface.
public interface ToTaste {
public String toTasteWith(String toTasteWith);
}
If no modifier is included, the default access modifier will be used making the interface only available to other members of the package in which it is declared. When declared as public the interface can be used outside of the package.
Notice that the methods that are declared have no bodies. They end with a semicolon after the parameter list. They are, essentially, abstract methods. Each class that includes such an interface must implement all of the methods.
To use an interface in another class, the keyword is implements. In the following class Tea, the ToTaste interface defined the previous step is implemented. The method defined in the interface ToTast must be defined in the class Tea.
In the previous step, the method was only declared, no behaviour was defined. In the class that implements an interface, the method will define the behaviour of the method, and this behaviour could be different for every classes that will implement the interface.
For example, in the following example, the method defined that if the String passed as an argument is equals to cheese it will return Yummy. If it is cookies, will return Nice and if something else is passed, will return the String doesn't go with Tea.
public class Tea implements ToTaste{
private String nameTea;
public Tea (String nameTea) {
this.nameTea = nameTea;
}
public String getNameTea () {
return nameTea;
}
@Override
public String toTasteWith (String toTasteWith) {
if (toTasteWith.toLowerCase().equals("cheese")) {
return "Yummy";
} else if (toTasteWith.toLowerCase().equals("cookies")) {
return "Nice";
} else {
return "Doesn't go with tea";
}
}
Now, using the same interface ToTaste in the class Coffee. It is both permissible and common for classes that implement interfaces to define additional members of their own. For example, in Coffee, we can see a method that will return the name of the coffee. We could of course have other methods that getter and setter to this class.
public class Coffee implements ToTaste{
private String coffeeName;
public Coffee (String coffeeName) {
this.coffeeName = coffeeName;
}
public String getCoffeeName () {
return coffeeName;
}
@Override
public String toTasteWith (String toTasteWith) {
if (toTasteWith.toLowerCase().equals("cake")) {
return "This is a must have";
} else if (toTasteWith.toLowerCase().equals("meat")) {
return "Really, did you already tried it!!!!!";
} else {
return "Doesn't go with Coffee";
}
}
}
We can observe that the behaviour is not the same, and the method will not return the same String depending of the String passed as an argument.
An abstract class could implement an interface, and in this case, the class doesn't need to implement the method coming from the interface.
An interface is not a class, you cannot use the "new" operator to instantiate an interface. However, you can declare a type of variable of a type interface, and the interface variable must refer to an object of a class that are implementing the interface.
It is possible to declare variable as object reference that use a interface rather than a class type. When you call a method through one of these references, the correct version will be called based on the actual instance of the interface being referred to, the binding is done dynamically at run time.
public class TestInterface {
public static void main (String[] args) {
Coffee arabica = new Coffee("arabicca");
Tea english = new Tea("English breakfaste");
List<ToTaste> drinks = new ArrayList<>();
//ToTaste t = new ToTaste();
drinks.add(arabica);
drinks.add(english);
for (ToTaste drink:drinks){
System.out.println("drink " +drink.toString() +" with Cheese: "+drink.toTasteWith("cheese"));
}
}
}
In the previous example, we are declaring one instance of the object Tea, and one instance of the object Coffee. Both are implementing the interface ToTaste. By consequence, we can declare a data-structure (an ArrayList in this example) of type ToTaste. However, if you are uncommenting the line which follow, this will not work, as you cannot use the operator to instantiate an interface.
We have now an ArrayList that could contains any type of object of type ToTaste. We can add the instance of Coffee and the instance of Tea in this arrayList. For the type in the loop, it is necessary to declare the type of the iterator of type ToTaste, to invoke the method toTasteWith. Notice that for the first line displayed, it will display that it doesn't go with Coffee, and for the next line, it will display Yummy (Tea).
drink arabicca with Cheese: Doesn't go with Coffee
drink English breakfaste with Cheese: Yummy
All variables are implicitly final and static and they must be initialised. All methods and variables are implicitly public.
The variables declared into an interface will appear in any class that implements the interface.
A small example to declare a variable in an interface. For this example, we will implement an interface that have variables that represent some constants. These variables are public, static and final by default.
A small method have been declared too which is prepareIngredients().
public interface Prepare {
String WHAT_IS_TEA="leaves";
String WHAT_IS_COFFEE ="beans";
String prepareIngredients();
}
To use this interface, we can modify a little bit the Tea and Coffee example. To also demonstrate how to implement an abstract method that use an interface, an abstract class Tea have been implemented.
public abstract class Tea implements Prepare {
private String nameTea;
public Tea (String nameTea) {
this.nameTea = nameTea;
}
public String getNameTea () {
return nameTea;
}
}
And some concrete classes of Tea, both are inheriting from Tea that implements Prepare, by consequence it is at the level of these concrete classes that we implement the method prepareIngredients, that will have different behaviour for each type of Tea. And to use the variable from the Interface, we are simply invoking these constants.
public class TeaMilk extends Tea{
private String milk = "milk";
public TeaMilk (String nameTea) {
super(nameTea);
}
@Override
public String prepareIngredients () {
return WHAT_IS_TEA+" water "+ milk;
}
}
public class GreenTea extends Tea{
public GreenTea (String nameTea) {
super(nameTea);
}
@Override
public String prepareIngredients () {
return WHAT_IS_TEA+ " water ";
}
}
To test the interface, abstract method and concrete methods:
public class TestInterface {
public static void main (String[] args) {
GreenTea gt = new GreenTea("Matcha");
GreenTea greenOfLondon = new GreenTea("Green of london");
TeaMilk englishB = new TeaMilk("English");
List<Prepare> prepareList = new ArrayList<>(List.of(gt,greenOfLondon,englishB));
for (Prepare p : prepareList){
System.out.println(p.prepareIngredients());
}
}
}
Since Java 8, an interface can define what and how, as it is possible to add a "default" implementation. This default implementation will allows the developper to define some behaviour to an interface.
Even if an interface can now have default methods, an interface still cannot maintain a state information, and cannot have instance variable, and it is still not possible to create an instance of an interface. The default method have been introduce for several reasons, one of these is to be able to expend an interface withtout breaking the code. In the past, if a new method was added in an interface that was already widely used by other classes, all these classes should add the new implementation of this method. With the default method, this can be avoid, as this default method will be used if no other implementation is explicitly provided.
The second reason to add the default method is that in the old time when we were using an interface that had several methods declared, and the class that was implementing this interface didn't needed all the methods declared in the interface, we were implementing methods with empty body. It was happening quite a lot for all the Swing and AWT libraries of Java that is using a certain number of interface.
To declare a default method:
public interface Prepare {
default String prepareADrink(){
return "default preparation";
}
int boilAtThisTemperature ();
}
The interface Prepare this time implements the default method prepareADrink that will not need to be re-define in all the classes that do implement prepare and the method boilWaterTemperature that will need to be implement in every classes that implement this interface.
public class Tea implements Prepare{
@Override
public int boilAtThisTemperature () {
return 100;
}
}
In the class Tea, we only implement the method boilWaterTemperature, the default method will be used.
public class Coffee implements Prepare{
@Override
public String prepareVictuals () {
return "To prepare a Coffee you need coffee";
}
@Override
public int boilAtThisTemperature () {
return 80;
}
}
In the class Coffee we implement both, the method boilWaterTemperature and the default method prepareADrink.
As usual to test these different classes and interface.
public class TestInterface {
public static void main (String[] args) {
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println(String.format("Tea %s temperature %d", tea.prepareVictuals(),tea.boilAtThisTemperature()));
System.out.println(String.format("Coffee %s temperature %d", coffee.prepareVictuals(),coffee.boilAtThisTemperature()));
}
}
Since Java 9, Interface can add private method. These methods can be invoke only inside the interface and by the default methods.
Some thoughts about the difference between Abstract classes and Interface:
You can find the code for this example in https://git.cardiff.ac.uk/ASE_GROUP_2020/code_for_codelabs.git