Cú pháp và các tính năng mới trong Java 8
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

TÀI LIỆU ĐANG ĐƯỢC CẬP NHẬP 60%

1- Giới thiệu

Từ khi Java ra đời và sự nâng cấp về mặt cú pháp và các tính năng, có vài dấu mốc thực sự quan trọng:
  • Java 1.0: Bắt đầu một ngôn ngữ lập trình.
  • Java 1.1, 1.2, 1.3, 1.4 về mặt cú pháp và tính năng không có nhiều thay đổi lớn.
  • Java 1.5 (Hay còn gọi là Java 5) đã có những thay đổi lớn, với việc đưa vào một vài khái niệm.
    • Generic
    • Autoboxing/Unboxing
    • Nâng cấp tính năng cho vòng lặp for ("foreach").
    • Các kiểu liệt kê có tính an toàn (Type-safe enumerations).
    • Varargs
    • Nhập khẩu tĩnh (Static import)
    • Metadata
  • Java 6,7 không có nhiều thay đổi lớn về mặt ngôn ngữ.
  • Java 8 có một sự thay đổi lớn về mặt ngôn ngữ. Với việc đưa vào một vài khái niệm và tính năng:
    • Phương thức mặc định cho interface (Default interface methods)
    • Biểu thức Lambda (Lambda expressions)
    • Method references
    • Annotation có thể lặp (Repeatable annotations)
    • Stream
Trong tài liệu này tôi sẽ giới thiệu với các bạn về các tính năng và cú pháp của Java 8.

2- Phương thức mặc định cho Interface

Java 8 cho phép bạn thêm một method không trìu tượng vào interface bằng cách sử dụng từ khóa default. Các method này được hiểu như các phương thức mở rộng. Đây là ví dụ đầu tiên của bạn:
  • Formula.java
package org.o7planning.tutorial.j8.itf;

public interface Formula {

   // Khai báo một method trìu tượng.
   double calculate(int a);

   // Khai báo một method không trìu tượng.
   // Sử dụng từ khóa default.
   // (Hàm tính căn bậc 2 của một số)
   default double sqrt(int a) {
       return Math.sqrt(a);
   }

}
Và class FormulaImpl thi hành interface Formula.
  • FormulaImpl.java
package org.o7planning.tutorial.j8.itf;

// Class thi hành Interface Formula
public class FormulaImpl implements Formula {

 // Chỉ cần thi hành method trìu tượng của Formula.
 @Override
 public double calculate(int a) {
     return a*a - a;
 }

}
  • FormulaTest.java
package org.o7planning.tutorial.j8.itf;

public class FormulaTest {

    public static void main(String[] args) {
        Formula formula = new FormulaImpl();

        // ==> 5
        double value1 = formula.sqrt(25);
        System.out.println("Value1 = " + value1);

        // ==> 600
        double value2 = formula.calculate(25);
        System.out.println("Value2 = " + value2);

    }

}

3- Interface tính năng (Functional Interface)

Java 8 coi các Interface có duy nhất một method trìu tượng là các Functional Interface. Bạn có thể sử dụng annotation @FunctionalInterface để đánh dấu một interface của bạn là Functional Interface, điều này không bắt buộc, tuy nhiên trình biên dịch của Java sẽ thông báo lỗi cho bạn nếu vô tình thêm một method trìu tượng khác nữa vào interface có đánh dấu bởi annotation này.
Dưới đây là một số ví dụ thực hành với @FunctionalInterface:
Ví dụ dưới đây là một FunctionalInterface hợp lệ vì có duy nhất một method trìu tượng.
package org.o7planning.tutorial.j8.funcitf;

@FunctionalInterface
public interface Foo {

   void something();

   default void defaultMethod() {
       System.out.println("..");
   }
}
Không hợp lệ:
Hợp lệ:
Không hợp lệ:
Hợp lệ:

4- Biểu thức Lambda

Trước hết chúng ta xem lại cách Java phiên bản trước 8 sắp xếp một tập hợp có sẵn.

Về so sánh và sắp xếp trong Java bạn có thể xem chi tiết hơn tại:

  • SortBefore8Example.java
