Interface ở Kotlin có gì hot?
Bài đăng này đã không được cập nhật trong 3 năm
Trong Java hay Kotlin đều không có đa kế thừa, để khắc phục được điều này, các lập trình viên đã sử dụng interface
Mặc dù vẫn còn những hạn chế nhất định khi sử dụng interface nhưng qua nhiều phiên bản của Java và Kotlin, interface đã ngày càng mạnh mẽ theo thời gian
Hôm nay chúng ta sẽ cùng nhau nhìn xem interface sẽ có những tính năng gì nổi bật qua các phiên bản nhé
1. Interface trong Java 7
Trong Java 7, một interface chỉ có thể khai báo 2 loại :
- Hằng số (Constant Variables)
- Phương thức trừu tượng (Abstract Method)
interface OnCheckNumberListener {
int MAX_VALUE = 1000;
boolean isEvenNumber(int number);
}
Những phương thức của interface trong phiên bản này không được phép có thân hàm
Class implement interface đều phải cài đặt lại những phương thức trong interface
Bây giờ hãy cùng mình xét một bài toán nho nhỏ: Có 3 class A, B, C cùng implement interface
OnCheckNumberListener
Khi ấy, cả 3 class A, B, C
đều phải cài đặt phương thức isEvenNumber()
như sau:
class A implements OnCheckNumberListener {
@Override
public boolean isEvenNumber(int number) {
return number % 2 == 0;
}
}
class B implements OnCheckNumberListener {
@Override
public boolean isEvenNumber(int number) {
return number % 2 == 0;
}
}
class C implements OnCheckNumberListener {
@Override
public boolean isEvenNumber(int number) {
return number % 2 == 0;
}
}
Rõ ràng thấy, phương thức isEvenNumber()
bắt buộc phải viết lại ở cả 3 class A, B, C
nhưng nội dung của chúng thì giống nhau hoàn toàn
Nếu làm như vậy sẽ không thể tối ưu tính tái sử dụng mã nguồn được
Để giải quyết vấn đề trên, ở Java 7, chúng ta có thể tạo ra 1 lớp abstract
để cài đặt phương thức isEvenNumber()
public abstract class CheckNumber implements OnCheckNumberListener {
@Override
public boolean isEvenNumber(int number) {
return number % 2 == 0;
}
}
Và lúc này, ở 3 class A, B, C
sẽ không phải cài đặt lại phương thức isEvenNumber
khi extends class CheckNumber
class A extends CheckNumber{
}
class B extends CheckNumber{
}
class C extends CheckNumber{
}
Cách này có vẻ khả quan, nhưng hãy nhớ 1 điều rằng, Java không có hỗ trợ đa kế thừa, vì thế cách này chỉ khả thi khi A, B, C
chưa kế thừa lớp cha nào trước đó mà thôi.
2. Interface trong Java 8
Trong Java 8, ngoài hằng số và những phương thức abstract, chúng ta có thể khai báo những phương thức mặc định (default methods) và phương thức tĩnh (static methods)
- Hằng số (Constant Variables)
- Phương thức trừu tượng (Abstract Method)
- Phương thức mặc định (Default method)
- Phương thức tĩnh (Static method)
interface OnCheckNumberListener {
int MAX_VALUE = 1000;
boolean isEvenNumber(int number);
default boolean isOddNumber(int number) {
return number % 2 != 0;
}
static boolean isNegativeNumber(int number){
return number < 0;
}
}
Có lẽ với sự cải tiến này trong Java 8, chúng ta sẽ không cần phải tạo một lớp abstract class CheckNumber
nữa, mà có thể cài đặt luôn trong interface
class A implements OnCheckNumberListener {
@Override
public boolean isEvenNumber(int number) {
return !isOddNumber(number);
}
}
Tuy nhiên, không phải lúc nào hàm default của chúng ta cũng ngắn gọn, chúng có thể rất dài mà chúng ta muốn tách nó ra làm nhiều hàm nhỏ hơn
Khi ấy, vì chúng đang ở trong 1 interface nên những hàm tách ra ấy sẽ phải là public, mà điều này thì có vẻ không phải là thứ mà chúng ta mong muốn
interface OnCheckNumberListener {
int MAX_VALUE = 1000;
boolean isEvenNumber(int number);
default boolean isOddNumber(int number) {
return checkFirst() && checkSecond() && checkThird(number);
}
default boolean checkFirst(){
return true;
}
default boolean checkSecond(){
return false;
}
default boolean checkThird(int number){
return number % 2 != 0;
}
}
3. Interface trong Java 9
Java 9 đã giới thiệu các phương thức private
và phương thứcprivate static
trong các interface
.
- Hằng số
- Phương thức abstract
- Phương thức default
- Phương thức static
- Phương thức private
- Phương thức private static
public interface DBLogging {
String MONGO_DB_NAME = "ABC_Mongo_Datastore";
String NEO4J_DB_NAME = "ABC_Neo4J_Datastore";
String CASSANDRA_DB_NAME = "ABC_Cassandra_Datastore";
default void logInfo(String message) {
log(message, "INFO");
}
default void logWarn(String message) {
log(message, "WARN");
}
default void logError(String message) {
log(message, "ERROR");
}
default void logFatal(String message) {
log(message, "FATAL");
}
private void log(String message, String msgPrefix) {
// Step 1: Connect to DataStore
// Step 2: Log Message with Prefix and styles etc.
// Step 3: Close the DataStore connection
}
// Any other abstract, static, default methods
}
4. Interface trong Kotlin
Ở Kotlin, interface được hỗ trợ nhiều hơn khi có thể có thể khai báo
- Thuộc tính
- Phương thức trừu tượng (Abstract Method)
- Phương thức default
Khác với các lớp trừu tượng, interface trong Kotlin không thể lưu trữ những trạng thái, mặc dù có thể có thuộc tính nhưng chúng cần phải là trừu tượng hoặc phải cài đặt cách thức truy cập
interface MyInterface {
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
fun foo2() // abstract
}
class Child : MyInterface {
override val prop: Int = 29
override fun foo2() {
// body
}
}
4.1 Giải quyết vấn đề xung đột ghi đè của Interface trong Kotlin
Vấn đề xung đột ghi đè của interface thường xảy ra khi một lớp implements nhiều interface có phương thức cùng tên
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
Chúng ta có 2 interface
A và B đều khai báo các hàmfoo()
và bar()
.
Cả hai đều cài đặt foo()
, nhưng chỉ B cài đặt bar()
, còn A thì để abstract
(không cài đặt)
Bây giờ, nếu chúng ta có một lớp C implements A
, rõ ràng, chúng ta phải ghi đè và cài đặt phương thức bar().
Tuy nhiên, nếu chúng ta có lớp D implements
cả A và B, chúng ta cần phải cài đặt lại tất cả những phương thức trùng tên từ A và B để đảm bảo chính xác phương thức đó được thực hiện như nào.
4.2 Thực hư việc khai báo thuộc tính trong interface
Xét interface đơn giản sau
interface A {
var first: Int
val second: Int
}
Đoạn code trên, nếu chúng ta đọc bytecode
mà nó sinh ra, nó sẽ tương đương trong Java như sau
interface A {
int getFirst();
void setFirst(first: Int);
int getSecond();
}
Vậy các bạn có thể thấy, bản chất khi chúng ta khai báo 1 thuộc tính trong interface chính là tạo ra 2 hàm getter
và setter
cho thuộc tính đó
Tuy nhiên, vì interface
trong Kotlin không lưu được trạng thái nên thuộc tính thực sự của nó không tồn tại mà chỉ tồn tại getter
và setter
Nếu là val thì sẽ chỉ có getter
, var thì đầy đủ cả getter/setter
Nếu một lớp implement interface A
theo cách
class AImpl : A {
override var first: Int = 3
override val second: Int get() = 4
}
thì sẽ tương đương trong Java
public final class AImpl implements A {
public int first = 3;
public int getFirst(){
return first;
}
public void setFirst(int first){
this.first = first;
}
public int getSecond(){
return 4;
}
}
Kết luận
Qua đó các bạn có thể thấy, Interface ngày càng được cải thiện qua nhiều phiên bản, hỗ trợ rất nhiều đặc biệt có thể khắc phục triệt để hơn nhược điểm không hỗ trợ đa kế thừa của nó
Tham khảo
[1] https://kotlinlang.org/docs/reference/interfaces.html [2] https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html
All rights reserved