Tuesday, June 15, 2021

Java Tutorial: Interface

Chapters

Java Interface

Note: Understanding of inheritance is required to understand this topic.
If a class is the blueprint of an object then interface is the blueprint of a class. Some folks call interface as "meta-class". Like standard class, interface can use the this and super keyword. Interface is a special type of class in java. Interface, like abstract class, provides abstraction. Let's create an example to demonstrate interface.
public class SampleClass{

  public static void main(String[]args){
  
    MyTV myTV = new MyTV();
    myTV.power();
    myTV.adjustLight(10);
  }
}

//interface
interface Controls{
  //interface variables, they're
  //default public and final
  //public and final keywords
  //can be omitted in interface
  //variables
  int LIGHT_LVL = 100;
  boolean POWER_STATE = true;
  
  //interface methods, they're
  //default public and abstract
  //the abstract and public
  //keywords can be omitted in
  //interface methods
  void adjustLight(int level);
  void power();
}

//use the implements keyword to implement
//an interface to a class
class MyTV implements Controls{
  private int lightLvl;
  private boolean isOff;
  
  MyTV(){
    lightLvl = LIGHT_LVL;
    isOff = POWER_STATE;
  }
  
  @Override
  public void adjustLight(int level){
  
    if(isOff){
      System.out.println("Can't adjust brightness because "+
                         "TV is off...");
      return;
    }
  
    System.out.println("Current brightness: "
                       + lightLvl + "%");
    System.out.println("Adjusting brightness by: "
                       + level + "%");
    lightLvl -= level;
    if(lightLvl < 0)
       lightLvl = 0;
    
    System.out.println("Current brightness: "
                       + lightLvl + "%");
  }
  
  @Override
  public void power(){
    if(isOff){
      isOff = false;
      System.out.println("power is on...");
    }
    else{
      isOff = true;
      System.out.println("power is off...");
    }
      
  }
}
Variables that are declared in an interface are implicitly public,static and final; Methods that are declared in an interface are implicitly public and abstract. public,static and final can be omitted in interface variables; abstract and public can be omitted in interface methods.

Once a class implements an interface with abstract methods, that class needs to define those abstract methods. Don't forget to follow the rules of method overriding when overriding an abstract method.

Differences Between Abstract Class and Interface

Abstract class and interface are used to achieve abstraction. Though, these two have differences:

1.) Abstract class fields can be final or non-final at the discretion of programmers whereas interface fields are final by default.

2.) Abstract class fields can be static or non-static at the discretion of programmers whereas interface fields are static by default.

3.) Abstract class fields can have different access modifier whereas interface fields are public by default.

4.) abstract keyword can be omitted in abstract methods in interface whereas abstract methods in abstract class need to explicitly include the abstract keyword.

5.) Abstract class has constructor whereas interface doesn't.

6.) Interface can have default methods whereas abstract class can't.

When To Use Interface And Abstract Class

1.) If you want to achieve full abstraction where all methods are abstract or close to full abstraction then, it's preferrable to use interface due to its properties. If you want to achieve partial abstraction then, it's preferrable to use abstract class.

2.) You may wanna use interface if you're building a complex abstraction. We can take advatange of java's multiple inheritance support to interfaces to create a complex type of abstraction.

3.) If you want your fields to have different access modifiers or you want to mix final and non-final fields then, use abstract class.

4.) Use interface if you want an implementation that can be used by any class in your project.

Inheritance Between Interfaces

We can use the extends keyword to subclass an interface by another interface. However, unlike in classes, a parent interface can subclass multiple interfaces by using a comma-separated list.
public class SampleClass{

  public static void main(String[]args){
  }
}

interface SystemMenuDefaults{
  String SCAN_INTERLACE = "Interlace";
  String SCAN_PROGRESSIVE = "Progressive";
}

interface ScreenControls{

  void adjustResolution();
  void scanMode(String scanType);
}

//an interface extending one interface
interface PeripheralControls extends ScreenControls{

  void moveUpAlternative();
  void moveDownAlternative();
}

//an interface extending two interfaces
interface SystemMenu extends SystemMenuDefaults,PeripheralControls{

  void screenRelatedMenu(int option);
}

interface Controls extends SystemMenu{
  int LIGHT_LVL = 100;
  boolean POWER_STATE = true;
  
  void adjustLight(int level);
  void power();
}

//The implementing class must implement
//the abstract methods of parent interface
//and its sub interfaces
class MyTV implements Controls{
  
  @Override
  public void adjustLight(int level){}
  @Override
  public void power(){}
  @Override
  public void screenRelatedMenu(int option){}
  @Override
  public void moveUpAlternative(){}
  @Override
  public void moveDownAlternative(){}
  @Override
  public void adjustResolution(){}
  @Override
  public void scanMode(String scanType){}
  
}
In the above example, we see that an interface can extend multiple interfaces. If a class implements a sub interface with parent interfaces then the class must define the abstract methods of the sub interface and its parent interfaces.

Inheritance Between Class And Interface

If we want a class to inherit an interface we use the implements keyword. Java supports multiple inheritance in interface. Thus, a class can inherit multiple interfaces using the implements keyword.
public class SampleClass{

  public static void main(String[]args){
  
    MyTV myTV = new MyTV();
    myTV.power();
    myTV.adjustLight(10);
  }
}

interface ControlsDefaults{
  int LIGHT_LVL = 100;
  boolean POWER_STATE = true;
}
  
interface Controls{
  
  void adjustLight(int level);
  void power();
}

//class implementing multiple interfaces using
//comma-separated list
class MyTV implements ControlsDefaults,Controls{
  private int lightLvl;
  private boolean isOff;
  
  MyTV(){
    lightLvl = LIGHT_LVL;
    isOff = POWER_STATE;
  }
  
  @Override
  public void adjustLight(int level){
  
    if(isOff){
      System.out.println("Can't adjust brightness because "+
                         "TV is off...");
      return;
    }
  
    System.out.println("Current brightness: "
                       + lightLvl + "%");
    System.out.println("Adjusting brightness by: "
                       + level + "%");
    lightLvl -= level;
    if(lightLvl < 0)
       lightLvl = 0;
    
    System.out.println("Current brightness: "
                       + lightLvl + "%");
  }
  
  @Override
  public void power(){
    if(isOff){
      isOff = false;
      System.out.println("power is on...");
    }
    else{
      isOff = true;
      System.out.println("power is off...");
    }
      
  }
}
Note: A class can inherilt an interface but an interface can't inherit a class.

To resolve shadowing of variables between interfaces, we can add their interfaces names everytime we access them from the implementing class.
public class SampleClass{

  public static void main(String[]args){
    ClassA classA = new ClassA();
  }
  
}

interface InterfaceA{
  String NAME = "InterfaceA";
}

interface InterfaceB{
  String NAME = "InterfaceB";
}

class ClassA implements InterfaceA,InterfaceB{
  
  ClassA(){
    System.out.println(InterfaceA.NAME);
    System.out.println(InterfaceB.NAME);
  }
}
Ambiguous Inheritance

1.) If a super interface and its sub interface have the same method then, the implementing class only needs to override the identical methods once. This also applies to implementing class with multiple super interfaces with identical methods.
public class SampleClass{

  public static void main(String[]args){
    ClassA classA = new ClassA();
  }
}

interface InterfaceA{

  void message();
}

interface InterfaceB extends InterfaceA{

  void message();
}

class ClassA implements InterfaceB{

  @Override
  public void message(){
    System.out.println("ClassA");
  }
  
  ClassA(){
    message();
  }
}
2.) If the implementing class has super class, super interfaces and their super interfaces and they have identical methods then, the implementing class won't be required to override the methods in the interfaces instead, java will accept the method implementation of the super class, if there's an impelementation in the super class. If all identical methods are abstract then, the implementing class only needs to override those methods once.
public class SampleClass{

