Stream API là gì? Stream API trong Java 8

Stream API là gì? Stream API trong Java 8

1. Stream API là gì?

Stream là 1 lớp trừu tượng mới được giới thiệu trong Java 8. Sử dụng Stream, bạn có thể xử lý dữ liệu 1 cách tự nhiên giống như các câu lệnh SQL. Ví dụ ta có câu SQL sau:

SELECT SUM(salary) FROM Employee

Câu lệnh trên tự động trả về tổng lương của tất cả Employee mà không cần phải thực hiện bất kì tính toán gì ở phía đầu cuối developer (Bình thường thì sẽ SELECT tất cả các Employeesau đó dùng code Java để duyệt và tính tổng của tất cả Employee).
Tương tự vấn đề trên, khi sử dụng Collections trong Java, chúng ta thực hiện các vòng lặp và thực hiện lại các đoạn kiểm tra. Giả sử muốn tính tổng lương của các employee có role = ‘Developer’ từ 1 danh sách chúng ta phải thực hiện lặp tất cả các phần tử, kiểm tra phần tử đó có role = ‘Developer’ rồi cộng lại. Trong khi nếu muốn xử lý chúng song song lại dễ gặp lỗi.

public static final String ROLE_DEVELOPER = "Developer";

public double sumSalaryOfDeveloper(List<Employee> listEmployee) {
  double sumSalary = 0;
  for (Employee emp : listEmployee) {
    if (emp.getRole().equals(ROLE_DEVELOPER)) {
      sumSalary += emp.getSalary();
    }
  }
  return sumSalary;
}

Để giải quyết vấn đề đó, Java 8 giới thiệu Stream API giúp developer xử lý dữ liệu khai báo và tận dụng kiến trúc đa lõi (multicore) mà không cần viết mã cụ thể cho nó.

public static final String ROLE_DEVELOPER = "Developer";

public double sumSalaryOfDeveloper(List<Employee> listEmployee) {
  return listEmployee.stream().filter(p -> p.getRole().equals(ROLE_DEVELOPER)).mapToDouble(p -> p.getSalary()).sum();
}

Như vậy, bạn có thể hiểu stream đại diện cho một collection được xử lý tuần tự và hỗ trợ rất nhiều loại operation để tính toán dựa trên những element của collection đó (tính tổng, convert sang map, …)

2. Cách tạo 1 Stream

2.1 Tạo 1 empty stream

Method empty() được sử dụng để tạo 1 empty stream:

Stream<String> streamEmpty = Stream.empty();

empty stream trường được dùng khi khởi tạo để tránh việc trả về null cho các stream không có phần tử nào:

public Stream<String> streamOf(List<String> list) {
    return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}

2.2 Tạo stream từ collection (Stream of Collection)

Stream cũng có thể được tạo từ bất kì kiểu collection nào (Collection, List, Set):

Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();

2.3 Stream of Array

Stream cũng có thể được tạo từ 1 array hoặc 1 phần của array:

Stream<String> streamOfArray = Stream.of("a", "b", "c");
String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);

2.4 Stream.builder()

Stream.builder() được sử dụng khi muốn thêm mới 1 phần vào bên phải stream.

Stream<String> streamBuilder =
  Stream.<String>builder().add("a").add("b").add("c").build();

2.5 Stream.generate()

Phương thức generate() chấp nhận một Supplier<T> cho các phần tử được tạo ra. Số phần tử được tạo ra là vô hạn nên cần phải dùng lệnh .limit() để giới hạn số phần tử được tạo ra.

Ví dụ đoạn code dưới đây sẽ tạo ra liên tục 10 String có giá trị là “stackjava”

Stream<String> streamGenerated =
  Stream.generate(() -> "stackjava").limit(10);

2.6 Stream.iterate()

1 cách khác để tạo ra 1 stream vô hạn là sử dụng method iterate():

Stream<String> streamIterated = Stream.iterate("StackJava", n -> n + 1).limit(5);
List<String> listIterated = streamIterated.collect(Collectors.toList());
System.out.println(listIterated);

Kết quả:

[StackJava, StackJava1, StackJava11, StackJava111, StackJava1111]

2.7 Stream of Primitives

IntStream intStream = IntStream.range(1, 3);

2.8 Stream of String

Stream<String> streamOfString =
  Pattern.compile(", ").splitAsStream("a, b, c");

2.9. Stream of File

Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = 
  Files.lines(path, Charset.forName("UTF-8"));

3. Các ví dụ, method thường dùng với Stream

3.1 Chuyển 1 stream sang Collection hoặc array:

  • Chúng ta có thể dùng streamcollect() để tạo 1 List, Set, Map từ stream.
Stream<Integer> intStream = Stream.of(1,2,3,4);
List<Integer> intList = intStream.collect(Collectors.toList());
System.out.println(intList); //prints [1, 2, 3, 4]

intStream = Stream.of(1,2,3,4); //stream bị đóng nên cần khởi tạo lại (*đọc phần lưu ý cuối bài)
Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10));
System.out.println(intMap); //prints {1=11, 2=12, 3=13, 4=14}
  • Dùng stream toArray() để tạo 1 array từ stream
Stream<Integer> intStream = Stream.of(1,2,3,4);
Integer[] intArray = intStream.toArray(Integer[]::new);
System.out.println(Arrays.toString(intArray)); //prints [1, 2, 3, 4]

3.2 Stream.filter()

Stream.filter() Trả về 1 stream viows các phần tử khớp với Predicate

Predicate<Integer> p = num -> num % 2 == 0;
List<Integer> list = Arrays.asList(3,4,6);
list.stream().filter(p).forEach(e -> System.out.print(e+" "));

Kết quả:

4 6  

3.3 Stream.allMatch(), Stream.anyMatch() and Stream.noneMatch()

allMatch(): trả về true nếu tất cả các phần tử khớp với Predicate.
anyMatch(): trả về true nếu có ít nhất 1 phần tử khớp với Predicate.
noneMatch(): trả về true nếu không có phần tử nào khớp với Predicate.

Ví du:

Predicate<Integer> p = num -> num % 2 == 0;
List<Integer> list = Arrays.asList(3,5,6);
System.out.println("allMatch:" + list.stream().allMatch(p));
System.out.println("anyMatch:" + list.stream().anyMatch(p));
System.out.println("noneMatch:" + list.stream().noneMatch(p));

Kết quả:

allMatch:false
anyMatch:true
noneMatch:false

3.4 Stream.findAny() and Stream.findFirst()

findAny(): Trả về 1 phần tử bất kì của stream.
findFirst(): Trả về phần tử đầu tiên của stream.

Ví dụ:

List<String> list = Arrays.asList("G","B","F","E");
String any = list.stream().findAny().get();
System.out.println("FindAny: "+ any);
String first = list.stream().findFirst().get();
System.out.println("FindFirst: "+ first);		

Kết quả:

FindAny: G
FindFirst: G 

3.5 Stream.distinct()

Stream.distinct() trả về 1 stream với các phần tử riêng biệt (không trùng nhau)

List<Integer> list = Arrays.asList(3,4,6,6,4);
System.out.print("Distinct elements: ");
list.stream().distinct().forEach(p -> System.out.print(p + ", "));

Kết quả:

Distinct elements: 3, 4, 6,

3.6 Stream map()

Sử dụng map() để map (ánh xạ) mỗi phần tử của stream sang 1 giá trị tương ứng. Ví dụ chuyển các string của 1 list sang chữ hoa:

Stream<String> names = Stream.of("stack", "Java", "stackjava.com");
System.out.println(names.map(s -> {
    return s.toUpperCase();
  }).collect(Collectors.toList()));
//prints [STACK, JAVA, STACKJAVA.COM]

3.7 Stream.max() and Stream.min()

max(): tìm phần tử có giá trị lớn nhất dựa theo Comparator.
min(): tìm phần tử có giá trị nhỏ nhất dựa theoComparator.

Ví dụ:

List<String> list = Arrays.asList("G","B","F","E");
String max = list.stream().max(Comparator.comparing(String::valueOf)).get();
System.out.println("Max:"+ max);
String min = list.stream().min(Comparator.comparing(String::valueOf)).get();
System.out.println("Min:"+ min);

Kết quả:

Max:G
Min:B
List<String> list = Arrays.asList("A","B","C");
list.stream().peek(s->System.out.println(s+s)).collect(Collectors.toList());		

Kết quả:

AA
BB
CC

 

Trả về 1 new stream gồm tất cả các phần tử của stream sau khi áp dụng Consumer.

Ví dụ:

* Một số lưu ý với Stream

Reuse với stream: stream không thể reuse lại:

Stream<String> names = Stream.of("stack", "java", "stackjava.com");
names.forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com
names.forEach(p -> System.out.print(p + " ")); // exception: Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

Cách xử lý: khởi tạo lại stream hoặc dùng  Supplier:

Stream<String> names = Stream.of("stack", "java", "stackjava.com");
names.forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com

// Khởi tạo lại stream
names = Stream.of("stack", "java", "stackjava.com");
names.forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com


// dùng supplier:
Supplier<Stream<String>> streamSupplier = () -> Stream.of("stack", "java", "stackjava.com");
streamSupplier.get().forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com
streamSupplier.get().forEach(p -> System.out.print(p + " ")); // print: stack java stackjava.com

References:

http://www.oracle.com/…/8-whats-new-2157071.html

stackjava.com