Try   HackMD
YAMLException: bad indentation of a sequence entry at line 9, column 17: - [Introduction](#Introduction) ^

title: Introduction of Interface in Spring Boot and Java8
description:

Introduction of Interface in Spring Boot and Java8

Author: TYT
Tags: Reading notes, Java, Spring Boot,Interface

Introduction

What is interface?

  • Interface is a reference type.
  • Contain method signature, default method, and static method.
  • Cannot be instantiated, only be implemented by class and extended by interface.
public interface RoadVehicle {

    String drive(String direction);

    // can be used when class implemented.
    default void turnoff() {
        System.out.println("turn off");
    }

    // can be used without implementaton.
    static void honk() {
        System.out.println("Do Do!");
    }
}

Why use interface?

  • Achieve
    • Loose coupling
      Tight coupling vs. Loose coupling
      • Tight coupling
      ​​​​​​​​public class Person {
      
      ​​​​​​​​    public static void main(String[] args){
      
      ​​​​​​​​        Shopping shopping = new Shopping();
      ​​​​​​​​        shopping.pay();
      ​​​​​​​​    }
      ​​​​​​​​}
      

      We have to commit and uncommit for switching payment instrument to pay.
      Otherwise, creat more shopping class, Shopping1 and Shopping2, etc. to cater to payment instrument instead of commit and uncommit.

      ​​​​​​​​public class Shopping {
      
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        CreditCard creditCard = new CreditCard();
      ​​​​​​​​        creditCard.pay();
      ​​​​​​​​//        SmartPhone smartPhone = new SmartPhone();
      ​​​​​​​​//        smartPhone.pay();
      ​​​​​​​​    }
      ​​​​​​​​}
      
      ​​​​​​​​public class CreditCard {
      
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        System.out.println("Pay by credit card.");
      ​​​​​​​​    }
      ​​​​​​​​}
      
      ​​​​​​​​public class SmartPhone {
      
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        System.out.println("Pay by smart phone.");
      ​​​​​​​​    }
      ​​​​​​​​}
      
      • Loose coupling
        Person can controll the payment instrument he/she want, kind of IoC I think.
      ​​​​​​​​public class Person {
      
      ​​​​​​​​    public static void main(String[] args){
      ​​​​​​​​        PaymentInstrument creditCard = new CreditCard();
      ​​​​​​​​//        PaymentInstrument smartPhone = new SmartPhone();
      
      ​​​​​​​​        Shopping shopping = new Shopping(creditCard);
      ​​​​​​​​        shopping.pay();
      ​​​​​​​​    }
      ​​​​​​​​}
      

      Creat variable to switch payment instrument easier.

      ​​​​​​​​public class Shopping {
      
      ​​​​​​​​    private PaymentInstrument paymentInstrument;
      
      ​​​​​​​​    public Shopping(PaymentInstrument paymentInstrument){
      ​​​​​​​​        this.paymentInstrument = paymentInstrument;
      ​​​​​​​​    }
      
      ​​​​​​​​    public void pay() {
      ​​​​​​​​        paymentInstrument.pay();
      ​​​​​​​​    }
      
      ​​​​​​​​}
      
      ​​​​​​​​public interface PaymentInstrument {
      
      ​​​​​​​​    void pay();
      ​​​​​​​​}
      
      ​​​​​​​​public class CreditCard implements PaymentInstrument{
      
      ​​​​​​​​    @Override
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        System.out.println("Pay by credit card.");
      ​​​​​​​​    }
      ​​​​​​​​}
      
      ​​​​​​​​public class SmartPhone implements PaymentInstrument{
      
      ​​​​​​​​    @Override
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        System.out.println("Pay by smart phone.");
      ​​​​​​​​    }
      ​​​​​​​​}
      
      Recommended vedio

      Image Not Showing Possible Reasons
      • The image file may be corrupted
      • The server hosting the image is unavailable
      • The image path is incorrect
      • The image format is not supported
      Learn More →

    • Multiple implement and interitance
    • Switch implement easily

When use interface

  • Specify the "behavior" of a particular data type
  • Expect the unrelated class whould implenment the interface
    • Storage device (interface) - Optical disc (1965), Floppy disk drive (1971), and USB flash drive (2000), etc.(class)
  • Multiple implement and interface
    • Class USB flash drive implenments interface storage device and charging equipment.

