Java những điều có thể bạn đã biết: Có gì mới trong Java 8 (Phần 1)
Java
77
java 8
5
stream
1
stream api
2
White

Hoàng Nguyễn viết ngày 17/12/2015

Trong các bài trước tôi đã từng đề cập tới những cải tiến của Java 8 như Java 8 Lambda Expressions hay Java 8 Repeating Annotations.

Hôm nay tôi sẽ giới thiệu đến các bạn về Stream API và Collectors trong Java 8. Là một trong những tính năng mới và cực kì được các developer yêu thích.

Bắt đầu nào.

Cùng xét ngữ cảnh chúng ta có một list như sau.

List<Peron> people = new ArrayList<>();

Bài toán chúng ta có là tính toán độ tuổi trung bình của những người có độ tuổi lớn hơn 20 trong list, cách thông thường các bạn sẽ nghĩ là duyệt hết tất cả các phần tử của list rồi so sánh tuổi của từng người, nếu lớn hơn 20 cộng thêm vào tổng tuổi, và tăng số lượng người lớn hơn 20 tuổi lên, cuối cùng chia ra để tính tuổi trung bình đúng không.

Dưới mindset của Java 8, để giải quyết bài toán này chúng ta sẽ làm chúng ta sử dụng phương pháp Map/Filter/Reduce.

Mapping, chúng ta sẽ làm việc là chuyển đổi dữ liệu từ List thành List chỉ chứa thông tin chúng ta cần xử lý đó là độ tuổi.

Filtering, lọc ra những phần tử pù hợp với điều kiện, cụ thể ở đây là độ tuổi lớn hơn 20. Sau bước này chúng ta sẽ có được một list với các phần tử có tuổi lớn hơn 20.

Reduction, sử dụng các phép tính toán để kết xuất ra dữ liệu cần thiết.

Để thực hiện được concept này, Java 8 đã đưa ra Stream API, vậy Stream API là gì?

Đầu tiên, về mặt kĩ thuật Stream API là một Java Inteface.

public interface Stream<T> extends BaseStream<T, Stream<T>> {
  // more code here
}

Như các bạn thấy Stream có nghĩa là chúng ta có thể có Steam, Stream, Stream, blabla…

Stream trông thì có vẻ giống như Collection nhưng nó lại là một concept hoàn toàn khác.

Stream dùng để xử lý dữ liệu một cách hiệu quả. Như thế nào thì gọi là hiệu quả? Hiệu quả ở đây gồm hai thứ. Thứ nhất, là việc xử lý song song. Thứ hai, là việc xử lý pipeline.

Vậy tại sao Java 8 không thay đổi Collection API mà lại tạo thêm Stream API? Đơn giản vì chúng ta, hay những người làm ra Java không muốn thay đổi những gì chúng ta đang làm bằng Collection API, nhưng vẫn cần một phương thức mới để xử lý dữ liệu hiệu quả hơn.

Làm thế nào để có một đối tượng Stream?

Để làm việc này chúng ta có khá nhiều cách. Chẳng hạn như.

Stream<Person> peopleStream = people.stream();

Sau khi có được Stream, thì chúng ta có thể khai báo các operator.

Đầu tiên, chúng ta sẽ xem xét operator forEach(), cũng như cách để consume stream.

stream.forEach(p -> System.out.println(p.name));

Hoặc có thể khai báo một Consumer để xử lý stream, Consumer cũng là một Java Interface, cụ thể nó là một Functional Interface, các bạn có thể tham khảo bài Java 8 Lambda Expressions để biết về Functional Interface

@FunctionalInterface
public Consumer<T> {

  void accept(T t);
}

// Hoặc khai báo bằng lamba
Consumer<T> consumer = p -> System.out.println(p);

// Hoặc dùng method reference
Consumer<T> consumer = System.out::println;

// Hoặc phức tạp hơn
@FunctionalInterface
public Consumer<T> {

  void accept(T t);

  default Consumer<T> andThen(Consumer<? super T> after) {
    Object.requireNonNull(after);
    return (T t) -> {
      accept(t);
      after.accept(t);
    }
  }
}

Ngoài ra cũng có thể tạo một chuỗi các Consumer như sau.

List<Person> people = new ArrayList<>();

Consumer<Person> c1 = people::add;
Consumer<Person> c2 = System.out::println;

Consumer<Person> c3 = c1.andThen(c2);

Tuy nhiên các bạn lưu ý, với stream thì chúng chỉ chỉ có thể truyền vào một Consumer, không thể truyền một chuỗi Consumer.

Tiếp theo, là cách filter dữ liệu với stream.

List<Person> people = new ArrayList<>();

Stream<Person> stream = people.stream();
Stream<Person> filtered = stream.filter(person -> person.getAge() > 20);

Phía trên là cách implement đơn giản nhất, ngoài ra Java 8 cung cấp cho chúng ta một Functional Interface khác để thực hiện việc filter là Predicate.

@FunctionalInterface
public Predicate<T> {

  void test(T t);

  // default method

  default Predicate<T> and(Predicate<? super T> other) {
    // more code here
  }

  default Predicate<T> or(Predicate<? super T> other) {
    // more code here
  }

  default Predicate<T> negate() {
    // more code here
  }

  // static

  static <T> Predicate<T> isEqual(Object o) {
    // more code here
  }

}

Dễ dàng thấy chúng ta có cách sử dụng kết hợp các Predicate như.

Predicate<Person> p1 = p -> p.getAge() > 20;
Predicate<Person> p2 = p -> p.getAge() < 30;
Predicate<Person> p3 = p -> p.getGender().equals("male");

Predicate<Person> p = p1.and(p2).and(p3);

Trong Predicate có hàm static là isEqual(), sử dụng như sau.

Predicate<String> p = Predicate.isEqual("two");

Stream<String> stream = Stream.of("one", "two", "three");
Stream<String> filtered = stream.filter(p);

Kết quả của filtered sẽ là Stream chứa duy nhất chuỗi “two”.

Chắc các bạn sẽ thắc mắc vậy sau khi filter thì dữ liệu sẽ trả về Stream, vậy trong stream đó là cái gì?

Nó chẳng là gì cả, đặc tính của Stream là không giữ bất cứ dữ liệu nào, dòng code stream.filter() chỉ là một declaration, không có bất cứ dữ liệu nào được xử lý tại dòng code này. Đây là lazy call, có nghĩa là khi tôi gọi method đó, compiler sẽ hiểu là một declaration chứ không thực hiện ngay tức thì. Lazy call cũng là cách gọi chung của tất cả method của Stream.

Sẵn tiện, chúng ta sẽ ngó qua method peek(). Đây là một method tương tự như forEach() nhưng thay vì không trả về gì cả như forEach() thì nó trả về một Stream như filter().

Cùng xem xét đoạn code dưới đây.

List<Person> result = new ArrayList<>();
List<Person> people = new ArrayList<>();

people.stream()
      .peek(System.out::println)
      .filter(p -> p.getAge() > 20)
      .peek(result::add);

Thử chạy các bạn sẽ thấy chẳng có gì được in ra màn hình cũng như chẳng có gì được add vào result. Bời vì một operation trả về Stream được coi là một hoạt động trung gian (Intermediary Operation). Và cũng chỉ như một declaration, chẳng có thứ gì được xử lý cho đến khi Final Operation, ở đây là forEach() được gọi, nó sẽ kích hoạt quá trình xử lý Stream. Thay đổi đoạn code như sau, và bạn sẽ thấy code của mình xuất ra màn hình list person cũng như thêm vào list result những person được đã được filter.

people.stream()
      .peek(System.out::println)
      .filter(p -> p.getAge() > 20)
      .forEach(result::add);

Its cool huh?

Tổng kết

Chúng ta đã tìm hiểu về Stream, thế nào là intermediary và final operation, về các API như forEach(Consumer), peek(Consumer), filter(Predicate). Trong bài kế tiếp chúng ta sẽ tìm hiểu tiếp về Mapping cũng như Reducing thông qua Stream API.

Tạm biệt và hẹn gặp lại.

Bài gốc: https://codeaholicguy.wordpress.com/2015/12/16/java-nhung-dieu-co-the-ban-da-biet-co-gi-moi-trong-java-8-phan-1/

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Hoàng Nguyễn

35 bài viết.
400 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
82 34
Nhu cầu về Javascript developer hiện nay trong thị trường IT là rất lớn. Nếu bạn có kiến thức ở mảng này thì cơ hội nghề nghiệp cũng như thu nhập c...
Hoàng Nguyễn viết gần 2 năm trước
82 34
White
51 19
Microservices hiện đang nhận được rất nhiều sự chú ý: các bài viết, các blog, các cuộc thảo luận trên phương tiện truyền thông, trên mạng xã hội, v...
Hoàng Nguyễn viết hơn 2 năm trước
51 19
White
33 2
Chuyện tối ưu code, (Link) là công việc hàng ngày của mỗi lập trình viên, điều đó ai cũng biết. Nhưng liệu code tối ưu có phải là code đẹp, và ngượ...
Hoàng Nguyễn viết 8 tháng trước
33 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
35 bài viết.
400 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!