Abstract class và Interface trong Java
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Trong tài liệu hướng dẫn này tôi sẽ hướng dẫn về Interface và class trìu tượng (Abstract Class). Đồng thời phân tích sự giống và khác nhau giữa chúng.

2- Class trìu tượng (Abstract Class)

Abstract class (Class trìu tượng). Hãy xem ví dụ về một class như thế:
// Đây là một class trìu tượng.
// Nó bắt buộc phải khai báo là abstract vì trong nó có một method trìu tượng
public abstract class ClassA  {

 // Đây là một method trìu tượng.
 // Nó không có thân hàm.
 // Method này có access modifier là: public
 public abstract void doSomething();

 // Method này có access modifier là protected
 protected abstract String doNothing();

 // Method này không khai báo access modifier
 // Nó có access modifier mặc định.
 abstract void todo() ;
}

// Đây là một class trìu tượng.
// Chủ động khai báo abstract, mặc dù nó không có method trìu tượng nào.
public abstract class ClassB   {

}
Đặc điểm của một class trìu tượng là:
  1. Nó được khai báo abstract.
  2. Nó có thể khai báo 0, 1 hoặc nhiều method trìu tượng bên trong.
  3. Không thể khởi tạo 1 đối tương trực tiếp từ một class trìu tượng.

3- Ví dụ với class trìu tượng

Hãy xem hình minh họa:
AbstractJob.java
package org.o7planning.tutorial.abs;

// Một class trìu tượng (Mô phỏng một công việc)
// Nó khai báo 2 method trìu tượng.
public abstract class AbstractJob {

  public AbstractJob() {

  }

  // Đây là một method trìu tượng.
  // Method này trả về tên của công việc.
  public abstract String getJobName();

  // Đây là một method trìu tượng.
  public abstract void doJob();

}
JavaCoding.java
package org.o7planning.tutorial.abs;

// Class này triển khai hết các method trìu tượng của class cha.
public class JavaCoding extends AbstractJob {

  public JavaCoding() {
  }

  // Method này triển khai method trìu tượng khai báo tại class cha
  @Override
  public void doJob() {
      System.out.println("Coding Java...");
  }

  // Method này triển khai method trìu tượng khai báo tại class cha.
  // Method này sẽ có thân hàm đầy đủ
  // Method trả về tên của công việc.
  @Override
  public String getJobName() {
      return "Coding Java";
  }

}
ManualJob.java
package org.o7planning.tutorial.abs;

// ManualJob - (Mô phỏng một công việc phổ thông)
// Class cha (AbstractJob) có 2 method trìu tượng.
// Class này mới chỉ triển khai 1 method trìu tượng của class cha.
// Vì vậy nó bắt buộc phải khai báo là abstract.
public abstract class ManualJob extends AbstractJob {

  public ManualJob() {

  }

  // Method này triển khai method trìu tượng khai báo tại class cha
  @Override
  public String getJobName() {
      return "Manual Job";
  }

}
BuildHouse.java
package org.o7planning.tutorial.abs;

// Class này thừa kế từ class trìu tượng ManualJob
// BuildHouse không khai báo abstract
// Vì vậy nó cần triển khai các method trìu tượng còn lại.
public class BuildHouse extends ManualJob {

  public BuildHouse() {

  }

  // Triển khai method trìu tượng của class cha.
  @Override
  public void doJob() {
      System.out.println("Build a House");
  }

}
Ví dụ demo
JobDemo.java
package org.o7planning.tutorial.abs;

public class JobDemo {

  public static void main(String[] args) {

      // Khởi tạo một đối tượng AbstractJob.
      // Nó khởi tạo từ cấu tử của class JavaCoding.
      AbstractJob job1 = new JavaCoding();

      // Gọi method doJob()
      job1.doJob();

      // method getJobName là trìu tượng trong class AbstractJob
      // Nhưng nó đã được triển khai tại một class con nào đó.
      // Vì vậy gọi không vấn đề gì.
      String jobName = job1.getJobName();

      System.out.println("Job Name 1= " + jobName);

      // Khởi tạo một đối tượng AbstractJob
      // từ cấu tử của class BuildHouse.
      AbstractJob job2 = new BuildHouse();

      job2.doJob();

      String jobName2 = job2.getJobName();

      System.out.println("Job Name 2= " + jobName2);

  }
}
Kết quả chạy ví dụ:

4- Interface

Chúng ta biết rằng một class chỉ có thể mở rộng từ một class cha.
// Class B là con của class A, hay nói là B mở rộng từ A
// Java chỉ cho phép một class mở rộng từ duy nhất một class khác.
public class B extends A  {
   // ....
}

// Trong trường hợp bạn không viết mở rộng từ một class nào.
// Java tự hiểu là nó thừa kế từ class Object.
public class B  {

}

// Cách khai báo class này, và cách trên là tương đương nhau.
public class B extends Object  {

}
Nhưng một class có thể mở rộng từ nhiều Interface
// Một class có thể mở rộng từ duy nhất một class
// Nhưng có thể mở rộng từ nhiều Interface.
public class Cat extends Animal implements CanEat, CanDrink {

  // ....
}

Các đặc điểm của interface

  1. Interface luôn luôn có modifier: public interface, cho dù bạn có khai báo rõ hay không.
  2. Nếu có các trường (field) thì chúng đều là: public static final, cho dù bạn có khai báo rõ hay không.
  3. Các method của nó đều là method trìu tượng, nghĩa là không có thân hàm, và đều có modifier là: public abstract, cho dù bạn có khai báo hay không.
  4. Interface không có cấu tử (Constructor).

