Java

Java 8 Tutorial Series

Gần 2 năm rưỡi sau khi phiên bản Java 7 ra đời, ngày 18/3/2014, Oracle chính thức giới thiệu thế hệ tiếp theo: Java 8. Phiên bản mới này được xem là Java-7-hoàn-thiện-hơn bởi vì nhiều tính năng quan trọng trong Java 8 theo kế hoạch ban đầu thì thuộc về Java 7, nhưng các chậm trễ trong quá trình phát triển đã khiến Oracle quyết định ra đời Java 7 gọn nhẹ hơn, với lời hứa sẽ giới thiệu những tính năng còn thiếu trong phiên bản kế tiếp.

Đây là loạt bài viết về những tính năng mới có mặt trong Java 8.

Series Java 8 Tutorial

 

Java 9 và kế hoạch cho tương lai

Oracle đã bắt đầu quá trình phát triển phiên bản tiếp theo là Java 9 với dự kiến phát hành trong năm 2016. Chưa có danh sách các tính năng sẽ đưa vào Java 9 ngoài những mục tiêu chung chung như hỗ trợ ứng dụng cần heap lớn, tích hợp tốt hơn với CPU theo hướng thực hiện song song nhưng tác vụ xử lý nặng, JVM sẽ tự điều chỉnh để chạy ngày càng tốt hơn,… Java 9 là bước đệm để java chuyển hoàn toàn sang thế hệ 64-bit, từ Java 10 thì JDK sẽ chỉ có phiên bản x64.

Java 8 hay và dở

Đối với nhiều lập trình viên java lâu năm, java 8 là phiên bản đáng mong đợi, với biểu thức lambda và động cơ Nashorn, năng suất của lập trình viên có thể tăng đáng kể.

Đối với người mới học java, những cải tiến này làm java mất tính trong sáng và có thể gây bối rối (đặc biệt là phương thức default cho interface).

Đối với sự phát triển ngôn ngữ, từ một ngôn ngữ tiên phong khi mới ra đời (1995), java đã dần dần mất đi vị trí dẫn đầu và định hướng cho sự phát triển các ngôn ngữ lập trình. Những cải tiến như biểu thức lambda, phương thức mặc định cho interface hay các phương thức sắp xếp song song trên Array,… là sự học tập một cách vụng về các tiếp cận vấn đề của C#.

 

http://www.edwardthienhoang.wordpress.com

Design Pattern

Singleton Pattern – Một object duy nhất

Singleton Pattern – Một object duy nhất

Xin chào các bạn, hôm nay chúng ta sẽ tiếp tục làm quen với một mẫu Design Pattern mới: Singleton pattern. Cái tên đã nói lên tất cả, nếu muốn trong suốt chương trình chỉ tồn tại duy nhất một đối tượng của một lớp nào đó thì Singleton chính là điều chúng ta cần nghĩ đến đầu tiên. Ví dụ như các lớp Logging, Configuration, … Trong JDK tồn tại khá nhiều class dưới dạng Singleton như java.lang.Runtime, tất cả các chương trình Java đều có duy nhất một đối tượng Runtime để cho phép tương tác với environment của chương trình đang chạy. Hoặc java.awt.Desktop, java.awt.GraphicsEnvironment. Ngoài ra, Singleton Pattern còn được sử dụng kèm với các Pattern khác như: Abstract Factory, Builder, Prototype, Facade…

Tạo một lớp Singleton

Để gọi là một Singleton, hay nói đơn giản nếu bạn muốn tạo một lớp Singleton thì lớp đó phải đáp ứng một số yêu cầu sau:

  1. Private constructor: nhằm không để bên ngoài có thể khởi tạo các thể hiện của lớp một cách tùy ý.
  2. Private static instance: một lớp Singleton cần có một thể hiện static của chính nó.
  3. Public static function: một hàm public static để thực hiện việc khởi tạo và trả về thể hiện của lớp. Nếu một class muốn gọi các hàm (non-static) khác của lớp thì cần thực hiện thông qua hàm static này.

Mình sẽ giải thích kỹ hơn bằng các ví dụ dưới đây. Chúng ta sẽ cùng điểm qua một số cách khởi tạo lớp Singleton, những điểm cần lưu ý cũng như best practice khi áp dụng Singleton:
1. Eager initialization
2. Static block initialization
3. Lazy Initialization
4. Thread Safe Singleton
5. Bill Pugh Singleton Implementation
6. Using Reflection to destroy Singleton Pattern
7. Enum Singleton
8. Serialization and Singletons