How to use interface?

  • Interface
    Use "extends" to extend the interface.
    ​​​​public interface I002 extends I001 {
    ​​​​    //TODO
    ​​​​}
    
    Use "," when mutiple interface is extended.
    ​​​​public interface I003 extends I001, I002 {
    ​​​​    //TODO
    ​​​​}
    
  • Class
    Use "implenments" to implenments the interface
    ​​​​public class C001 implenments I001 {
    ​​​​    //TODO
    ​​​​}
    
    Use "," when mutiple interface is implenmented.
    ​​​​public interface C002 implenments I001, I002 {
    ​​​​    //TODO
    ​​​​}
    
    Should create all method body which method signature is declared in interface.
    ​​​​public interface I001 {
    ​​​​    String greeting ();
    ​​​​}
    
    ​​​​ public class C001 implenments I001 {
    
    ​​​​     publc greeting (){
    ​​​​         return "hello";
    ​​​​     }
    ​​​​ }
    

Usage

One implement

Instantiate implement in native Java, likes List<int> list = new ArrayList<>(), Map<String, Object> map = new HasMap<>(), etc.
@RestController
@RequestMapping("/One")
public class PersonA1Controller {

    @RequestMapping(value = "/PersonA1", method = RequestMethod.GET)
    public String PersonA1(){

        PaymentInstrumentA paymentInstrumentA = new CreditCardA();
        return paymentInstrumentA.pay(); // Pay by credit card A.
    }
}
public interface PaymentInstrumentA {

    String pay ();
}
@Service
public class CreditCardA implements PaymentInstrumentA {

    @Override
    public String pay(){
        return "Pay by credit card A.";
    }
}
Use @Service/ @Component in Spring Boot
@RestController
@RequestMapping("/One")
public class PersonA2Controller {

    @Autowired
    PaymentInstrumentA paymentInstrumentA;

    @RequestMapping(value = "/PersonA2", method = RequestMethod.GET)
    public String PersonA2(){
       return paymentInstrumentA.pay();
    }
}
public interface PaymentInstrumentA {

    String pay ();
}
@Service
public class CreditCardA implements PaymentInstrumentA {

    @Override
    public String pay(){
        return "Pay by credit card A.";
    }
}