  public static void main(String[]args){
    ClassB classB = new ClassB();
  }
}

interface InterfaceA{

  void message();
}

interface InterfaceB extends InterfaceA{

  void message();
}

abstract class ClassA{

  public void message(){
    System.out.println("ClassA");
  }
}

class ClassB extends ClassA implements InterfaceB{
  
  ClassB(){
    message();
  }
}
If we override message() in ClassB then, ClassB only needs to override those message() methods once.
public class SampleClass{

  public static void main(String[]args){
    ClassB classB = new ClassB();
  }
}

interface InterfaceA{

  void message();
}

interface InterfaceB extends InterfaceA{

  void message();
}

abstract class ClassA{

  public void message(){
    System.out.println("ClassA");
  }
}

class ClassB extends ClassA implements InterfaceB{
  
  @Override
  public void message(){
    System.out.println("ClassB");
  }
  
  ClassB(){
    super.message();
    message();
  }
}
3.) If the implementing class has super class, super interfaces and their super interfaces and they have similar methods with same names, same number of parameters but different return type then, A compile-time error will occur in the implementing class because implementing class can't override those methods because overriding those methods in one class is very similar to method overloading.
public class SampleClass{

  public static void main(String[]args){
  }
}

interface InterfaceA{

  void message();
}

abstract class ClassA{
  //comment this method if you're gonna use
  //the fix
  abstract String message();
  
  //1.) Try this method to fix the error
  //abstract String message(String message);
}

class ClassB extends ClassA implements InterfaceA{

  @Override
  public void message(){}
  
  //comment this method definition if you're gonna use
  //the fix
  @Override
  String message(){return "";}
  
  /*2.)
  @Override
  String message(String message){ return message; }
  */
}
The example above will throw a compile-time error. To fix the error, we can add a parameter to one of the message() methods to comply with the method overloading rules.

Tag or Marker Interface

As the name implies, tag or marker interface tags of marks a class. Java has pre-defined marker interfaces like Serializable, EventListener,etc. These interfaces have special meaning to the JVM. For example, if we tag a class as serializable then the instances of that class can be serialize in a file. Take a look at this example:
import java.io.Serializable;

public class SampleClass{

  public static void main(String[]args){
  }
}

//that instances of this class can be serialize
class ClassA implements Serializable{
  String name;
  int num;
}
We can create our own marker interface if we wanna categorize our classes.
public class SampleClass{

  public static void main(String[]args){
  
    Rectangle rect = new Rectangle();
    
    //check if a shape is non-intersecting
    if(rect instanceof NonIntersecting)
      System.out.println("Rectangle has no intersecting "
                         +"line segments");
  }
}

//tag or marker interface
interface NonIntersecting{}

//tag this class as non-intersecting shape
class Rectangle implements NonIntersecting{
}

//tag this class as non-intersecting shape
class Triangle implements NonIntersecting{
}

//Pentagram has intersecting line segments
class Pentagram{
}

Functional Interface

This topic is pretty advance and an understanding of lambda expression is required to understand the use of this interface. Regardless, I'm gonna explain the basic concept of this interface very briefly. A functional interface is an interface with only one abstract method. However, it can have multiple default and static methods. default method is an added feature in JDK8.

This example shows the structure of a functional interface.
//@FunctionalInterface is an annotation
@FunctionalInterface
interface MyFunctionalInterface{

  void commit();
  
  //this method is optional in
  //functional interface
  default void greetings(){
    System.out.println("Hello! This is "+
                       "my functional interface");
  }
}
Java has pre-defined functional interfaces that we can use. Check out the java.util.function package. An understanding of generics is required to understand the syntax of the functional interfaces in that package.

Calling Method Using Interface Reference

We know that interface can't be instantiated. However, just like abstract class, typecasting can still happen between a sub class and a super interface.
public class SampleClass{