1. Eager initialization

public class EagerInitializedSingleton {

    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    //private constructor to avoid client applications to use constructor
    private EagerInitializedSingleton(){}

    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

Đây là cách đơn giản và an toàn nhất. Tuy nhiên, cách này có một nhược điểm là nếu lớp này không được sử dụng sau đó thì việc khởi tạo sẽ trở thành phí phạm vì biến instance sẽ được khởi tạo ngay lúc chương trình chạy bất kể là có sử dụng đến lớp Singleton hay không. Một điểm nữa là không thể handle exception.

2. Static block initialization

Cũng giống như Eager initialization ngoại trừ việc biến instance được khởi tạo trong khối static, ở ngay đây, exception sẽ được handle.

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;

    private StaticBlockSingleton(){}

    //static block initialization for exception handling
    static{
        try{
            instance = new StaticBlockSingleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }

    public static StaticBlockSingleton getInstance(){
        return instance;
    }
}

3. Lazy Initialization

public class LazyInitializedSingleton {
    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

Sử dụng cơ chế Lazy Initialization sẽ đảm bảo biến instance chỉ được khởi tạo nó thực sự được dùng đến, nghĩa là khi chúng ta cần đến lớp Singleton để xử lý công việc thông qua việc gọi hàm getInstance. Tuy nhiên Lazy Initialization lại để lộ ra một nhược điểm khi chạy trong môi trường multithreading. Khi có nhiều thread truy xuất đến hàm getInstance, sẽ xảy ra việc có nhiều đối tượng Singleton được tạo ra. Vì vậy chúng ta cần có các cơ chế implementation khác để đảm bảo tính thread-safe cho lớp Singleton. Cùng điểm qua 1 số cách dưới đây

4. Thread Safe Singleton

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

Bằng cách synchronized hàm getInstance, sẽ đảm bảo chỉ có duy nhất 1 thread được truy cập vào hàm getInstance và khởi tạo 1 đối tượng instance duy nhất. Đây là cách đơn giản và thông dụng nhất để đảm bảo tính thread-safe cho lớp Singleton. Tuy nhiên việc synchronized như vậy sẽ gây tốn chi phí về thời gian, vì tại 1 thời điểm, chỉ có duy nhất 1 thread được truy cập vào hàm getInstance để lấy ra (hoặc khởi tạo) đối tượng instance.

Thay vào đó, sử dụng kỹ thuật double checked locking sẽ giảm được chi phí cho hàm getInstance

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    if(instance == null){
        synchronized (ThreadSafeSingleton.class) {
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

5. Bill Pugh Singleton Implementation

Phương pháp Bill Pugh sử dụng inner static helper class để khởi tạo đối tượng Singleton. Cách này được xem là hay và nên áp dụng nhất.

public class BillPughSingleton {

    private BillPughSingleton(){}

    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

Ở đây, tuy là lớp SingletonHelper đã khởi tạo trực tiếp biến INSTANCE, nhưng biến INSTANCE chỉ được load vào memory khi nó được gọi thông qua hàm getInstance (các bạn tham khảo thêm về static inner class trong Java)

Ở trên là 1 số ví dụ về việc tạo ra 1 lớp Singleton trong Java. Còn rất nhiều vấn đề liên quan đến Singleton mà mình sẽ đề cập ở các bài viết khác như:

  • So sánh Singleton và Static class
  • Singleton trong môi trường multi JVM
  • Singleton sẽ không được đảm bảo nếu sử dụng Reflection
  • Serialize và deserialize đối tượng Singleton

http://www.edwardthienhoang.wordpress.com

Java

Java 8 Tutorial: Optional

Java 8 Tutorial: Optional

Optional là 1 container được giới thiệu trong Java 8, theo quảng cáo của Oracle thì nó sẽ giúp cho các lập trình viên tránh được cơn ác mộng NullPointerException. Nhưng theo mình thấy thì thay vì kiểm tra null bằng cách thông thường, Optional cung cấp 1 số hàm, tiện ích khác để chúng ta check null. Mình cũng chưa có cơ hội áp dụng vào thực tế, nhưng trước hết, cùng điểm qua 1 số hàm mà Optional cung cấp.

Optional chính xác là gì

Theo Javadoc của Java 8 định nghĩa thì:
Optional là một class nằm trong gói java.util, nó là 1 object container có thể đang chứa một object thực hoặc một giá trị null. Bằng việc sử dụng hàm isPresent có thể check được object được chứa trong nó là null hay không, và hàm get để get ra object đó.

empty
Tạo ra 1 empty Optional
// Create an empty Optional
Optional emptyOptional = Optional.empty();

of

Hàm of nhận vào 1 đối tượng non-null và trả về một đối tượng Optional chứa đối tượng đó. Nếu truyền vào hàm of 1 đối tượng null, chúng ta nhận về 1 NullPointerException

// Create Optional object which contains a String object
Optional name = Optional.of("Edward");
// It will throw NullPointerException
Optional nullName = Optional.of(null);

ofNullable

Tương tự như of, nhưng trong trường hợp hàm nhận vào 1 null object, nó sẽ trả về 1 Empty Optional thay vì throw NullPointerException

// Return an empty Optional
Optional empty = Optional.ofNullable(null);
// Simply check it. true
System.out.println(empty.equals(emptyOptional));

isPresent

Trả về true trong trường hợp object đang tồn tại. Ngược lại là false

// true
System.out.println(name.isPresent());
// false
System.out.println(emptyOptional.isPresent());

get

Trả về object nếu đang tồn tại trong Optional. Ngược lại throw NoSuchElementException

// Edward
System.out.println(name.get());
// It will throw NoSuchElementException
System.out.println(emptyOptional.get());

ifPresent

Hàm nhận vào 1 Consumer. Nếu object đang tồn tại sẽ chạy đoạn mã thực thi của Consumer, ngược lại sẽ không làm gì cả. Có thể hiểu nó là 1 Functional Interface nhận vào 1 tham số, không có giá trị trả về.

// Hello Edward
name.ifPresent((value) -> System.out.println("Hello " + value));
// Nothing happens
emptyOptional.ifPresent((value) -> System.out.println("Hello " + value));

orElse

Trả về object nếu đang tồn tại, ngược lại trả về giá trị mặc định do Client định nghĩa

// Edward
System.out.println(name.orElse("NoName"));
// NoName
System.out.println(emptyOptional.orElse("NoName"));

orElseGet

Phương thức này tương tự như phương thức orElse ở trên. Nhưng trong trường hợp object không tồn tại, nó sẽ dùng 1 Supplier interface để tạo ra giá trị mặc định. Supplier có thể được hiểu như một Functional Interface không nhận vào tham số nào, và có kiểu trả về.

// Edward
System.out.println(name.orElseGet(() -> {return "NoName";}));
// NoName
System.out.println(emptyOptional.orElseGet(() -> {return "NoName";}));

orElseThrow

Cũng tương tự như 2 phương thức orElse và orElseGet. Nhưng trong trường hợp object không tồn tại, sẽ throw ra Exception từ lambda expression do Client tự định nghĩa.

// Edward
try {
	System.out.println(name.orElseThrow(() -> {return new Exception();})); // Lambda expression
} catch (Exception e) {
	e.printStackTrace();
}
// Cannot get object from emptyOptional
try {
	// It will throw Exception
	System.out.println(emptyOptional.orElseThrow(Exception::new)); // Here is also lambda expression, both are the same
} catch (Exception e) {
	// Catch it
	System.out.println("Cannot get object from emptyOptional");
	e.printStackTrace();
}

map

Trong trường hợp object đang tồn tại, sử dụng lambda expression với đối số là object đó và tạo ra 1 Optional mới chứa object chính là kết quả trả về từ lambda expression. Ngược lại, trả về 1 empty Optional

Optional upperName = name.map((value) -> {return value.toUpperCase();});
// EDWARD
upperName.ifPresent((value) -> {System.out.println(value);});
Optional lowerName = emptyOptional.map((value) -> {return value.toUpperCase();});
// Nothing happens
lowerName.ifPresent((value) -> {System.out.println(value);});

flatMap

Phương thức này cũng nhận vào 1 lambda expression giống như phương thức map. Nhưng kết quả trả về từ lambda expression luôn là 1 Optional

// EDWARD
Optional upperName2 = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName2.orElse("NoName"));
Optional lowerName2 = emptyOptional.map((value) -> {return value.toUpperCase();});
// NoName
System.out.println(lowerName2.orElse("NoName"));

filter

Phương thức này truyền vào 1 lambda expresison thực thi cho Predicate interface như là một điều kiện kiểm tra. Nếu object đang chứa trong Optional không thỏa mãn sẽ trả về một empty Optional, ngược lại trả về một Optional mới chứa object đó.

Optional validName = name.filter((value) -> {return value.length() > 3;});
// Edward is valid
validName.ifPresent((value) -> {System.out.println(value + " is valid");});
Optional validName1 = name.filter((value) -> {return value.length() > 10;});
// Nothing happens
validName1.ifPresent((value) -> {System.out.println(value + " is valid");});

Các bạn có thể xem full source code ở đây:

import java.util.Optional;

public class Optional1 {

	public static void main(String[] args) {
		// empty
		// Create an empty Optional
		Optional emptyOptional = Optional.empty();

		// of
		// Create Optional object which contains a String object
		Optional name = Optional.of("Edward");
		try {
			// It will throw NullPointerException
			Optional nullName = Optional.of(null);
		}
		catch(Exception e) {
			System.out.println(e.getMessage());
			e.printStackTrace();
		}

		// ofNullable
		// Return an empty Optional
		Optional empty = Optional.ofNullable(null);
		// Simply check it. true
		System.out.println(empty.equals(Optional.empty()));

		// isPresent
		// true
		System.out.println(name.isPresent());
		// false
		System.out.println(emptyOptional.isPresent());

		// get
		// Edward
		System.out.println(name.get());
		try {
			// It will throw NoSuchElementException
			System.out.println(emptyOptional.get());
		}
		catch(Exception e) {
			System.out.println(e.getMessage());
			e.printStackTrace();
		}

		// ifPresent
		// Hello Edward
		name.ifPresent((value) -> System.out.println("Hello " + value));
		// Nothing happens
		emptyOptional.ifPresent((value) -> System.out.println("Hello " + value));

		// orElse
		// Edward
		System.out.println(name.orElse("NoName"));
		// NoName
		System.out.println(emptyOptional.orElse("NoName"));

		// orElseGet
		// Edward
		System.out.println(name.orElseGet(() -> {return "NoName";}));
		// NoName
		System.out.println(emptyOptional.orElseGet(() -> {return "NoName";}));

		// orElseThrow
		// Edward
		try {
			System.out.println(name.orElseThrow(() -> {return new Exception();})); // Lambda expression
		} catch (Exception e) {
			e.printStackTrace();
		}
		// Cannot get object from emptyOptional
		try {
			// It will throw Exception
			System.out.println(emptyOptional.orElseThrow(Exception::new)); // Here is also lambda expression, both are the same
		} catch (Exception e) {
			// Catch it
			System.out.println("Cannot get object from emptyOptional");
			e.printStackTrace();
		}

		// map
		Optional upperName = name.map((value) -> {return value.toUpperCase();});
		// EDWARD
		upperName.ifPresent((value) -> {System.out.println(value);});
		Optional lowerName = emptyOptional.map((value) -> {return value.toUpperCase();});
		// Nothing happens
		lowerName.ifPresent((value) -> {System.out.println(value);});

		// flatMap
		// EDWARD
		Optional upperName2 = name.flatMap((value) -> Optional.of(value.toUpperCase()));
		System.out.println(upperName2.orElse("NoName"));
		Optional lowerName2 = emptyOptional.map((value) -> {return value.toUpperCase();});
		// NoName
		System.out.println(lowerName2.orElse("NoName"));

		// filter
		Optional validName = name.filter((value) -> {return value.length() > 3;});
		// Edward is valid
		validName.ifPresent((value) -> {System.out.println(value + " is valid");});
		Optional validName1 = name.filter((value) -> {return value.length() > 10;});
		// Nothing happens
		validName1.ifPresent((value) -> {System.out.println(value + " is valid");});

	}

}

Kết quả:

null
java.lang.NullPointerException
	at java.util.Objects.requireNonNull(Unknown Source)
	at java.util.Optional.(Unknown Source)
	at java.util.Optional.of(Unknown Source)
	at com.edward.tutorial.java8.optional.Optional1.main(Optional1.java:17)
true
true
false
Edward
No value present
java.util.NoSuchElementException: No value present
	at java.util.Optional.get(Unknown Source)
	at com.edward.tutorial.java8.optional.Optional1.main(Optional1.java:41)
Hello Edward
Edward
NoName
Edward
NoName
Edward
Cannot get object from emptyOptional
java.lang.Exception
	at com.edward.tutorial.java8.optional.Optional1$$Lambda$6/798154996.get(Unknown Source)
	at java.util.Optional.orElseThrow(Unknown Source)
	at com.edward.tutorial.java8.optional.Optional1.main(Optional1.java:76)
EDWARD
EDWARD
NoName
Edward is valid

Kết thúc bài giới thiệu về Optional 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 Core Overview

http://www.edwardthienhoang.wordpress.com

Java

Java 8 Tutorial: Method Reference

Java 8 Tutorial: Method Reference

Đi cùng với Lambda Expression, Java 8 có thêm một khái niệm mới là Method Reference. Chúng ta thường dùng Lambda Expression để tạo ra các method vô danh (anonymous method), nhưng đôi lúc Lambda Expression cũng có thể tham chiếu đến các phương thức có sẵn để thực thi. Tất nhiên là phương thức đó phải có các tham số đầu vào và kiểu trả về tương ứng với Lambda Expression.

Việc tham chiếu đến các phương thức có sẵn được thực hiện qua toán tử “::“. Hãy xem một ví dụ dưới đây.

Sử dụng Lambda expression để sort 1 arraylist

import java.util.ArrayList;
import java.util.List;

public class MethodReference {
	public static void main(String[] args) {
		// Initialize an array
		List arr = new ArrayList();
		arr.add(3);
		arr.add(4);
		arr.add(5);
		arr.add(1);
		arr.add(2);
		// Sort array using Lambda Expression
		arr.sort((x, y) -> {return (x  y) ? 1 : 0);});
		// Print out
		arr.forEach((x) -> System.out.println(x));
	}
}

Thay vì phải định nghĩa ra Lambda Expression, nếu chúng ta đã có sẵn 1 Comparator thì vẫn có thể dùng lại được.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class MethodReference {
	public static void main(String[] args) {
		// Initialize an array
		List arr = new ArrayList();
		arr.add(3);
		arr.add(4);
		arr.add(5);
		arr.add(1);
		arr.add(2);
		// Sort array using Lambda Expression
		arr.sort(MethodReference::compare);
		// Print out
		arr.forEach((x) -> System.out.println(x));
	}
	// Existing comparison logic
	// This method has the same signature
	// with method compare in Comparator class which is a Functional Interface
	// and is applied as a parameter in List.sort method
	public static int compare(Integer x, Integer y) {
		return (x  y) ? 1 : 0);
	}
}

Lambda Expression đã tham chiếu đến hàm static compare trong lớp MethodReference để định nghĩa logic sort qua câu lệnh

MethodReference::compare

Hàm compare này có cú pháp giống như hàm compare trong interface Comparator. Comparator là 1 Functional Interface vì nó chỉ có duy nhất 1 hàm abstract là compare. Phương thức List.sort nhận vào 1 đối số là Lambda Expression đại diện cho interface Comparator. Vì vậy bất kỳ phương thức nào có cùng cú pháp giống như hàm compare đều được chấp nhận.

Ở ví dụ trên chúng ta dùng static method reference. Trong Java 8 hỗ trợ 4 kiểu reference là:

  • Reference to a static method
  • Reference to an instance method of a particular object
  • Reference to an instance method of an arbitrary object of a particular type
  • Reference to a constructor

Reference to an instance method of a particular object

Ví dụ đơn giản nhất cho kiểu reference này như sau:

package com.edward.tutorial.java8.test;

import java.util.ArrayList;
import java.util.List;

public class Test {

	/**
	 * @param Main method
	 */
	public static void main(String[] args) {
		// Initialize array
		List arr = new ArrayList();
		arr.add(1);
		arr.add(4);
		arr.add(3);
		arr.add(2);

		// Sort it
		arr.sort((x, y) -> (x > y) ? 1 : (x < y) ? -1 : 0);

		// Check it
		arr.forEach(System.out::println);

	}

}

Ở trên, đoạn in ra các phần tử trong array, chúng ta truyền vào 1 Lambda exrepssion tham chiếu đến method println của object out trong lớp System. Nếu viết theo cú pháp Lambda thông thường sẽ như sau:

arr.forEach((x) -> System.out.println(x));

Nghĩa là Lambda expression nhận vào 1 tham số x, và hàm println cũng nhận vào 1 tham số x, vậy có thể thay biểu thức Lambda bằng hàm println đó.

Reference to an instance method of an arbitrary object of a particular type

Hãy xem ví dụ dưới:

String[] stringArray = { "Barbara", "James", "Mary", "John",
	    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, (x, y) -> { return x.compareToIgnoreCase(y);});

Biến số x ở đây sẽ gọi hàm compareToIgnoreCase, vì vậy chúng ta có thể giản lược x và thay thế bằng method reference như sau:

Arrays.sort(stringArray, String::compareToIgnoreCase);

Reference to a constructor

Tôi sử dụng lại ví dụ trong bài viết Optional kỳ trước. Các bạn có thể đọc bài viết tại đây

Việc thực hiện hàm Optional.orElseThrow được thực hiện như sau:

// Edward
try {
	System.out.println(name.orElseThrow(() -> {return new Exception();})); // Lambda expression
} catch (Exception e) {
	e.printStackTrace();
}
// Cannot get object from emptyOptional
try {
	// It will throw Exception
	System.out.println(emptyOptional.orElseThrow(Exception::new)); // Here is also lambda expression, both are the same
} catch (Exception e) {
	// Catch it
	System.out.println("Cannot get object from emptyOptional");
	e.printStackTrace();
}

Các bạn chỉ cần đến phần tham số truyền vào trong hàm orElseThrow

orElseThrow(() -> {return new Exception();})
orElseThrow(Exception::new)

Biểu thức Lambda không nhận vào đối số nào và trả về 1 đối tượng Expcetion. Điều đó tương đồng với hàm new để khởi tạo 1 đối tượng Exception. Nên ta có thể viết theo cả 2 cách trên.

Kết thúc bài giới thiệu về Method Reference 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 Core Overview

http://www.edwardthienhoang.wordpress.com

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 Core Overview

http://www.edwardthienhoang.wordpress.com

Java

Java 8 Tutorial: Functional Interface

Java 8 Tutorial: Functional Interface

Trong Java, xuất hiện một khái niệm mới là SAM (Single Abstract Method) hay còn gọi là Functional Interface. Functional Interface thực chất là 1 Interface với duy nhất 1 abstract method (method không có phần body). Có thể các bạn sẽ tự hỏi Interface thì làm gì có các method được implement. Điều đó chỉ đúng với các phiên bản Java 7 trở về. Trong Java 8, 1 Interface có thể định nghĩa các default method (phương thức mặc định). Chúng ta sẽ tìm hiểu về Default Method trong phần sau.

@FunctionalInterface

Nếu một interface được đánh dấu với @FunctionalInterface annotation thì nó phải có 1 và duy nhất 1 abstract function, ngược lại nếu không tồn tại 1 abstract function hoặc có nhiều hơn 1 thì trình biên dịch sẽ báo lỗi.

@FunctionalInterface
interface IFoo {
	void foo1();
	void foo2();
}

java: Unexpected @FunctionalInterface annotation
IFoo is not a functional interface
multiple non-overriding abstract methods found in interface IFoo

Nhưng interface dưới đây lại hợp lệ

@FunctionalInterface
interface IFoo {
	void foo1();
	default void foo2(); // Default method
}

Trong trường hợp 1 interface chỉ có một abstract method nhưng không được đánh dấu bằng @FunctionalInterface annotation thì vẫn được hiểu là 1 functional interface. Ngược lại, nó sẽ là 1 interface bình thường.

Một số Functional Interface trong Java 8

Trong Java đã tồn tại rất nhiều Interface chỉ có một method như: Runnable, Comparator.. Trong Java 8 có một package mới là java.util.function, package này chứa rất nhiều functional interface, các interface này sẽ được dùng trong rất nhiều API khác trong Java 8. Ở đây tôi sẽ liệt kê ra 1 số FI nổi bật, chi tiết sẽ được đề cập trong các bài viết sau.

Consumer<T>: Xử lý dựa trên một tham số đầu vào có kiểu T và không có kiểu trả về
Supplier<T>: Không nhận vào tham số nào, xử lý và trả về một đối tượng có kiểu T
Predicate<T>: Xử lý dựa trên một tham số đầu vào có kiểu T và không có kiểu trả về giá trị boolean
Function<T, R>: Xử lý dựa trên một tham số đầu vào có kiểu T và trả về một đối tượng có kiểu R

Kết thúc bài giới thiệu về Functional Interface 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 Core Overview

http://www.edwardthienhoang.wordpress.com

Java

Java 8 tutorial: Cơ bản về Lambda Expression

Java 8 tutorial: Cơ bản về Lambda Expression

Cuối cùng thì Lambda Expression đã xuất hiện trong phiên bản J2SE 8. Nếu đã làm qua các ngôn ngữ khác như: C#, Ruby, các bạn có thể đã biết được Lambda Expression là gì. Trong Java, xuất hiện một khái niệm mới là SAM (Single Abstract Method) hay còn gọi là Functional Interface. Functional Interface thực chất là 1 Interface với duy nhất 1 abstract method (method không có phần body). Các bạn có thể đọc bài viết Functional Interface ở đây. Vậy có thể hiểu Lambda Expression là một cách viết ngắn gọn để implement phần body cho các Functional Interface. Để tạo một lambda expression, bạn chỉ định đầu vào parameters ở phía bên trái của operator ->, và bạn viết các câu lệnh ở phía bên kia.

Trong Java đã tồn tại rất nhiều Interface chỉ có một method như: Runnable, Comparator.. Vậy ta cũng có thể gọi đó là các Functional Interface. Hãy bắt đầu viết một ví dụ đơn giản đầu tiên với những cái sẵn có, sắp xếp 1 ArrayList dùng Comparator.

List arr = new ArrayList();
arr.add(5);
arr.add(4);
arr.add(1);
arr.add(3);

Hãy tạo 1 đối tượng Comparator theo cách bình thường:

Comparator cmp1 = new Comparator() {
	@Override
	public int compare(Integer x, Integer y) {
		return (x  y) ? 1 : 0);
	}
};

Việc sắp xếp ArrayList được thực hiện như sau:

arr.sort(cmp);

Bây giờ hãy thử dùng Lambda Expression để tạo 1 đối tượng Comparator

Comparator cmp2 = (x, y) -> {
	return (x  y) ? 1 : 0);
};