Multiple implement

  • Inter-class
    Use default name (lowercase in the first letter) as variable name.
    ​​​​@RestController
    ​​​​@RequestMapping("/Multiple/Inter")
    ​​​​public class PersonB1Controller {
    
    ​​​​    @Autowired
    ​​​​    PaymentInstrumentB creditCardB1ServiceImpl;
    
    ​​​​    @RequestMapping(value = "/PersonB1", method = RequestMethod.GET)
    ​​​​    public String PersonB1(){
    ​​​​        return creditCardB1ServiceImpl.pay(); // Pay by credit card B1.
    ​​​​    }
    ​​​​}
    
    ​​​​public interface PaymentInstrumentB {
    ​​​​    String pay();
    ​​​​}
    
    ​​​​@Service
    ​​​​public class CreditCardB1ServiceImpl implements PaymentInstrumentB {
    
    ​​​​    @Override
    ​​​​    public String pay() {
    ​​​​        return "Pay by credit card B1.";
    ​​​​    }
    ​​​​}
    
    Name implenment and use @Qualifier to specify default implenment
    ​​​​@RestController
    ​​​​@RequestMapping("/Multiple/Inter")
    ​​​​public class PersonB2Controller {
    
    ​​​​    @Qualifier("CreditCardB2ServiceImpl")
    ​​​​    @Autowired
    ​​​​    PaymentInstrumentB paymentInstrumentB;
    
    ​​​​    @RequestMapping(value = "/PersonB2", method = RequestMethod.GET)
    ​​​​    public String pay(){
    ​​​​        return paymentInstrumentB.pay(); // Pay by credit card B2.
    ​​​​    }
    ​​​​}
    
    ​​​​public interface PaymentInstrumentB {
    ​​​​    String pay();
    ​​​​}
    
    ​​​​@Service("CreditCardB2ServiceImpl")
    ​​​​public class CreditCardB2ServiceImpl implements PaymentInstrumentB {
    
    ​​​​    @Override
    ​​​​    public String pay() {
    ​​​​        return "Pay by credit card B2.";
    ​​​​    }
    ​​​​}
    
    Use @Primary to specify default implenment
    ​​​​@RestController
    ​​​​@RequestMapping("/Multiple/Inter")
    ​​​​public class PersonB3Controller {
    
    ​​​​    @Autowired
    ​​​​    PaymentInstrumentB creditCardCService;
    
    ​​​​    @RequestMapping(value = "/PersonB3", method = RequestMethod.GET)
    ​​​​    public String pay(){
    ​​​​        return creditCardCService.pay(); // Pay by credit card B3.
    ​​​​    }
    ​​​​}
    
    ​​​​public interface PaymentInstrumentB {
    ​​​​    String pay();
    ​​​​}
    
    ​​​​@Primary
    ​​​​@Service
    ​​​​public class CreditCardB3ServiceImpl implements PaymentInstrumentB {
    
    ​​​​    @Override
    ​​​​    public String pay() {
    ​​​​        return "Pay by credit card B3.";
    ​​​​    }
    ​​​​}
    
  • Intra-class
    Instantiate implenment to switch
    ​​​​@RestController
    ​​​​@RequestMapping("/Multiple/Intra")
    ​​​​public class PersonCController {
    
    ​​​​    @Qualifier("CreditCardC1")
    ​​​​    @Autowired
    ​​​​    PaymentInstrumentC paymentInstrumentC;
    
    ​​​​    @RequestMapping(value = "/PersonC1", method = RequestMethod.GET)
    ​​​​    public String payD001(){
    ​​​​        return paymentInstrumentC.pay(); // Pay by credit card C1.
    ​​​​    }
    
    ​​​​    @RequestMapping(value = "/PersonC2", method = RequestMethod.GET)
    ​​​​    public String payD002(){
    ​​​​        PaymentInstrumentC paymentInstrumentC = new CreditCardC2();
    ​​​​        return paymentInstrumentC.pay(); // Pay by credit card C2.
    ​​​​    }
    ​​​​}
    
    
    ​​​​public interface PaymentInstrumentC {
    
    ​​​​    String pay();
    ​​​​}
    
    ​​​​//@Primary
    ​​​​@Service("CreditCardC1")
    ​​​​public class CreditCardC1 implements PaymentInstrumentC {
    
    ​​​​    @Override
    ​​​​    public String pay() {
    ​​​​        return "Pay by credit card C1.";
    ​​​​    }
    ​​​​}
    
    ​​​​@Service("CreditCardC2")
    ​​​​public class CreditCardC2 implements PaymentInstrumentC {
    
    ​​​​    @Override
    ​​​​    public String pay() {
    ​​​​        return "Pay by credit card C2.";
    ​​​​    }
    ​​​​}
    
  • Will Run build failed if don't use @Primary, @Qualifer, and default name, e.g. stackoverflow.
    ​​​​Description:
    ​​​​Field {interfaceName} in {class which declars interface}required a single bean, but 2 were found: 
    ​​​​    {implenment01}: defined in fill {file path}
    ​​​​    {implenment02}: defined in fill {file path}
    ​​​​Action:
    ​​​​Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
    
  • With unit test
    • App code

      • Use @Primary/ name implenment, and use @Qualifier/ default name to specify implenment.
      ​​​​​​​​@RestController
      ​​​​​​​​@RequestMapping("Multiple/UnitTest")
      ​​​​​​​​public class PersonDController {
      
      ​​​​​​​​    @Autowired
      ​​​​​​​​    PaymentInstrumentD paymentInstrumentD;
      
      ​​​​​​​​    @RequestMapping(value = "/PersonD", method = RequestMethod.GET)
      ​​​​​​​​    public String pay(){
      ​​​​​​​​        return paymentInstrumentD.pay(); // Pay by credit card D.
      ​​​​​​​​    }
      ​​​​​​​​}
      
      ​​​​​​​​public interface PaymentInstrumentD {
      
      ​​​​​​​​    String pay();
      ​​​​​​​​}
      
      ​​​​​​​​// @Primary
      ​​​​​​​​@Service
      ​​​​​​​​public class CreditCardD implements PaymentInstrumentD {
      
      ​​​​​​​​    @Override
      ​​​​​​​​    public String pay() {
      ​​​​​​​​        return "Pay by credit card D.";
      ​​​​​​​​    }
      ​​​​​​​​}
      
    • Unit test code

      use default name/ @Qualifier to specify implenment.
      • default name
      ​​​​​​​​@RunWith(SpringRunner.class)
      ​​​​​​​​@ContextConfiguration(classes=ShareApplication.class)
      ​​​​​​​​public class PersonDControllerTest1 {
      
      ​​​​​​​​    @Autowired
      ​​​​​​​​    PaymentInstrumentD creditCardDTest1;
      
      ​​​​​​​​    @Test
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        System.out.println(creditCardDTest1.pay());
      ​​​​​​​​    }
      ​​​​​​​​}
      
      ​​​​​​​​@Service
      ​​​​​​​​public class CreditCardDTest1 implements PaymentInstrumentD {
      
      ​​​​​​​​    @Override
      ​​​​​​​​    public String pay() {
      ​​​​​​​​        return "Pay by credit card D Test 1.";
      ​​​​​​​​    }
      ​​​​​​​​}
      
      • @Qualifier
      ​​​​​​​​@RunWith(SpringRunner.class)
      ​​​​​​​​@ContextConfiguration(classes=ShareApplication.class)
      ​​​​​​​​public class PersonDControllerTest2 {
      
      
      ​​​​​​​​    @Qualifier("CreditCardDTest2")
      ​​​​​​​​    @Autowired
      ​​​​​​​​    PaymentInstrumentD paymentInstrumentD;
      
      ​​​​​​​​    @Test
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        System.out.println(paymentInstrumentD.pay());
      ​​​​​​​​    }
      ​​​​​​​​}
      
      ​​​​​​​​@Service("CreditCardDTest2")
      ​​​​​​​​public class CreditCardDTest2 implements PaymentInstrumentD {
      
      ​​​​​​​​    @Override
      ​​​​​​​​    public String pay() {
      ​​​​​​​​        return "Pay by credit card D Test 2.";
      ​​​​​​​​    }
      ​​​​​​​​}
      
      Will get return from app code if do not use default name/ @Qualifier to specify implenment.
      ​​​​​​​​@RunWith(SpringRunner.class)
      ​​​​​​​​@ContextConfiguration(classes=ShareApplication.class)
      ​​​​​​​​public class PersonDControllerTest2 {
      
      
      ​​​​​​​​    @Qualifier("CreditCardDTest2")
      ​​​​​​​​    @Autowired
      ​​​​​​​​    PaymentInstrumentD paymentInstrumentD;
      
      ​​​​​​​​    @Test
      ​​​​​​​​    public void pay(){
      ​​​​​​​​        System.out.println(paymentInstrumentD.pay());
      ​​​​​​​​    }
      ​​​​​​​​}
      
      ​​​​​​​​@Service("CreditCardDTest2")
      ​​​​​​​​public class CreditCardDTest2 implements PaymentInstrumentD {
      
      ​​​​​​​​    @Override
      ​​​​​​​​    public String pay() {
      ​​​​​​​​        return "Pay by credit card D Test 2.";
      ​​​​​​​​    }
      ​​​​​​​​}
      
    • App code still get return from app code's implenment, even unit test use @Primary.

    • Will Run build failed if don't use @Primary, @Qualifer, and default name.

      ​​​​​​​​Description:
      ​​​​​​​​Field {interfaceName} in {class which declars interface}required a single bean, but 2 were found: 
      ​​​​​​​​    {implenment01}: defined in fill {file path}
      ​​​​​​​​    {implenment02}: defined in fill {file path}
      ​​​​​​​​Action:
      ​​​​​​​​Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
      
    • If app code and unit test code both use @primary

      • Run build will be succussed.
      • Run test will be failed.

