Một trong những thay đổi lớn trong Java 8 là khái niệm về Interface. Như chúng ta đã biết trong những phiên bản Java trước, interface chỉ cho phép chúng ta khai báo các phương thức public abstract methods bên trong nó. Nhưng trong Java 8, chúng ta sẽ có thêm 2 khái niệm mới đối với interface là phương thức default (default method) và phương thức static (static method).
Với Interface, khi chúng ta thay đổi các phương thức bên trong nó đòi hỏi phải thay đổi tất cả các class được implements từ nó. Một khi số lượng các class được implements từ interface phát triển nhiều lên thì đến mức độ nào đó interface có thể không mở rộng được nữa. Đây là lý do tại sao khi thiết kế một ứng dụng, hầu hết các framework cung cấp một base class, sau đó chúng ta sẽ extends và override các phương thức phù hợp với ứng dụng đang thực hiện.
Trong phần tiếp theo của bài viết này, chúng ta sẽ cùng tìm hiểu về Default method và Static Method trong Java 8.
Nội dung
Phương thức default trong Interface – Default method
Giới thiệu
Java 8 giới thiệu môt khái niệm mới về Default Method dành cho Interface. Nó cho phép chúng ta thêm vào các chức năng cho interface mà không làm phá vỡ các lớp implement từ interface này.
Ví dụ: chương trình của chúng ta có một Interface Shape. Interface này có một phương thức là draw(). Ta có 2 lớp Rect và Circle implements phương thức draw() lớp Shape này. Bây giờ, chúng ta muốn thêm một phương thức setColor() cho Interface Shape. Trước phiên bản Java 8, chúng ta bắt buộc phải implement phương thức setColor() này trong các lớp Rect và Circle. Với Java 8, chúng ta có cách sử dụng đơn giản như sau:
public interface Shape { void draw(); default void setColor(String color) { System.out.println("Draw shape with color " + color); } }
Phương thức setColor(String color) chính là phương thức default của Shape. Khi một class được implements từ Shape nó không bắt buộc phải implement phương thức default. Tính năng này giúp chúng ta dễ dàng mở rộng các phương thức bổ sung phát sinh sau này mà không ảnh hưởng đến các class liên quan, chúng ta chỉ cần viết thêm các phương thức default trong interface.
Điều gì xảy ra khi có đa thừa kế?
Như chúng ta đã biết, một class có thể implements nhiều interface cùng 1 lúc. Vậy điều gì sẽ xảy ra khi các interface này có các default methods cùng tên?
Khi một class triển khai 2 interface chứa default method cùng tên sẽ xảy ra xung đột (conflict). Bởi vì lúc này Java sẽ không biết phải sử dụng phương thức mặc định nào cho phù hợp. Khi đó, trình biên dịch của Java sẽ thông một lỗi tương tự như sau:
Để giải quyết vấn đề này chúng ta có thể sử dụng một trong hai cách để giải quyết bên dưới:
- Override lại phương thức doSomething từ lớp con.
- Gọi default method của một interface cụ thể bằng cách sử dụng từ khóa super.
Ví dụ:
package com.gpcoder.default_method; interface Interface1 { default void doSomething() { } } interface Interface2 { default void doSomething() { } } public class MultiInheritance implements Interface1, Interface2 { @Override public void doSomething() { Interface1.super.doSomething(); } }
Giả sử chúng ta có một super class có phương thức (abstract method hoặc non-abstract method) cùng tên với default method của interface. Trong trường hợp này, thì phương thức nào sẽ được thực thi?
package com.gpcoder.default_method; interface Interface3 { default void doSomething() { System.out.println("Execute in Interface3"); } } class Parent { public void doSomething() { System.out.println("Execute in Parent"); } } public class MultiInheritance2 extends Parent implements Interface3 { public static void main(String[] args) { MultiInheritance2 m = new MultiInheritance2(); m.doSomething(); // Execute in Parent } }
Nếu một lớp con thừa kế một phương thức (abstract hoặc non-abstract) từ một super class và một phương thức cùng tên trong các super interface, thì lớp con sẽ thừa kế phương thức của super class và các phương thức của interface sẽ bị bỏ qua.
Tóm tắt một số đặc điểm quan trọng về default methods trong interface:
- Giúp chúng ta dễ dàng mở rộng interface mà không phá vỡ các class được implements từ nó
- Giúp chúng ta tránh dùng các class tiện ích, ví dụ như tất cả phương thức của class Collections có thể được cung cấp ngay bên trong interface của nó.
- Giúp chúng ta tháo gỡ các class cơ sở (base class), chúng ta có thể tạo phương thức default và trong class được implement có thể chọn phương thức để override.
- Một trong những lý do xuất hiện của phương thức default là để nâng cấp Collection API trong Java 8 hỗ trợ cho Lambda Expression.
- Nếu bất kỳ class nào kế thừa những phương thức default giống nhau, thì nó sẽ không còn hiệu lực. Một điều tương tự, một phương thức default sẽ không thể override một phương thức từ java.lang.Object. Lý do rất đơn giản là bởi vì Object là base class của tất cả các class trong Java. Vì vậy nếu chúng ta có các phương thức của class Object được định nghĩa là phương thức default trong interface, nó sẽ không dùng được bởi vì các phương thức của Object luôn luôn được sử dụng. Đây lý do tại sao chúng ta sẽ không có bất cứ phương thức default nào override các phương thức của class Object.
- Phương thức default cũng có thể được gọi là phương thức Defender (Defender Methods) hay là phương thức Virtual mở rộng (Virtual extension methods).
- Phương thức default cho phép các Java interface đã tồn tại phát triển thêm mà không gây lỗi trong quá trình biên dịch. Ví dụ như bổ sung các phương thức vào interface java.util.Collection: stream(), parallelStream(), forEach(), …
Phương thức static trong Interface – Static method
Phương thức static cũng giống như phương thức default, ngoại trừ việc nó không thể được override chúng trong class được implements.
Ví dụ:
package com.gpcoder.static_method; interface Vehicle { default void print() { if (isValid()) System.out.println("Vehicle printed"); } static boolean isValid() { System.out.println("Vehicle is valid"); return true; } void showLog(); } public class Car implements Vehicle { @Override public void showLog() { print(); Vehicle.isValid(); } }
Trong đoạn code trên, trong lớp Car chúng ta có thể sử dụng phương thức static isValid() của interface Vehicle.
Những đặc điểm quan trọng về phương thức static trong interface:
- Phương thức static là một thành phần của interface, chúng ta có thể sử dụng nó trong class được implements từ nó.
- Phương thức static rất hữu ích trong việc cung cấp các phương thức tiện ích.
- Lớp con có thể định nghĩa một phương thức cùng tên với phương thức static của interface cha. Tuy nhiên, chúng ta không thể thêm annotation @Override cho phương thức này.
- Chúng ta không thể định nghĩa phương thức static của các phương thức thuộc class Object, chúng ta sẽ gặp lỗi “This static method cannot hide the instance method from Object”. Điều này không cho phép trong Java, khi Object là base class cho tất cả các class và chúng ta không thể có một phương thức static và một phương thức khác cùng định dạng.
Nhìn lại Abstract classes vs interfaces trong Java 8
Với default method trong Java 8, chúng ta thấy rằng chức năng của abstract class và interface giống như nhau. Tuy nhiên điều này không hoàn toàn đúng, mặc dù bây giờ chúng ta có các phương thức cụ thể (các phương thức có thân) trong các interface giống như abstract class, điều này không có nghĩa là chúng giống nhau. Vẫn còn ít sự khác biệt giữa chúng, một trong số đó là abstract class có thể có hàm tạo (constructor) trong khi trong các interface chúng ta không thể có các hàm tạo.
Mục đích của interface là cung cấp sự trừu tượng đầy đủ (100% abstract), trong khi mục đích của abstract class là cung cấp một phần trừu tượng hóa (<100% abstract). Điều này vẫn đúng. Interface giống như một kế hoạch chi tiết cho lớp của bạn, với việc giới thiệu các phương thức mặc định, chúng ta có thể thêm các tính năng bổ sung trong các interface mà không ảnh hưởng đến các lớp implement hay end user.