Việc sắp xếp ArrayList cũng tương tự và cho ra kết quả giống nhau

arr.sort(cmp);

Thậm chí bạn có thể viết như sau:

arr.sort((x, y) -> {return (x  y) ? 1 : 0);});

Từ ví dụ trên có thể suy ra

arr.sort((x, y) -> {return (x  y) ? 1 : 0);});

tương đương với

Comparator

Vậy có thể kết luận Lambda Expression chính là 1 Functional Interface

Cùng viết thêm 1 ví dụ không sử dụng build-in class của Java

Đầu tiên, định nghĩa ra 1 Functional Interface, interface này sẽ nhận vào 2 tham số kiểu int và trả về 1 giá trị int

package com.edward.tutorial.java8.lambda;

public interface CalculatorCommand {
	public int doCalculate(int x, int y);
}

Tiếp theo viết một lớp có phương thức nhận vào interface ở trên để thực thi:

package com.edward.tutorial.java8.lambda;

public class Calculator {
	public int calculate(int x, int y, CalculatorCommand inCommand) {
		return inCommand.doCalculate(x, y);
	}
}

Hàm Calculator.calculate sẽ trả về kết quả tùy vào việc Client hiện thực hàm CalculatorCommand.doCalculate như thế nào.

Viết 1 lớp để test:

package com.edward.tutorial.java8.lambda;

public class Lambda1 {
	public static void main(String[] args) {
		Calculator cal = new Calculator();
		int result = cal.calculate(10, 20, (x, y) -> {return x * y;});
		// 200
		System.out.println(result);
	}
}

Bây giờ thì việc mapping để hiểu được Lambda Expression hoạt động như thế nào thật đơn giản.

(Còn tiếp)

Đến đây các bạn đã có cái nhìn đầu tiên về Lambda expression. 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 Core Overview

http://www.edwardthienhoang.wordpress.com