package org.o7planning.tutorial.j8.lambda;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class SortBefore8Example {

   public static void main(String[] args) {

       // Một danh sách các loại quả.
       List<String> fruits = Arrays.asList("Grapefruit", "Apple", "Durian",
               "Cherry");

       // Sử dụng method tiện ích của Collections để sắp xếp lại tập hợp.
       // Cung cấp một bộ so sách (Comparator).
       Collections.sort(fruits, new Comparator<String>() {

           @Override
           public int compare(String o1, String o2) {
               return o1.compareTo(o2);
           }

       });

       for (String fruit : fruits) {
           System.out.println(fruit);
       }

   }
}
Kết quả khi chạy ví dụ trên:
Java 8 coi các interface chỉ có duy nhất một method là các interface tính năng ( Functional Interface). Và đó ý tưởng để khi thi hành interface này bạn chỉ cần viết method triển khai method trìu tượng duy nhất đó. Comparator là một interface có duy nhất một method, và nó là một Functional Interface. Bạn có thể viết lại ví dụ trên với cú pháp Lambda của Java 8:
  • SortJava8Example.java
package org.o7planning.tutorial.j8.lambda;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class SortJava8Example {

  public static void main(String[] args) {

      // Một danh sách các loại quả.
      List<String> fruits = Arrays.asList("Grapefruit", "Apple", "Durian",
              "Cherry");

      // Sử dụng method tiện ích của Collections để sắp xếp lại tập hợp.
      // Cung cấp một bộ so sánh vào tham số thứ 2 của method.
      // Vì Comparator là interface có duy nhất 1 method.
      // Có thể viết ngắn gọn với biểu thức Lambda.
      // Không cần viết tên interface, không cần viết tên method.
      Collections.sort(fruits, (String o1, String o2) -> {
          return o1.compareTo(o2);
      });

      for (String fruit : fruits) {
          System.out.println(fruit);
      }

  }
}
Trong một khối lệnh nếu chỉ có một lệnh duy nhất, bạn có thể bỏ đi { }, khi đó đoạn code trên bạn có thể viết ngắn hơn:

Collections.sort(fruits, (String o1, String o2) -> o1.compareTo(o2)  );
 
Thậm chí bộ Compiler của Java đủ thông minh để nhận thức được kiểu phần tử trong tập hợp của bạn cần sắp xếp là kiểu gì, trong ví dụ này là kiểu String, và vì vậy bộ so sánh (Comparator) chắc chắn là so sánh các kiểu dữ liệu String. Bạn có thể viết ngắn gọn hơn nữa.

Collections.sort(fruits, (o1, o2) -> o1.compareTo(o2));

Ví dụ khác với biểu thức Lambda.

  • Converter.java
package org.o7planning.tutorial.j8.lambda;

@FunctionalInterface
public interface Converter<F, T> {
    
    T convert(F from);
    
}
Sử dụng interface Converter theo cách của Java trước phiên bản 8 (Không sử dụng biểu thức Lambda).
  • ConverterBefore8Example.java
package org.o7planning.tutorial.j8.lambda;

public class ConverterBefore8Example {

   public static void main(String[] args) {

       // Khởi tạo một đối tượng Converter.
       Converter<String, Integer> converter = new Converter<String, Integer>() {

           @Override
           public Integer convert(String from) {
               return Integer.parseInt(from);
           }

       };

       // ==> 100
       Integer value = converter.convert("0100");

       System.out.println("Value = " + value);
   }
}
Sử dụng biểu thức Lambda của Java 8:
  • ConveterJava8Example.java
package org.o7planning.tutorial.j8.lambda;

public class ConveterJava8Example {

  public static void main(String[] args) {

      // Converter là một Functional Interface
      // Sử dụng cú pháp Java 8 (Lambda)
      // Trong trường hợp khởi tạo đối tượng từ FunctionalInterface.
      Converter<String, Integer> converter1 = (String from) -> {
          return Integer.parseInt(from);
      };

      // ==> 100
      Integer value1 = converter1.convert("0100");

      System.out.println("Value1 = " + value1);

      // Hoặc đơn giản hơn:
      Converter<String, Integer> converter2 = (from) -> Integer
              .parseInt(from);

      // ==> 200
      Integer value2 = converter2.convert("00200");

      System.out.println("Value2 = " + value2);
     
      // Nếu method chỉ có một tham số, có thể bỏ qua ().
      Converter<String, Integer> converter3 = from -> Integer
              .parseInt(from);

      // ==> 300
      Integer value3 = converter3.convert("00300");

      System.out.println("Value3 = " + value3);
     
  }

}