  public static void main(String[]args){
  
    Messenger messenger = new Messenger();
    
    //create the interface reference and upcast
    //NotificationSender instance
    Notifications notif = new NotificationSender();
    
    //these methods only need messageNotification()
    //and alertNotification methods. So, using 
    //Notification reference will suffice here and
    //avoid the misuse of displayNotificationCount().
    messenger.sendMessage("Hi There!",notif);
    messenger.sendAlert("You Battery is low!",notif);
    
    //downcast the NotificationSender instance that is
    //referenced in notif variable in order to access
    //the displayNotificationCount() method.
    NotificationSender ns = (NotificationSender)notif;
    ns.displayNotificationCount();
    
  }
}

interface Notifications{

  void messageNotification();
  void alertNotification();
}

class NotificationSender implements Notifications{
  private int msgNotifCount;
  private int alertNotifCount;

  @Override
  public void messageNotification(){
    System.out.println("Message Sent!");
    msgNotifCount++;
  }
  
  @Override
  public void alertNotification(){
    System.out.println("Alert! This matter is "
                       +"somewhat important!");
    alertNotifCount++;
  }
  
  public void displayNotificationCount(){
    
    System.out.println("message notifation count: "
                       +msgNotifCount);
    System.out.println("alert notifation count: "
                       +alertNotifCount);
  }
}

class Messenger{

  void sendMessage(String message,Notifications notif){
    System.out.println(message);
    
    //call messageNotification() using the
    //reference of Notifications interface
    notif.messageNotification();
  }
  
  void sendAlert(String alert,Notifications notif){
    System.out.println(alert);
    
    //call alertNotification() using the
    //reference of Notifications interface
    notif.alertNotification();
  }
}
Static Class In Interface

An interface can't have an inner class due to the fact that it doesn't have contructor. Although, interface can have static nested class as its member. Classes that are defined in interface are implicitly static.
public class SampleClass{

  public static void main(String[]args){
  
    MyTV myTV = new MyTV();
    myTV.adjustLight();
    myTV.adjustVolume();
    
    if(myTV.toggleAltControls())
      myTV.useAltControls(Controls.VOLUME);
    
  }
}

interface Controls{
  String LIGHT = "light";
  String VOLUME = "volume";
  
  
  //class member of interface
  //is static by default
  //static keyword can be omitted
  class PeripheralControls{
    
    void adjustVolumeAlternative(){
      System.out.println("Alternative Volume "+
                         "Controller");
    }
    
    void adjustLightAlternative(){
      System.out.println("Alternative Light "+
                         "Controller");
    }
    
  }
  
  void adjustLight();
  void adjustVolume();
}

class MyTV implements Controls{
  private PeripheralControls perpCon;
  private boolean isActivated;
  
  MyTV(){
    isActivated = false;
    perpCon = new PeripheralControls();
  }
  
  @Override
  public void adjustLight(){
    System.out.println("Main Light "+
                       "Controller");
  }
  
  @Override
  public void adjustVolume(){
    System.out.println("Main Volume "+
                       "Controller");
  }
  
  boolean toggleAltControls(){
    isActivated = !isActivated;
    return isActivated;
  }
  
  void useAltControls(String control){
  
    if(isActivated){
    
      switch(control){
      
        case Controls.LIGHT:
        perpCon.adjustLightAlternative();
        break;
        
        case Controls.VOLUME:
        perpCon.adjustVolumeAlternative();
        break;
        
        default:
        System.out.println("Invalid Input.");
        break;
        
      }
    }
    else System.out.println("Can't use "
                    +"alternative controls "
                    +"because it's not activated");
  }
  
}

Added features In Interface since Java 8

Since Java 8, there are added features that improve java interface. Those improvements really pushed interface boundary making it more flexible and usable. These are the added features:

Default Method

Default method also called defender method or virtual extension method was introduced in java8. Prior to java8, interface can't have defined methods.

However, having no defined methods in interface pose a problem. For example, if an interface has been implemented multiple times in a project and then we want to add another method; We have no choice but to implement that additional method to all implementing classes of that interface.

First of all, that's gonna be tedious and a frustrating work. One of the main purposes of default methods is to provide a simple way of adding new methods without updating the implementing classes. Let's take a look at this example.
public class SampleClass{

