Abstract class và Interface trong Java

Trong bài viết này, chúng ta sẽ cùng tìm hiểu về lớp trừu tượng (abstract class) và interface trong Java. Chúng ta sẽ đánh giá sự giống và khác nhau giữa hai khái niệm này.

Lớp trừu tượng (Abstract Class) trong Java

Đặc điểm của lớp trừu tượng (Abstract Class)

  • Một lớp được khai báo với từ khóa abstract là lớp trừu tượng (abstract class).
  • Lớp trừu tượng có thể có các phương thức abstract hoặc non-abstract.
  • Lớp trừu tượng có thể khai báo 0, 1 hoặc nhiều phương thức trừu tượng bên trong.
  • Không thể khởi tạo trực tiếp một đối tượng từ một class trừu tượng.
  • Một lớp kế thừa từ lớp trừu tượng không cần phải implement các phương thức non-abstract, nhưng phải override các phương thức abstract. Trừ khi lớp con đó cũng là lớp trừu tượng.

Ví dụ về lớp trừu tượng và phương thức trừu tượng

Giả sử chúng ta có yêu cầu vẽ một hình bất kỳ với màu đỏ, sao cho cách sử dụng là giống nhau, bất kể đó là hình chữ nhật (rectangle), hình tròn (circle), tam giác (triangle), đường (line), …

Để đáp ứng yêu cầu trên, chúng ta có thể tạo một lớp trừu tượng Shape. Lớp này cung cấp một phương thức trừu tượng draw, phương thức này để đảm bảo rằng tất cả các hình đều có cùng cách sử dụng (draw). Ngoài ra, có phương thức không trừu tượng getColor để cung cấp màu sử dụng chung cho tất cả các hình. Tiếp theo, chúng ta tạo 2 lớp Rectangle và Circle kế thừa từ lớp Shape, 2 lớp này có những cách xử lý draw khác nhau. Cuối cùng, chúng ta tạo class ShapeApp, gọi phương thức draw để vẽ hình theo yêu cầu.

public abstract class Shape {
    private String color = "red";

    public Shape() {
    }

    public abstract void draw();

    public String getColor() {
        return color;
    }
}

public class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("Vẽ hình chữ nhật màu " + super.getColor());
    }
}

public class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Vẽ hình tròn màu " + super.getColor());
    }
}

public class ShapeApp {
    public static void main(String[] args) {
        Shape rect = new Rectangle();
        rect.draw();
        System.out.println("-");
        Shape circle = new Circle();
        circle.draw();
    }
}

Kết quả:

Vẽ hình chữ nhật màu đỏ
-
Vẽ hình tròn màu đỏ

Một vài lưu ý:

  • Lớp con bắt buộc phải implement tất cả các phương thức trừu tượng của lớp cha.
  • Không thể khởi tạo trực tiếp một lớp trừu tượng.

Interface trong Java

Đặc điểm của Interface

  • Các phương thức trong interface đều là các phương thức trừu tượng.
  • Interface là một kỹ thuật để thu được tính trừu tượng hoàn toàn và đa kế thừa trong Java.
  • Interface luôn có modifier là public interface, cho dù bạn có khai báo rõ hay không.
  • 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.
  • Các phương thức của interface đều là phương thức 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.
  • Interface không có hàm khởi tạo (constructor).
  • Một interface không phải là một lớp. Viết một interface giống như viết một lớp, nhưng chúng có 2 định nghĩa khác nhau. Một lớp mô tả các thuộc tính và hành vi của một đối tượng. Một interface chứa các hành vi mà một class triển khai.
  • Trừ khi một lớp triển khai interface là lớp trừu tượng abstract, còn lại tất cả các phương thức của interface cần được định nghĩa trong class.

Ví dụ sử dụng Interface trong Java

Tương tự như yêu cầu ở ví dụ về sử dụng abstract class ở trên, nhưng chúng ta sẽ dùng Interface để áp dụng vào chương trình.

public interface Shape {
    String color = "red";
    void draw();
}

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Vẽ hình chữ nhật màu " + color);
    }
}

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Vẽ hình tròn màu " + color);
    }
}

public class ShapeApp {
    public static void main(String[] args) {
        Shape rect = new Rectangle();
        rect.draw();
        System.out.println("-");
        Shape circle = new Circle();
        circle.draw();
    }
}

Kết quả:

Vẽ hình chữ nhật màu đỏ
-
Vẽ hình tròn màu đỏ

Khi ghi đè các phương thức được định nghĩa trong interface, có một số qui tắc:

  • Các checked exception không nên được khai báo trong phương thức implements, thay vào đó nên được khai báo trong phương thức interface hoặc các lớp con được khai báo bởi phương thức interface.
  • Signature (ký số) của phương thức interface và kiểu trả về nên được duy trì khi ghi đè phương thức (overriding method).
  • Một lớp triển khai chính nó có thể là abstract và vì thế các phương thức interface không cần được triển khai.

Khi triển khai interface, có vài quy tắc:

  • Một lớp có thể triển khai một hoặc nhiều interface tại một thời điểm.
  • Một lớp chỉ có thể kế thừa một lớp khác, nhưng có thể triển khai nhiều interface.
  • Một interface có thể kế thừa từ nhiều interface khác.

Đa thừa kế trong Java bằng Interface

Nếu một lớp triển khai đa kế thừa, hoặc một interface kế thừa từ nhiều interface thì đó được gọi là đa kế thừa.

Trong Java, một lớp chỉ được thừa kế (extends) từ một lớp, nhưng có thể triển khai nhiều interface. Tuy nhiên, một interface có thể thừa kế (extends) nhiều interface.