5- Functional Interface API

Java 8 xây dựng sẵn một số lượng khá lớn các Functional Interface, chúng nằm trong package java.util.function, tại đây tôi sẽ hướng dẫn bạn sử dụng một số Interface đó, để hiểu hơn về biểu thức Lambda và sự tiện dụng của chúng.

5.1- java.util.function.Consumer

Consumer là một Functional interface xây dựng sẵn của Java 8, nó có một method trìu tượng duy nhất chấp nhận một tham số đầu vào, và method này không trả về gì cả.
  • Consumer.java
package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {

   // Method chấp nhận một tham số đầu vào
   // và không trả về gì cả.
   void accept(T t);

}
Sử dụng method List.forEach(Consumer):
// java.util.List extends java.util.Collection  (extends Iterable)

// Interface java.util.Iterable:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
   for (T t : this) {
       action.accept(t);
   }
}
  • ConsumerExample.java
package org.o7planning.tutorial.j8.api;

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class ConsumerExample {

   // Sử dụng method List.forEach(Consumer) theo cách Java < 8.
   // In ra danh sách các phân tử trong List.
   public static void beforeJ8() {
       List<String> list = Arrays.asList("a", "b", "c", "a1", "a2");

       list.forEach(new Consumer<String>() {

           @Override
           public void accept(String t) {
               System.out.println(t);
           }

       });
   }

   // Sử dụng method List.forEach(Consumer) theo cách Java 8.
   // Với việc sử dụng biểu thức Lambda.
   public static void java8Consumer() {
       List<String> list = Arrays.asList("a", "b", "c", "a1", "a2");

       list.forEach((String t) -> {
           System.out.println(t);
       });
   }

   // Sử dụng method List.forEach(Consumer) theo cách Java 8.
   // Với việc sử dụng biểu thức Lambda.
   // (Đơn giản hơn)
   public static void java8ConsumerMoreSimple() {
       List<String> list = Arrays.asList("a", "b", "c", "a1", "a2");

       list.forEach((String t) -> System.out.println(t));
   }
   
}

5.2- java.util.function.Predicate

Predicate là một Functional interface xây dựng sẵn của Java 8, nó có một method trìu tượng duy nhất chấp nhận một tham số đầu vào, và method trả về giá trị boolean (true/false). Method này dùng để đánh giá tham số đầu vào phù hợp với một logic gì đó hay không.
  • Predicate.java
package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

   // Đánh giá một tham số đầu vào và trả về true hoặc false.
   boolean test(T t);

}
Ví dụ sau đây sẽ lọc một danh sách các tự nhiên và in ra danh sách các số lẻ, sử dụng Predicate theo cách Java8, và trước Java8.
  • PredicateExample.java
