Java

Java 8 Tutorial: Default Method

Java 8 Tutorial: Default Method

Java 8 giới thiệu một tính năng mới là Default Method. Nó cho phép thêm mới các method mới vào interface có sẵn mà các lớp con không cần phải implement lại method nó. Giúp cho cấu trúc của chương trình không bị phá vỡ.

Ví dụ:

@FunctionalInterface
public interface IFoo {
	public void foo1();
	public default void foo2() {
		System.out.println("IFoo.foo2()...");
	}
}
public class FooImp implements IFoo {
	@Override
	public void foo1() {
		System.out.println("FooImp.foo1()...");
	}
}
public class DefaultMethodTest {
	public static void main(String[] args) {
		IFoo foo = new FooImp();
		foo.foo1();
		foo.foo2();
	}
}

Kết quả:

FooImp.foo1()...
IFoo.foo2()...

Như vậy khi ta thấy, nếu FooImp không có nhu cầu implement lại phương thức foo2(), nó có thể dùng lại phương thức đã được implement sẵn trong interface.

Làm như vậy rất hữu ích, hãy tưởng tượng khi bạn có 1 interface có trăm lớp đang implement nó, khi muốn thêm một method mới vào interface, khi đó mọi việc sẽ rất khó khăn và tốn chi phí vì tất cả các lớp con phải implement lại method đó, nhưng chưa hẳn tất cả các lớp con đó đã cần dùng đến method mới. Việc định nghĩa default method trong interface sẽ giúp hạn chế được điều này, những lớp con sẽ implement lại method này nếu cần thiết.

Và các bạn có thể hỏi, vậy interface bây giờ có khác gì với abstract class? Khi cả hai đều có thể có các method được định nghĩa trước? Default method trong interface được đưa ra với mục đích thêm mới một method vào trong interface mà không phá vỡ cấu trúc của các lớp con. Còn default method trong abstract class có nhiều cách sử dụng và liên quan đến tính kế thừa hơn. Nó cho phép các lớp con override lại hàm ở lớp abstract bằng cách implement mới hoàn toàn hoặc tái sử dụng lại hàm ở abstract class bằng cách gọi super sau đó sẽ viết thêm những phần implement mới. Trong interface, các lớp con chỉ có một lựa chọn là sử dụng lại hoặc implement lại default method ở interface. Sau đây là một ví dụ cho vấn đề này

public abstract class AbstractClass {
	public void foo1() {
		System.out.println("AbstractClass.foo1()");
	}
}
public class ConcreteClass extends AbstractClass {
	@Override
	public void foo1() {
		super.foo1();
		System.out.println("ConcreteClass.foo1()");
	}
}
public class AbstractClassTest {
	public static void main(String[] args) {
		AbstractClass myClass = new ConcreteClass();
		myClass.foo1();
	}
}

Kết quả:

AbstractClass.foo1()
ConcreteClass.foo1()

Xung đột các default method

Nếu các bạn đã làm việc qua các ngôn ngữ hỗ trợ đa kế thừa thì hẳn biết được khái niệm “Diamond of death”. Nghĩa là khi lớp con không biết sẽ gọi hàm trong lớp cha nào? Đó là vấn đề khá đau đầu khi lập trình với các ngôn ngữ hỗ trợ đa kế thừa. Java từ lúc mới ra đời kiên quyết không hỗ trợ đa kế thừa mà chỉ hỗ trợ multi-interface implement (không biết nên dịch thế nào :D). Khi Java hỗ trợ default method cho các interface, vấn đề này một lần nữa lại xuất hiện trên interface. Hãy xem xét ví dụ sau:

@FunctionalInterface
public interface IFoo {
	public void foo1();
	public default void foo2() {
		foo1();
		System.out.println("IFoo.foo2()...");
	}
}
@FunctionalInterface
public interface IFoo2 {
	public void foo1();
	public default void foo2() {
		foo1();
		System.out.println("IFoo2.foo2()...");
	}
}
public class FooImp implements IFoo, IFoo2 {
	@Override
	public void foo1() {
		System.out.println("FooImp.foo1()...");
	}
}
public class DefaultMethodTest {
	public static void main(String[] args) {
		IFoo foo = new FooImp();
		foo.foo1();
		foo.foo2();
	}
}

Trình biên dịch sẽ báo lỗi:
Duplicate default methods named foo2 with the parameters () and () are inherited from the types IFoo2 and IFoo

Bởi vì nó không biết sẽ lựa chọn method food2 trong interface nào để thực thi.

Để tránh điều này, chúng ta sẽ implement lại method foo2 trong lớp FooImp

public class FooImp implements IFoo, IFoo2 {
	@Override
	public void foo1() {
		System.out.println("FooImp.foo1()...");
	}

	@Override
	public void foo2() {
		System.out.println("FooImp.foo2()...");
	}
}

Kết quả:

FooImp.foo1()...
FooImp.foo2()...

Tiếp theo hãy tạo 1 interface mới IFoo3

@FunctionalInterface
public interface IFoo3 extends IFoo {

}

Trình dịch không báo lỗi và hiểu IFoo3 vẫn là 1 Functional Interface vì nó kế thừa từ IFoo đã có duy nhất 1 abstract method. Nếu ta thêm một abstract method vào trong IFoo3, sẽ nhận được lỗi vì nó đã có hơn 1 abstract method

@FunctionalInterface
public interface IFoo3 extends IFoo {
	public void foo3();
}

Trình dịch sẽ báo lỗi:
Invalid ‘@FunctionalInterface’ annotation; IFoo3 is not a functional interface

Override các phương thức của lớp Object trong interface

Hãy thử đoạn code dưới đây:

public interface IFoo4 {
	public default boolean equals(Object o) {
		return true;
	}
	public int hashCode();
}

Trình dịch sẽ báo lỗi:
A default method cannot override a method from java.lang.Object

Nghĩa là chúng ta không thể override các phương thức của lớp Object trong interface bằng default method

Static method trong Functional interface

Chúng ta có thể định nghĩa các static method trong Functional interface

@FunctionalInterface
public interface IFoo5 {
	public void foo5();
	public static void foo6() {
		System.out.println("Static IFoo5.foo6()...");
	}
}

Có thể gọi hàm foo6() bằng cách

IFoo5.foo6();

Lambda expression với Functional interface

Lambda expresison trong bài viết trước mình đã có giới thiệu qua là một Functional Interface. Chúng ta có thể khởi tạo một Functional interface thông qua Lambda expression. Các bạn có thể tham khảo bài viết về Lambda expression tại đây

public class DefaultMethodTest {
	public static void main(String[] args) {
		IFoo5 foo = new IFoo5() {
			@Override
			public void foo5() {
				System.out.println("foo.foo5()...");
			}
		};

		foo.foo5();
	}
}

Chúng ta convert qua Lambda expression như sau:

public class DefaultMethodTest {
	public static void main(String[] args) {
		IFoo5 foo = () -> {System.out.println("foo.foo5()...");};
		foo.foo5();
	}
}
public class DefaultMethodTest {
	public static void main(String[] args) {
		IFoo5 foo = () -> {System.out.println("foo.foo5()...");};
		foo.foo5();
	}
}

Kết quả:

foo.foo5()...

Kết thúc bài giới thiệu về default method trong Java 8 tại đây. Mình rất vui nếu có thể nhận được các đóng góp các ý kiến phản hồi bằng cách comment bên dưới bài viết. Các bạn có thể tham khảo một số bài viết khác trong series Java 8 Tutorial tại đây:
Series Java 8 Tutorial

http://www.edwardthienhoang.wordpress.com

Advertisements

2 thoughts on “Java 8 Tutorial: Default Method”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s