  public static void main(String[]args){
  
  Message1 msg1 = new Message1();
  if(msg1.message(null) == false)
    msg1.defaultMessage();
  
  Message2 msg2 = new Message2();
  if(msg2.message(null) == false)
    msg2.defaultMessage();
  
  }
}

interface DefaultMessage{
  
  //default method is not required to be overriden
  //we can override this if it's needed
  default void defaultMessage(){
    System.out.println("Default message");
  }
  
  boolean message(String message);
}

class Message1 implements DefaultMessage{
  
  //Overriding the default method
  @Override
  public void defaultMessage(){
    System.out.println("Default message in Message1");
  }
  
  @Override
  public boolean message(String message){
  
    if(message == null)
      return false;
  
    System.out.println("Message1 message: "
                       +message);
    return true;
  }
}

class Message2 implements DefaultMessage{

  @Override
  public boolean message(String message){
    if(message == null)
      return false;
  
    System.out.println("Message2 message: "
                       +message);
    return true;
  }
}
In the above example, we see that we're the implementing classes are not required to override the default method. Thus, implementing classes are not required to update everytime we add default methods in our interface. Also, default method is public by default.

If we want to access identical default methods between interfaces, we need to override the identical methods first then we can use the super keyword to call for the methods.
public class SampleClass{

  public static void main(String[]args){
  
    ClassA classA = new ClassA();
  }
}

interface InterfaceA{

  default void message(){
    System.out.println("InterfaceA");
  }
}

interface InterfaceB{

  default void message(){
    System.out.println("InterfaceB");
  }
}

class ClassA implements InterfaceA,InterfaceB{
  
  //Override message() first
  @Override
  public void message(){
    //call default methods using the super
    //keyword
    InterfaceA.super.message();
    InterfaceB.super.message();
  }
  
  ClassA(){
    message();
  }
}
If you don't override message() first, you will encounter this error:
"ClassA inherits unrelated defaults..."

Default method can be redeclared as abstract method in another interface.
public class SampleClass{

  public static void main(String[]args){
  
    ClassA classA = new ClassA();
  }
}

interface InterfaceA{

  //default method
  default void message(){
    System.out.println("InterfaceA");
  }
}

interface InterfaceB extends InterfaceA{
  
  //redeclare default method as abstract method
  void message();
}

class ClassA implements InterfaceB{
  
  //redifine message() in implementing class
  @Override
  public void message(){
    System.out.println("ClassA");
  }
  
  ClassA(){
    message();
  }
}
Default method has this "static-non-static" behaviour, if you will. Default method can only access static class/interface members. However, default method can't be called like static method. Take a look at this example:
public class SampleClass{

  public static void main(String[]args){
    
    //error: can't call getName() default method directly
    //System.out.println(ClassA.Interface1.getName());
    
    new ClassB();
    
    //static method can be called directly
    ClassA.Interface1.setName("main");
    
    new ClassB();
  }
}

abstract class ClassA{
  //this variable can be accessed by
  //default method in nested interface
  //because it's static
  private static String name = "ClassA";
  
  //nested interface
  interface Interface1{
  
    default String getName(){
      return name;
    }
    
    static void setName(String name){
      ClassA.name = name;
    }
  }
  
}

//implement Interface1 in ClassA
class ClassB implements ClassA.Interface1{
  
  ClassB(){
    //default method can be called in
    //implementing class
    System.out.println(getName());
  }
}
Private and Static Method

Interface can now have private and static methods. A non-private static method is implicitly public.
public class SampleClass{

  public static void main(String[]args){
    Defaults.defaultMessage();
  }
}

interface Defaults{
  
  //private static method
  private static void defaultFont(){
    System.out.println("Default Font Acquired!");
  }
  
  //implicitly public
  static void defaultMessage(){
    System.out.println("Acquiring Default Font...");
    defaultFont();
    System.out.println("This is a Default Message!");
  }
  
}

Nested Interface