package org.o7planning.tutorial.j8.api;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class PredicateExample {

   // Sử dụng method Stream.filter(Predicate<T>) theo cách Java < 8.
   // Lọc một danh sách các số nguyên và in ra các số lẻ.
   public static void beforeJ8() {
       List<Integer> list = Arrays.asList(1, 4, 5, 1, 7, 8);

       // Luồng chứa các phần tử của danh sách trên.
       Stream<Integer> stream = list.stream();

       // Một luồng mới chỉ chứa các số lẻ
       Stream<Integer> stream2 = stream.filter(new Predicate<Integer>() {

           @Override
           public boolean test(Integer t) {
               return t % 2 == 1;
           }
       });
   }

   // Sử dụng method Stream.filter(Predicate<T>) theo cách Java < 8.
   // Lọc một danh sách các số nguyên và in ra các số lẻ.
   // Với việc sử dụng biểu thức Lambda.
   public static void java8Predicate() {
       List<Integer> list = Arrays.asList(1, 4, 5, 1, 7, 8);

       // Luồng chứa các phần tử của danh sách trên.
       Stream<Integer> stream = list.stream();

       // Một luồng mới chỉ chứa các số lẻ
       Stream<Integer> stream2 = stream.filter(t -> {
           return t % 2 == 1;
       });

       // Stream.forEach(Consumer<T>)
       stream2.forEach(t -> System.out.println(t));
   }

   // Sử dụng method Stream.filter(Predicate<T>) theo cách Java < 8.
   // Lọc một danh sách các số nguyên và in ra các số lẻ.
   // Với việc sử dụng biểu thức Lambda.
   // Đơn giản và ngắn gọn hơn nữa.
   public static void java8ConsumerMoreSimple() {
       List<Integer> list = Arrays.asList(1, 4, 5, 1, 7, 8);

       // Luồng chứa các phần tử của danh sách trên.
       Stream<Integer> stream = list.stream();

       stream.filter(t -> t % 2 == 1).forEach(t -> System.out.println(t));
   }

}

5.3- java.util.function.Function

Function là một Functional interface xây dựng sẵn của Java 8, nó có một method trìu tượng duy nhất chấp nhận một tham số đầu vào, và method trả về một đối tượng khác.
  • Function.java
package java.util.function;

import java.util.Objects;


@FunctionalInterface
public interface Function<T, R> {

  // Method chấp nhận một tham số
  // Và trả về một giá trị.
  R apply(T t);

}
Ví dụ: Cho một danh sách các String, in ra các String  trong tập hợp dưới dạng chữ hoa.
  • FunctionExample.java
package org.o7planning.tutorial.j8.api;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;

public class FunctionExample {

   // Sử dụng method Stream.map(Function) theo cách Java < 8.
   // In ra danh sách các phân tử trong List.
   public static void beforeJ8() {
       List<String> list = Arrays.asList("a", "c", "B", "e", "g");

       // Luồng chứa các phần tử của danh sách.
       Stream<String> stream = list.stream();

       // Stream.map(Function):
       // <R> Stream<R> map(Function<? super T, ? extends R> mapper);

       // Trả về một Stream mới, với các phần tử được thay đổi đi.
       Stream<String> streamUpper = stream.map(new Function<String, String>() {

           @Override
           public String apply(String t) {
               return t == null ? null : t.toUpperCase();
           }

       });

       streamUpper.forEach(t -> System.out.println(t));
   }

   public static void java8Function() {
       List<String> list = Arrays.asList("a", "c", "B", "e", "g");

       // Luồng chứa các phần tử của danh sách.
       Stream<String> stream = list.stream();

       stream.map(t -> t == null ? null : t.toUpperCase()).forEach(
               t -> System.out.println(t));
   }

   public static void main(String[] args) {

       beforeJ8();

       java8Function();

   }
   
}

Một số Functional interface tương tự:

  • java.util.function.IntFunction<R>
  • java.util.function.DoubleFunction<R>
  • java.util.function.LongFunction<R>
@FunctionalInterface
public interface IntFunction<R> {
 
    R apply(int value);
}


@FunctionalInterface
public interface LongFunction<R> {
 
    R apply(long value);
}


@FunctionalInterface
public interface DoubleFunction<R> {
 
    R apply(double value);
}

5.4- java.util.function.Supplier

Supplier là một Functional interface xây dựng sẵn của Java 8, nó có một method trìu tượng duy nhất không tham số, và method trả về một đối tượng.
 
  • Supplier.java
package java.util.function;

@FunctionalInterface
public interface Supplier<T> {

    // Gets a result.
    T get();

}
  • SupplierExample.java
package org.o7planning.tutorial.j8.api;

import java.util.function.Supplier;

public class SupplierExample {

   // Một method với tham số là Supplier<String>.
   public static void display(Supplier<String> supp) {
       System.out.println(supp.get());
   }

   // Không sử dụng Lambda.
   public static void beforeJ8() {
       display(new Supplier<String>() {

           @Override
           public String get() {
               return "Hello";
           }
       });
       display(new Supplier<String>() {

           @Override
           public String get() {
               return "World";
           }
       });
   }