Một interface không thể triển khai (implements) interface khác, do interface không thể có phần cài đặt, chỉ chứa các khai báo.

Ví dụ một lớp triển khai nhiều interface:

public interface Shape {
    void draw();
}

public interface Color {
    String getColor();
}

public class Rectangle implements Shape, Color {
    @Override
    public void draw() {
        System.out.println("Vẽ hình chữ nhật màu " + this.getColor());
    }

    @Override
    public String getColor() {
        return "red";
    }
}

Ví dụ một interface kế thừa (extend) nhiều interface:

public interface Shape {
    void draw();
}

public interface Color {
    String getColor();
}

public interface ShapeColor extends Shape, Color {
}

public class Circle implements ShapeColor {
    @Override
    public void draw() {
        System.out.println("Vẽ hình tròn màu " + this.getColor());
    }

    @Override
    public String getColor() {
        return "red";
    }
}

Câu hỏi: Đa kế thừa không được hỗ trợ thông qua lớp trong Java nhưng có thể bởi Interface, tại sao?

Như đã giới thiệu, kế thừa không được hỗ trợ thông qua lớp. Nhưng nó được hỗ trợ bởi Interface bởi vì không có tính lưỡng nghĩa khi trình triển khai được cung cấp bởi lớp Implementation.

Ví dụ đa thừa kế với Interface:

public interface Printable {
    void print();
}

public interface Showable {
    void print();
}

public class InterfaceDemo implements Printable, Showable {
    public void print() {
        System.out.println("Welcome to gpcoder.com");
    }

    public static void main(String args[]) {
        InterfaceDemo obj = new InterfaceDemo();
        obj.print();
    }
}

Trong ví dụ trên, interface Printable và Showable có cùng các phương thức print() nhưng trình triển khai của nó được cung cấp bởi lớp InterfaceDemo, vì thế không có tính lưỡng nghĩa ở đây.

Ví dụ đa thừa kế với class:

public class Printable {
    void print() {
        System.out.println("Printable");
    }
}

public class Showable {
    void print() {
        System.out.println("Showable");
    }
}

// Không thể thực hiện đa thừa kế với class
public class InterfaceDemo extends Printable, Showable {
    public static void main(String args[]) {
        InterfaceDemo obj = new InterfaceDemo();
        obj.print(); // Không thể xác định được gọi phương thức print() của class nào
    }
}

Trong ví dụ trên, lớp Printable và Showable có cùng các phương thức print() và InterfaceDemo kế thừa 2 class đó không override lại phương thức print() nên trình biên dịch không biết thực thi phương thức print() của lớp Printable hay là của lớp Showable. Để đảm bảo an toàn và giảm tính phức tạp của hệ thống nên Java không hỗ trợ đa thừa kế đối với class.

Marker (hay Tagging) Interface trong Java là gì?

Đó là một Interface mà không có thành viên nào. Ví dụ: Serializable, Cloneable, Remote, … Chúng được sử dụng để cung cấp một số thông tin thiết yếu tới JVM để mà JVM có thể thực hiện một số hoạt động hữu ích.

Ví dụ:

public interface Serializable {
}

Có hai mục đích thiết kế chủ yếu của tagging interface là:
- Tạo một cha chung: Như với EventListener interface, mà được kế thừa bởi hàng tá các interface khác trong Java API, bạn có thể sử dụng một tagging interface để tạo một cha chung cho một nhóm interface. Ví dụ, khi một interface kế thừa EventListener, thì JVM biết rằng interface cụ thể này đang được sử dụng trong một event.
- Thêm một kiểu dữ liệu tới một class: Đó là khái niệm tagging. Một class mà triển khai một tagging interface không cần định nghĩa bất kỳ phương thức nào, nhưng class trở thành một kiểu interface thông qua tính đa hình (polymorphism).

### Interface lồng nhau trong Java

Một Interface có thể có Interface khác, đó là lồng Interface.

Ví dụ:

```java
public interface Printable {
    void print();

    interface MessagePrintable {
        void msg();
    }
}
Lớp trừu tượng (Abstract Class) Interface
Thể hiện tính trừu tượng < 100% Thể hiện tính trừu tượng 100%
Lớp trừu tượng có thể có các phương thức abstract và non-abstract Interface chỉ có phương thức abstract
Lớp trừu tượng không hỗ trợ đa kế thừa Interface hỗ trợ đa kế thừa
Lớp trừu tượng có thể có các biến final, non-final, static và non-static Interface chỉ có các biến static final
Lớp trừu tượng có thể có phương thức static, phương thức main và constructor Interface không thể có phương thức static
Từ khóa abstract được sử dụng để khai báo lớp trừu tượng Từ khóa interface được sử dụng để khai báo Interface
Lớp trừu tượng có thể cung cấp trình triển khai của Interface Interface không cung cấp trình triển khai cụ thể của lớp trừu tượng
Sử dụng Abstract class khi chúng ta chỉ có thể hoàn thành một vài chức năng chuẩn của hệ thống, một vài chức năng còn lại các lớp extends phải hoàn thành. Những tính năng đã hoàn thành này vẫn sử dụng như bình thường, đây là những tính năng chung. Sử dụng Interface khi bạn muốn tạo dựng một bộ khung chuẩn gồm các chức năng mà tất cả module/ project cần phải có. Các module phải implements tất cả chức năng đã được định nghĩa.

Tài liệu tham khảo:

Chuyên mục: OOP
Được gắn thẻ: Basic Java, OOP

Bình luận

FEATURED TOPIC