4.1- Cấu trúc của một Interface

NoAccessModifierInterface.java
package org.o7planning.tutorial.itf;

// Đây là một interface không khai báo access modifier.
// Chỉ các class cùng package mới có thể thi hành interface này.
interface NoAccessModifierInterface {

}
CanMove.java
package org.o7planning.tutorial.itf;

// Interface này định nghĩa những thứ có khả năng di chuyển.
// Interface bắt buộc phải khai báo access modifier là public hoặc không khai báo gì
// Không thể viết protected hoặc private cho interface.
public interface CanMove {

// (Chạy)
// Các method trong Interface đều là method trìu tượng.
// Luôn luôn là: public abstract ...
public abstract void run();

// (Quay trở lại)
// Cho dù không viết rõ public abstract thì java luôn hiểu là vậy.
void back();

// (Lấy ra vận tốc chạy).
public int getVelocity();

}
CanDrink.java
package org.o7planning.tutorial.itf;

// Interface này định nghĩa những thứ có khả năng biết uống.
// Interface bắt buộc phải khai báo access modifier là public hoặc không khai báo gì
// Không thể viết protected hoặc private cho interface.
public interface CanDrink {

// Các trường trong Interface đều là public static final.
// Cho dù bạn có khai báo rõ hay không java luôn hiểu ngầm vậy.
public static final String PEPSI = "PEPSI";
final String NUMBER_ONE = "NUMBER ONE";
String SEVENUP = "SEVEN UP";

public void drink();

}
CanEat.java
package org.o7planning.tutorial.itf;

// Interface này định nghĩa thứ có khả năng biết ăn.
// Interface bắt buộc phải khai báo access modifier là public hoặc không khai báo gì
// Không thể viết protected hoặc private cho interface.
public interface CanEat {

 public void eat();
}

4.2- Class thi hành Interface

Animal.java
package org.o7planning.tutorial.cls;

import org.o7planning.tutorial.itf.CanMove;

// Animal (Class mô phỏng lớp động vật)
// Nó mở rộng từ class Object (Mặc dù không ghi rõ).
// Và khai báo thi hành (hoặc gọi là thừa kế) interface CanMove.
// Interface CanMove có 3 method trìu tượng.
// Class này mới triển khai 1 method
// Vì vậy nó bắt buộc phải khai báo abstract
// Các method trìu tượng còn lại sẽ được class con triển khai
public abstract class Animal implements CanMove {

  // Triển khai method run() từ interface CanMove.
  @Override
  public void run() {
      System.out.println("Animal run...");
  }

}
Cat.java
package org.o7planning.tutorial.cls;

import org.o7planning.tutorial.itf.CanDrink;
import org.o7planning.tutorial.itf.CanEat;

// Class Cat mở rộng từ class Animal và thi hành 2 interface CanEat, CanDrink.
// Cat là class thông thường (Nó không khai báo abstract)
// Vì vậy nó phải triển khai mọi method trìu tượng của các Interface.
public class Cat extends Animal implements CanEat, CanDrink {

  private String name;

  public Cat(String name) {
      this.name = name;
  }

  public String getName() {
      return this.name;
  }

  // Triển khai method của interface CanMove
  @Override
  public void back() {
      System.out.println(name + " cat back ...");
  }

  // Triển khai method của interface CanMove
  @Override
  public int getVelocity() {
      return 110;
  }

  // Triển khai method của interface CanEat
  @Override
  public void eat() {
      System.out.println(name + " cat eat ...");
  }

  // Triển khai method của interface CanDrink
  @Override
  public void drink() {
      System.out.println(name + " cat drink ...");
  }

}
Mouse.java
package org.o7planning.tutorial.cls;

import org.o7planning.tutorial.itf.CanDrink;
import org.o7planning.tutorial.itf.CanEat;

public class Mouse extends Animal  implements CanEat, CanDrink {

  @Override
  public void back() {
      System.out.println("Mouse back ...");
  }

  @Override
  public int getVelocity() {
      return 85;
  }

  @Override
  public void drink() {
      System.out.println("Mouse drink ...");
  }

  @Override
  public void eat() {
      System.out.println("Mouse eat ...");
  }

}
AnimalDemo.java
package org.o7planning.tutorial.cls;

import org.o7planning.tutorial.itf.CanEat;

public class AnimalDemo {

  public static void main(String[] args) {
      // Class Cat thừa kế trường tĩnh từ interface CanDrink.
      System.out.println("Drink " + Cat.SEVENUP);

      // Khởi tạo một đối tượng CanEat
      // Một đối tượng khai báo là CanEat
      // Nhưng thực tế là Cat.
      CanEat canEat1 = new Cat("Tom");

      // Một đối tượng khai báo là CanEat
      // Nhưng thực tế là Mouse.
      CanEat canEat2 = new Mouse();

      // Tính đa hình thể hiện rõ tại đây.
      // Java luôn biết một đối tượng là kiểu gì
      // ==> Tom cat eat ...
      canEat1.eat();
      // ==> Mouse eat ...
      canEat2.eat();

      boolean isCat = canEat1 instanceof Cat;

      System.out.println("catEat1 is Cat? " + isCat);

      // Ép kiểu
      if (canEat2 instanceof Mouse) {
          Mouse mouse = (Mouse) canEat2;
          // Gọi method drink (Thừa kế từ CanDrink).
          mouse.drink();
      }

  }
}
Kết quả chạy ví dụ: