Abstract class và Interface trong 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 lớp trừu tượng.
// Nó bắt buộc phải khai báo là abstract 
// vì trong nó có một phương thức trừu tượng
public abstract class ClassA  {

	// Đây là một phương thức trừu tượng.
	// Nó không có thân (body).
	// Access modifier của phương thức này là public.
	public abstract void doSomething();

	// Access modifier của phương thức này là protected.
	protected abstract String doNothing();
	// Phương thức này không khai báo access modifier.
	// Access modifier của nó là mặc định.
	abstract void todo() ;
}

// Đây là một lớp trừu tượng.
// Nó được khai báo là abstract,
// mặc dù nó không có phương thức 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 lớp trừu tượng

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

// Một lớp trừu tượng (Mô phỏng một công việc)
// Nó khai báo 2 phương thức trừu tượng.
public abstract class AbstractJob {

	public AbstractJob() {

	}

	// Đây là một phương thức 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 phương thức trừu tượng.
	public abstract void doJob();

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

// Lớp này thực hiện hết các phương thức trừu tượng của lớp cha.
public class JavaCoding extends AbstractJob {

	public JavaCoding() {
	}

	// Thực hiện phương thức trừu tượng khai báo tại lớp cha.
	@Override
	public void doJob() {
		System.out.println("Coding Java...");
	}

	// Thực hiện phương thức trừu tượng khai báo tại lớp cha.
	// Phương thức này sẽ có thân (body).
	// Và 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)
// Lớp cha (AbstractJob) có 2 phương thức trừu tượng.
// Lớp này mới chỉ thực hiện 1 phương thức trừu tượng của lớp cha.
// Vì vậy nó bắt buộc phải khai báo là abstract.
public abstract class ManualJob extends AbstractJob {

	public ManualJob() {

	}

	// Thực hiện phương thức trừu tượng của lớp 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 được khai báo là trừu tượng.
// Vì vậy nó cần thực hiện tất cả các phương thức trừu tượng còn lại.
public class BuildHouse extends ManualJob {

	public BuildHouse() {

	}

	// Thực hiện phương thức trừu tượng của lớp 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
		// từ Constructor của lớp JavaCoding.
		AbstractJob job1 = new JavaCoding();

		// Gọi phương thức doJob()
		job1.doJob();

		// Phương thức getJobName là trừu tượng trong lớp AbstractJob.
		// Nhưng nó đã được thực hiện tại một lớp con nào đó.
		// Vì vậy bạn có thể gọi nó.
		String jobName = job1.getJobName();

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

		// Khởi tạo một đối tượng AbstractJob
		// từ Constructor của lớp 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- Tổng quan về Interface

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

// Trong trường hợp không chỉ rõ lớp B mở rộng từ một lớp cụ thể nào.
// Mặc định, hiểu rằng B mở rộng từ lớp Object.
public class B  {

}

// Cách khai báo này, và cách phía 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 lớp chỉ có thể mở rộng từ 1 lớp cha.
// Nhưng có thể thực hiện (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 là: 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ó Constructor (cấu tử).

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

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

// Đây là một interface không chỉ định rõ 'access modifier'.
// Access modifier của nó là mặc định.
// Chỉ các lớp 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 một tiêu chuẩn 
// về những thứ có khả năng di chuyển. 
public interface CanMove {

	// Các phương thức trong Interface đều là trừu tượng và public.
	public abstract void run();

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

	// Tốc độ.
	public int getVelocity();

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

// Interface này định nghĩa ra một tiêu chuẩn 
// về những thứ có khả năng biết uống.
public interface CanDrink {

	// Các trường (field) 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 ra một tiêu chuẩn 
// về những thứ có khả năng biết ăn. 
public interface CanEat {

	public void eat();
}

6- Class thực hiện Interface

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

import org.o7planning.tutorial.itf.CanMove;

// Animal (Động vật).
// Lớp này mở rộng từ lớp Object (Mặc dù không ghi rõ).
// Và được khai báo thực hiện hiện (hoặc gọi là thừa kế) interface CanMove.
// Interface CanMove có 3 phương thức trừu tượng.
// Lớp này chỉ thực hiện 1 phương thức.
// Vì vậy nó bắt buộc phải khai báo là abstract.
// Các phương thức trừu tượng còn lại sẽ được các lớp con thực hiện.
public abstract class Animal implements CanMove {

	// Thực hiện phương thức run() của 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;

// Lớp Cat mở rộng từ lớp Animal và thi hành 2 interface CanEat, CanDrink.
// Cat là lớp thông thường (Nó không được khai báo là trừu tượng).
// Vì vậy nó phải thực hiện mọi phương thức 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;
	}

	// Thực hiện phương thức của interface CanMove.
	@Override
	public void back() {
		System.out.println(name + " cat back ...");
	}

	// Thực hiện phương thức của interface CanMove.
	@Override
	public int getVelocity() {
		return 110;
	}

	// Thực hiện phương thức của interface CanEat.
	@Override
	public void eat() {
		System.out.println(name + " cat eat ...");
	}

	// Thực hiện phương thức 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) {

		// 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 (Polymorphism) 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 (Cast).
		if (canEat2 instanceof Mouse) {
			Mouse mouse = (Mouse) canEat2;

			// Gọi phương thức drink (Thừa kế từ CanDrink).
			mouse.drink();
		}

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

Xem thêm các chuyên mục: