Reading Time: 9 minutes

Mặc dù Java 8 đã được phát hành từ lâu (tháng 3 năm 2014), rất nhiều dự án vẫn đang chạy nó vì đây là một bản phát hành lớn với nhiều tính năng mới.

Chúng ta hãy cùng điểm qua các tính năng của Java 8 với một vài ví dụ cụ thể để giúp bạn hiểu rõ chúng hơn.

Các tính năng của Java 8

Các tính năng nổi bật trong Java 8

1. Phương thức forEach() trong interface (giao diện) Iterable

Mỗi khi cần duyệt qua một Collection, chúng ta thường phải tạo một Iterator với mục đích chính là để duyệt qua các phần tử. Sau đó, ta triển khai logic nghiệp vụ (business logic) trong một vòng lặp cho từng phần tử của Collection. Nếu iterator không được sử dụng đúng cách, chúng ta có thể gặp phải lỗi ConcurrentModificationException.

Java 8 đã thêm phương thức forEach vào interface java.lang.Iterable để khi viết code chúng ta có thể tập trung vào business logic. Nó nhận một đối tượng java.util.function.Consumer làm đối số. Điều này giúp tách riêng business logic ra một nơi khác để có thể tái sử dụng.

Chúng ta hãy xem cách sử dụng forEach qua một ví dụ đơn giản.

package com.journaldev.java8.foreach;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

public class Java8ForEachExample {

 public static void main(String[] args) {
  
  //tạo Collection mẫu
  List<Integer> myList = new ArrayList<Integer>();
  for(int i=0; i<10; i++) myList.add(i);
  
  //duyệt với Iterator
  Iterator<Integer> it = myList.iterator();
  while(it.hasNext()){
   Integer i = it.next();
   System.out.println("Iterator Value::"+i);
  }
  
  //duyệt phương thức forEach của Iterable vơi lớp anonymous
  myList.forEach(new Consumer<Integer>() {

   public void accept(Integer t) {
    System.out.println("forEach anonymous class Value::"+t);
   }

  });
  
  //duyệt triển khai Consumer interface
  MyConsumer action = new MyConsumer();
  myList.forEach(action);
  
 }

}

//triển khai Consumer có thể được dùng lại
class MyConsumer implements Consumer<Integer>{

 public void accept(Integer t) {
  System.out.println("Consumer impl Value::"+t);
 }
}

Số dòng mã có thể tăng lên, nhưng phương thức forEach giúp tách biệt logic duyệt (iteration logic) và business logic. Nhờ đó nó nâng cao tính phân tách trách nhiệm (separation of concern) và làm cho code trở nên sạch hơn.

Tính năng của  Java 8

2. Phương thức default và static trong Interface

Nếu đọc kỹ chi tiết về phương thức forEach, bạn sẽ nhận thấy nó được định nghĩa trong interface Iterable. Tuy nhiên, chúng ta đều biết rằng các interface truyền thống không thể có phần thân phương thức (method body).

Kể từ Java 8, các interface đã được cải tiến để có thể chứa các phương thức có code triển khai. Ta có thể sử dụng từ khóa defaultstatic để tạo ra các interface với các phương thức này.

Ví dụ phần triển khai của phương thức forEach trong interface Iterable:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

Chúng ta biết rằng Java không hỗ trợ đa kế thừa (multiple inheritance) đối với class, bởi vì điều này sẽ dẫn đến Diamond Problem (vấn đề kim cương**)**. Vậy thì vấn đề này sẽ được xử lý như thế nào với các interface, khi mà giờ đây chúng đã trở nên tương tự như các abstract class?

Giải pháp được đưa ra là trình biên dịch (compiler) sẽ báo lỗi trong trường hợp này và chúng ta sẽ phải tự cung cấp phần code logic cụ thể trong class triển khai các interface đó.

package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface1 {

 void method1(String str);
 
 default void log(String str){
  System.out.println("I1 logging::"+str);
 }
 
 static void print(String str){
  System.out.println("Printing "+str);
 }
 
 //sẽ có lỗi biên dịch khi ta cố ghi đè phương thức Object
 //"A default method cannot override a method from java.lang.Object"
 
// default String toString(){
//  return "i1";
// }
 
}
package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface2 {

 void method2();
 
 default void log(String str){
  System.out.println("I2 logging::"+str);
 }

}

Lưu ý rằng cả hai interface này đều có một phương thức chung là log() với logic triển khai riêng.

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

 @Override
 public void method2() {
 }

 @Override
 public void method1(String str) {
 }

 //MyClass won't compile without having it's own log() implementation
 @Override
 public void log(String str){
  System.out.println("MyClass logging::"+str);
  Interface1.print("abc");
 }
 
}

Như bạn có thể thấy, Interface1 có phần triển khai phương thức static được sử dụng trong phần triển khai của phương thức MyClass.log(). Java 8 sử dụng rất nhiều phương thức defaultstatic trong Collection API. Các phương thức default được thêm vào để đảm bảo code của chúng ta vẫn duy trì tính tương thích ngược.

Nếu một class nào đó trong hệ thống phân cấp có một phương thức với cùng signature (chữ ký phương thức), thì các phương thức default trong interface sẽ bị bỏ qua (trở nên không liên quan).

Class Object là class cha cơ sở của tất cả các class. Do đó nếu chúng ta định nghĩa các phương thức default như equals(), hashCode() trong interface, chúng cũng sẽ bị bỏ qua. Vì lý do này, để đảm bảo tính rõ ràng, các interface không được phép định nghĩa các phương thức default trùng với các phương thức của class Object.

3. Giao diện hàm (functional interface) và biểu thức Lambda

Nếu bạn để ý đoạn mã interface ở trên, bạn sẽ thấy annotation @FunctionalInterface. Functional interface là một khái niệm mới được giới thiệu trong Java 8. Một interface chỉ có duy nhất một phương thức trừu tượng (abstract method) được gọi là functional interface. Chúng ta không bắt buộc phải sử dụng annotation @FunctionalInterface để đánh dấu một interface là functional interface.

Annotation @FunctionalInterface là một công cụ giúp tránh việc vô tình thêm các phương thức trừu tượng khác vào functional interface. Bạn có thể hình dung nó giống như annotation @Override, và nên được sử dụng. java.lang.Runnable với chỉ một phương thức trừu tượng duy nhất là run(), là một ví dụ điển hình của functional interface.

Một trong những lợi ích chính của functional interface là khả năng sử dụng các biểu thức lambda (lambda expression) để khởi tạo chúng.

Mặc dù chúng ta có thể khởi tạo một interface bằng anonymous class (lớp ẩn danh), cách này thường làm cho code trở nên khá cồng kềnh.

Runnable r = new Runnable(){
   @Override
   public void run() {
    System.out.println("My Runnable");
   }};

Do functional interface chỉ có một phương thức duy nhất, các biểu thức lambda có thể dễ dàng cung cấp phần triển khai cho phương thức đó. Chúng ta chỉ cần cung cấp các tham số (argument) của phương thức và business logic.

Ví dụ, ta có thể viết lại phần triển khai ở trên bằng biểu thức lambda như sau:

Runnable r1 = () -> {
   System.out.println("My Runnable");
  };

Nếu phần triển khai phương thức chỉ có một câu lệnh (statement), chúng ta thậm chí không cần dùng dấu ngoặc nhọn {}. Ví dụ, anonymous class Interface1 ở trên có thể được khởi tạo bằng lambda như sau:

Interface1 i1 = (s) -> System.out.println(s);
  
i1.method1("abc");

Như vậy, biểu thức lambda là một cách tiện lợi để tạo ra các instance của functional interface (thông qua anonymous class). Việc sử dụng biểu thức lambda không mang lại lợi ích nào lúc chạy (runtime). Bạn có thể sử dụng chúng một cách thận trọng nếu không ngại việc viết thêm một vài dòng code.

Một package mới là java.util.function đã được thêm vào với nhiều functional interface được thiết kế để làm type mục tiêu (target type) cho các biểu thức lambda và tham chiếu phương thức.

4. Java Stream API cho các thao tác dữ liệu hàng loạt trên Collection

Một API mới là java.util.stream (Stream API) đã được thêm vào Java 8 để thực hiện các thao tác kiểu filter/map/reduce trên collection. Nó cho phép chả hai kiểu chạy tuần tự (sequential) và song song (parallel).

Đây là một trong những tính năng hay nhất cho những ai làm việc rất nhiều với Collection và thường xuyên phải xử lý Big Data, nơi chúng ta cần lọc dữ liệu dựa trên các điều kiện nhất định.

Interface Collection đã được mở rộng với các phương thức default là stream()parallelStream() để lấy về đối tượng Stream phục vụ cho việc thực thi tuần tự hoặc song song.

Chúng ta hãy xem cách sử dụng chúng qua một ví dụ đơn giản:

package com.journaldev.java8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

 public static void main(String[] args) {
  
  List<Integer> myList = new ArrayList<>();
  for(int i=0; i<100; i++) myList.add(i);
  
  //stream tuần tự
  Stream<Integer> sequentialStream = myList.stream();
  
  //stream song song
  Stream<Integer> parallelStream = myList.parallelStream();
  
  //dùng lambda với Stream API cho ví dụ filter
  Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
  //dùng lambda trong forEach
  highNums.forEach(p -> System.out.println("High Nums parallel="+p));
  
  Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
  highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));

 }

}

Nếu bạn chạy đoạn code ví dụ ở trên, bạn sẽ nhận được output như sau:

High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99

Lưu ý rằng kết quả của việc xử lý song song không được sắp xếp theo thứ tự. Do đó, tính năng này sẽ rất hữu ích khi bạn làm việc với các collection có kích thước lớn.

5. Java Time API

Việc xử lý Date (Ngày), Time (Giờ), và Time Zone (Múi giờ) trong Java trước đây luôn gặp nhiều trở ngại. Java thiếu API và cách tiếp cận chuẩn cho các vấn đề này. Một trong những bổ sung đáng giá của Java 8 là package java.time để giúp chuẩn hóa và đơn giản hóa quy trình làm việc với thời gian trong Java.

Chỉ cần nhìn qua cấu trúc các package của Java Time API, bạn sẽ thấy rằng nó sẽ rất dễ sử dụng. API này bao gồm các package con như java.time.format (cung cấp các class để hiển thị và chuyển đổi ngày giờ) và java.time.zone (hỗ trợ cho các múi giờ và quy tắc của chúng).

Time API mới ưu tiên sử dụng enum thay vì các hằng số kiểu integer để biểu diễn tháng và các ngày trong tuần. Một trong những class hữu ích là DateTimeFormatter. Nó có thể chuyển đổi các đối tượng DateTime sang dạng String.

6. Các cải tiến trong Collection API

Chúng ta đã tìm hiểu về phương thức forEach() và Stream API dành cho collection. Một số phương thức mới khác được thêm vào Collection API bao gồm:

  • Phương thức default Iterator.forEachRemaining(Consumer action): thực hiện một action (hành động) đã cho đối với các phần tử còn lại cho đến khi tất cả chúng đã được xử lý hết hoặc action đó gây ra lỗi.
  • Phương thức default Collection.removeIf(Predicate filter): xóa tất cả các phần tử của collection hiện tại thỏa mãn một điều kiện cho trước.
  • Phương thức Collection.spliterator(): trả về một instance Spliterator có thể được dùng để duyệt qua các phần tử một cách tuần tự hoặc song song.
  • Các phương thức mới trong Map như compute(), putIfAbsent(), forEach()replace().
  • Cải thiện hiệu năng của class HashMap trong trường hợp xảy ra xung đột khóa.

7. Các cải tiến trong Concurrency API

Một số cải tiến quan trọng cho Concurrency API bao gồm:

  • Các phương thức mới trong ConcurrentHashMap như compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce()search().
  • CompletableFuture: có thể được hoàn thành một cách tường minh (thiết lập giá trị và trạng thái của nó).
  • Phương thức Executors.newWorkStealingPool(): tạo ra một nhóm luồng (thread pool) kiểu work-stealing (cơ chế đánh cắp công việc), sử dụng tất cả các processor hiện có làm mức độ song song (parallelism level) mục tiêu.

8. Các cải tiến trong Java IO

Một số cải tiến đáng chú ý về Java IO (Input/Output) bao gồm:

  • Files.list(Path dir): trả về một Stream được khởi tạo lười với các phần tử là các phần tử trong thư mục dir.
  • Files.lines(Path path): đọc tất cả các dòng từ một file và trả về dưới dạng một Stream<String>.
  • Files.find(Path start, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption... options): trả về một Stream<Path> được khởi tạo lười bằng cách tìm kiếm file trong một cây thư mục bắt đầu từ start.
  • BufferedReader.lines(): trả về một Stream<String>, với các phần tử là các dòng được đọc từ BufferedReader đó.

Các cải tiến API Core khác trong Java 8

Một số cải tiến API khác trong Java 8 Core bao gồm:

  1. Phương thức static ThreadLocal.withInitial(Supplier<S> supplier) giúp tạo instance ThreadLocal một cách dễ dàng.
  2. Interface Comparator được mở rộng với nhiều phương thức default và static mới, hỗ trợ việc sắp xếp theo thứ tự tự nhiên (natural ordering), thứ tự đảo ngược (reverse order), v.v.
  3. Các phương thức min(), max()sum() được thêm vào các lớp wrapper Integer, Long, và Double.
  4. Các phương thức logicalAnd(), logicalOr(), và logicalXor() được thêm vào class Boolean.
  5. Phương thức ZipFile.stream(): trả về một Stream có thứ tự duyệt qua các entry trong file ZIP. Các entry này xuất hiện trong Stream theo đúng thứ tự chúng có trong thư mục trung tâm của file ZIP.
  6. Một vài phương thức tiện ích mới trong class Math.
  7. Lệnh jjs được thêm vào để gọi Nashorn Engine.
  8. Lệnh jdeps được thêm vào để phân tích các file class.
  9. JDBC-ODBC Bridge đã bị loại bỏ.
  10. Vùng nhớ PermGen đã bị loại bỏ.

Tổng kết

Trên đây là cái nhìn tổng quan về các tính năng quan trọng của Java 8 kèm theo các ví dụ minh họa. Nếu bạn nhận thấy vẫn còn những khía cạnh quan trọng chưa được đề cập, đừng ngần ngại chia sẻ trong phần bình luận bên dưới. Chúng tôi rất mong nhận được góp ý từ bạn!

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.