Like classes, interface can also be nested. An interface can be nested in a class or another interface. One of the uses of nested interface is to group interfaces that are related to each other.
Interface In Another Interface

An interface can have interface as its members. A nested interface in another interface is implicilty public.
public class SampleClass{

  public static void main(String[]args){
    String toolToUse = Toolkit.HAMMER;
    
    switch(toolToUse){
    
      case Toolkit.HAMMER:
      Toolkit.ConstructionTools.useHammer();
      break;
      
      case Toolkit.SCREWDRIVER:
      Toolkit.HardwareTools.useScrewDriver();
      break;
    }
    
  }
}

interface Toolkit{
  String HAMMER = "hammer";
  String NAILS = "nails";
  String SCREWDRIVER = "screwdriver";
  String SCREWS = "screws";
  
  //nested interface
  interface ConstructionTools{
  
    static void useHammer(){
      getNails();
      System.out.println("Using " + HAMMER + "...");
      System.out.println("Job's Done!");
    }
    
    private static void getNails(){
      System.out.println("Getting nails...");
      System.out.println(NAILS+" acquired!");
    }
  }
  
  //nested interface
  interface HardwareTools{
    static void useScrewDriver(){
      getScrews();
      System.out.println("Using " + SCREWDRIVER + "...");
      System.out.println("Job's Done!");
    }
    
    private static void getScrews(){
      System.out.println("Getting screws...");
      System.out.println(SCREWS+" acquired!");
    }
  }
}
Here's another example:
public class SampleClass{

  public static void main(String[]args){
    MyConstTools mct = new MyConstTools();
    mct.useHammer();
    mct.useDrill();
    
    MyCustomTools cTools = new MyCustomTools();
    cTools.useTool();
    
  }
}

interface Tools{
  
  interface ConstructionTools{
    
    void useHammer();
    void useDrill();
  }
  
  void useTool();
}

//implement nested interface
class MyConstTools implements Tools.ConstructionTools{

  @Override
  public void useHammer(){
    System.out.println("Using my hammer...");
  }
  
  @Override
  public void useDrill(){
    System.out.println("Using my drill...");
  }
  
}

//implement top-level interface
class MyCustomTools implements Tools{

  @Override
  public void useTool(){
    System.out.println("Using my cutom-made tool");
  }
}
Interface In a Class

An interface can be nested in a class. Unlike interface in another interface, interface in a class can have public, default or protected access modifier.
public class SampleClass{

  public static void main(String[]args){
  
    MyToolBox toolBox = new MyToolBox(ToolBox.
                                      WoodenBox.BOX_NAME,
                                      7);
                        
    if(ToolBox.WoodenBox.checkBox(toolBox))
      System.out.println("This box is a: " +
                         ToolBox.WoodenBox.BOX_NAME);
    else if(ToolBox.MetalBox.checkBox(toolBox))
      System.out.println("This box is a: " +
                         ToolBox.MetalBox.BOX_NAME);
                         
  }
}

abstract class ToolBox{
  private static String name;
  
  ToolBox(String name){
  
    this.name = name;
  }
  
  //nested interface
  interface WoodenBox{
    String BOX_NAME = "Wooden Box";
    
    static boolean checkBox(ToolBox box){
      //interface can only access static
      //members of a class
      if(box.getName() == name)
        return true;
      else return false;
      
    }
  }
  
  //nested interface
  interface MetalBox{
    String BOX_NAME = "Metal Box";
    
    static boolean checkBox(ToolBox box){
      //interface can only access static
      //members of a class
      if(box.name == name)
        return true;
      else
        return false;
    }
  }
  
  String getName(){ return name; }
  
}

class MyToolBox extends ToolBox{
  private int weight;
  
  MyToolBox(String name,int weight){
    super(name);
    this.weight = weight;
  }
}
In the above example, we see that interface can access the name static variable in ToolBox. Remember, nested interface in a class can access static members but not instance members because nested interface behave similarly to nested static class.

No comments:

Post a Comment