Misuse and overuse

  • Create interface just in case.
  • Create interface is only for a class.
  • Create all method body which declare in interface.
  • Name implenment after interface.
    • bad
      ​​​​​​​​public interface PaymentInstrument {
      ​​​​​​​​    
      ​​​​​​​​    String pay();
      ​​​​​​​​}
      
      ​​​​​​​​@Service
      ​​​​​​​​public class PaymentInstrumentImpl implenments PaymentInstrument {
      
      ​​​​​​​​    publc pay(){
      ​​​​​​​​        return "Pay by paymentInstrumentImpl!";
      ​​​​​​​​     }
      ​​​​​​​​ }
      
    • better
      ​​​​​​​​public interface PaymentInstrument {
      ​​​​​​​​    String pay();
      ​​​​​​​​}
      
      ​​​​​​​​@Service
      ​​​​​​​​public class CreditCardVisa implenments PaymentInstrument {
      
      ​​​​​​​​    publc pay(){
      ​​​​​​​​        return "Pay by Visa!";
      ​​​​​​​​     }
      ​​​​​​​​ }
      

Summary

  • Interface characteristics
    • Loose coupling.
    • Multiple implenment and ingeritance.
    • Switch implenment easily.
  • Usage
    • Create a interface to extend interface.
    • Create a class to implenment interface.
    • Use @Primary or name implenment, e.g. @Service("serviceName") and @Component("componentName")
    • Use default name or @Qualifier to specify implenment when declare interface.
  • Misuse and overuse
    • Avoid using without planing.
    • Create interface is only for a class.

Quiz

  • Output of following Code
    (A) Hello!
    (B) Hi!

    ​​​​public class Main {
    
    ​​​​    @Autowired
    ​​​​    IService i002ServiceImpl;
    
    ​​​​    publc static void main(String[] args){
    ​​​​        iService.greeting();
    ​​​​     }
    ​​​​ }
    
    ​​​​public interface IService {
    
    ​​​​     String greeting ();
    ​​​​}
    
    ​​​​@Primary
    ​​​​@service("I001")
    ​​​​public class I001ServiceImpl extends Base {
    
    ​​​​    publc void greeting(){
    ​​​​        System.out.println("Hello!");
    ​​​​     }
    ​​​​ }
    
    ​​​​@Service("I002")
    ​​​​public class I002ServiceImpl extends Base {
    
    ​​​​    publc void greeting(){
    ​​​​        System.out.println("Hi!");
    ​​​​     }
    ​​​​ }
    
    Answer
    ​​​​​​  (B) Hello!