   // Sử dụng biểu thức Lambda.
   public static void java8Supplier() {
       display(() -> {
           return "Hello";
       });

       display(() -> {
           return "World";
       });
   }

   // Sử dụng biểu thức Lambda.
   // (Ngắn gọn hơn nữa).
   public static void java8SupplierShortest() {
       display(() -> "Hello");

       display(() -> "World");
   }

   public static void main(String[] args) {

   }

}

Một số Functional Interface tương tự:

  • java.util.function.BooleanSupplier
  • java.util.function.IntSupplier
  • java.util.function.DoubleSupplier
  • java.util.function.LongSupplier

6- Method reference

Đó là một tính năng có liên quan đến biểu thức Lambda. Nó cho phép bạn tham chiếu tới một method hoặc một constructor mà không cần thực thi chúng. Method references và Lambda là giống nhau vì cả hai yêu cầu một loại mục tiêu đó bao gồm một Interface chức năng (Functional Interface) tương thích.
Java 8 cho phép bạn truyền một tham chiếu của một method hoặc một constructor thông qua việc sử dụng từ khóa ::
Trước khi đi vào vấn đề hãy xem một ví dụ đơn giản.
MyFunction là một Functional Interface. Nó định nghĩa ra một method có 2 tham số int a và b, và trả về giá trị int.
  • MyFunction.java
package org.o7planning.tutorial.j8.mref;

@FunctionalInterface
public interface MyFunction {

   // Method này có 2 tham số a, b và trả về giá trị int.
   // Định nghĩa một method làm gì đó với a và b
   // và trả về một số int.
   public int doSomething(int a, int b);
   
}
MyMathUtils là class có 2 method tĩnh để tính tổng và tính hiệu 2 số int.
  • MyMathUtils.java
package org.o7planning.tutorial.j8.mref;

public class MyMathUtils {

   // Method này có 2 tham số a, b và trả về giá trị int.
   // Method này khác tên, nhưng có cấu trúc giống method trìu tượng
   // định nghĩa trong interface MyFunction (Functional Interface).
   public static int sum(int a, int b) {
       return a + b;
   }

   // Method này có 2 tham số a, b và trả về giá trị int.
   // Method này khác tên, nhưng có cấu trúc giống method trìu tượng
   // định nghĩa trong interface MyFunction (Functional Interface).
   public static int minus(int a, int b) {
       return a - b;
   }

}
  • MethodReferenceExample.java
package org.o7planning.tutorial.j8.mref;

public class MethodReferenceExample {

  // Tham số thứ 3 của method này là MyFunction (Một Functional Interface).
  // Khi sử dụng method này:
  // Bạn có thể truyền vào tham chiếu method giống cấu trúc method trìu tượng
  // định nghĩa trong MyFunction.
  public static int action(int a, int b, MyFunction func) {
      return func.doSomething(a, b);
  }

  public static void main(String[] args) {
      int a = 100;
      int b = 30;

      // Truyền vào tham chiếu method sum của class MyMathUtils
      // ==> 130
      int c = action(a, b, MyMathUtils::sum);

      System.out.println("c = " + c);

      // Truyền vào tham chiếu method minus của class MyMathUtils.
      // == 70
      int d = action(a, b, MyMathUtils::minus);

      System.out.println("d = " + d);

      // Truyền vào tham chiếu method subtractExact của class Math.
      // ==> 70
      int e = action(a, b, Math::subtractExact);

      System.out.println("e = " + e);

      // Truyền vào tham chiếu method min của class Math.
      // ==> 30
      int f = action(a, b, Math::min);

      System.out.println("f = " + f);
  }
}
Thông qua ví dụ ở trên bạn có thể thấy cách thức sử dụng từ khóa :: để truyền vào tham chiếu của một method. Nếu bạn gọi một method trong method đó có một tham số là Functional Interface, bạn có thể truyền vào một tham chiếu method có cấu trúc giống với cấu trúc method định nghĩa trong Functional interface.

7- Contructor reference

Bạn có thể sử dụng MyClass::new để tham chiếu tới một constructor. This is very similar to reference to to a static method.The difference between the two is, the constructor reference method name is new.