Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Java

Thành Thạo Java 8 Streams: Ví Dụ Code Thực Tế & Mẹo

Trong bài blog này, chúng ta sẽ thảo luận các tính năng của Java 8 Streams và cung cấp rất nhiều ví dụ code khác nhau.

Java Streams mang lập trình hàm (functional programming) đến Java và được hỗ trợ từ Java 8 nên nếu bạn có phiên bản Java cũ hơn, bạn phải nâng cấp lên Java 8 để dùng Java Streams.

Tại Sao Dùng Java Streams?

Một số ưu điểm của streams:

  • Streams sẽ khiến bạn trở thành lập trình viên Java hiệu quả hơn (bạn sẽ thấy với rất ít dòng code bạn thực sự có thể đạt được rất nhiều dùng streams).
  • Chúng sử dụng nhiều lambda expression (biểu thức lambda) – những hàm "dùng một lần" (disposable functions).
  • ParallelStreams cho phép thao tác đa luồng (multi-threaded) cho tập dữ liệu lớn rất dễ dàng.

Pipeline Của Streams

Trong hầu hết các trường hợp, một streams pipeline bao gồm:

  • nguồn (source) (nơi dữ liệu chảy ra)
  • theo sau bởi không hoặc nhiều trung gian operations (intermediate operations)
  • và một terminal operation

Thành Thạo Java 8 Streams: Ví Dụ Code Thực Tế & Mẹo

Nguồn sẽ stream một luồng các phần tử.

Luồng phần tử đó có thể được lọc (filter), sắp xếp (sort), hoặc ánh xạ (map) hoặc các chuỗi phép toán khác áp dụng cho mỗi phần tử.

Ở cuối, nó có thể được thu thập (collected) hoặc rút gọn (reduced) hoặc một terminal operation khác nhưng chỉ một terminal operation được thực hiện.

Nguồn Stream (Stream Source)

Stream source có thể đến từ collection, list, set, mảng int, long, double, string, v.v.

Các Phép Toán Stream (Stream Operations)

Stream operations hoặc là trung gian (intermediate) hoặc là terminal:

  • Intermediate operations như filter, map, hoặc sort trả về một stream để chúng ta có thể chuỗi (chain) nhiều intermediate operations.
  • Terminal operations nhận một stream và chúng có thể trả về void hoặc trả về kết quả non-stream như một reduction, ví dụ reduce các mục thành một list.

Các Phép Toán Trung Gian (Intermediate Operations)

  • Cho phép không hoặc nhiều intermediate operations.
  • Thứ tự quan trọng; với tập dữ liệu lớn: filter trước rồi sort hoặc map.
  • Với tập dữ liệu rất lớn dùng ParallelStream để bật nhiều luồng.

Một số intermediate operations bao gồm:

  • anyMatch()
  • distinct()
  • filter()
  • findFirst()
  • flatMap()
  • map()
  • skip()
  • sorted()

Các Phép Toán Terminal (Terminal Operations)

Chỉ cho phép một terminal operation.

  • forEach áp dụng cùng một hàm cho mỗi phần tử, ví dụ in từng phần tử.
  • collect lưu tất cả các phần tử vào một collection hoặc list hoặc mảng.
  • Tất cả các tùy chọn khác reduce stream thành một phần tử tóm tắt đơn lẻ.

Một số ví dụ của reduce functions:

  • count()
  • max()
  • min()
  • reduce()

Ví Dụ Code Java Streams

Bây giờ hãy xem các khái niệm trên trong các ví dụ code.

Integer Stream

Ví dụ đầu tiên chỉ là một integer stream. Chúng ta sẽ tạo một integer stream dùng class IntStream và hàm range của nó cho ta một khoảng integer.

forEach là terminal operation của chúng ta. Với mỗi mục, chúng ta chỉ in nó ra.

import java.io.IOException;
import java.util.stream.IntStream;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        IntStream
            .range(1, 10)
            .forEach(System.out::print);
        System.out.println();
    }
}

Đầu ra:

123456789

Integer Stream Với Skip

Ví dụ thứ hai dùng integer stream nhưng chúng ta thêm skip() ở đây, nên trong trường hợp này, chúng ta sẽ bỏ qua 5 phần tử đầu tiên của stream.

Điều này sẽ chỉ in các phần tử 6 đến 9. Chúng ta cũng dùng một lambda expression đơn giản để in mục.

import java.io.IOException;
import java.util.stream.IntStream;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        IntStream
            .range(1, 10)
            .skip(5)
            .forEach(x -> System.out.println(x));
        System.out.println();
    }
}

Đầu ra:

6
7
8
9

Integer Stream Với Sum

Ví dụ thứ ba, chúng ta lại dùng IntStream để tạo stream đối tượng của chúng ta tuy nhiên, chúng ta đặt đó bên trong câu lệnh println() làm tham số cho print line.

Chúng ta sẽ in ra tổng từ khoảng 1 đến 5, nói cách khác 1 2 3 & 4, nó sẽ in ra chỉ tổng của những con số đó:

import java.io.IOException;
import java.util.stream.IntStream;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        System.out.println(
            IntStream
                .range(1, 5)
                .sum());
        System.out.println();
    }
}

Đầu ra:

10

Stream.of

Ví dụ tiếp theo dùng hàm Stream.of, rất tiện vì bạn có thể stream integer, giá trị floating point hoặc string thậm chí object.

Trong ví dụ này, chúng ta chỉ làm một sắp xếp bảng chữ cái thẳng (straight alphabetical sort) sau đó tìm mục đầu tiên dùng hàm findFirst(). Sau đó, chúng ta chỉ in mục đầu tiên trong list.

import java.io.IOException;
import java.util.stream.Stream;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        Stream.of("Ava", "Aneri", "Alberto")
            .sorted()
            .findFirst()
            .ifPresent(System.out::println);
    }
}

Đầu ra:

Alberto

Stream Từ Mảng, Sắp Xếp, Lọc Và In

Trong ví dụ tiếp theo, chúng ta sẽ stream từ một mảng. Sau đó sắp xếp, lọc và in.

Ở đây, chúng ta sẽ lọc ra các mục chỉ bắt đầu bằng s.

Chúng ta dùng lambda expression nhận X là mỗi tên và kiểm tra cái nào bắt đầu bằng chữ s và truyền chúng đi.

Sau đó chúng ta sắp xếp chúng và với mỗi mục vượt qua sắp xếp đó chúng ta in nó.

import java.io.IOException;
import java.util.Arrays;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        String[] names = {"Al", "Ankit", "Kushal", "Brent", "Sarika", "amanda", "Hans", "Shivika", "Sarah"};
        Arrays.stream(names)
            .filter(x -> x.startsWith("S"))
            .sorted()
            .forEach(System.out::println);
    }
}

Đầu ra:

Sarah
Sarika
Shivika

Trung Bình Của Mảng Nguyên

Bây giờ hãy xem làm sao chúng ta có thể lấy trung bình của bình phương một mảng int.

Ở đây, chúng ta dùng hàm Arrays.stream() để stream các integer và sau đó dùng map() để ánh xạ mỗi mục mỗi integer thành bình phương của nó.

import java.util.Arrays;

public class JavaStreams {
    public static void main(String[] args) {
        Arrays.stream(new int[] {2, 4, 6, 8, 10})
            .map(x -> x * x)
            .average()
            .ifPresent(System.out::println);
    }
}

Đầu ra:

44.0

Lưu ý nó in ra double thay vì integer.

Stream Từ List, Lọc Và In

Trong ví dụ này, chúng ta sẽ stream từ một list, lọc những mục đó và in.

Lưu ý rằng bên trong hàm map(), chúng ta sẽ chuyển tất cả tên thành chữ thường.

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

public class JavaStreams {
    public static void main(String[] args) {
        List people = Arrays.asList("Al", "Ankit", "Brent", "Sarika", "amanda", "Hans", "Shivika", "Sarah");
        people
            .stream()
            .map(String::toLowerCase)
            .filter(x -> x.startsWith("a"))
            .forEach(System.out::println);
    }
}

Đầu ra:

al
ankit
amanda

Chúng ta thấy có ba tên bắt đầu bằng a và chúng đều ở chữ thường.

Stream Các Dòng Từ File Text, Sắp Xếp, Lọc, Và In

Trong ví dụ tiếp theo, chúng ta sẽ stream các dòng từ một file text. Chúng ta sẽ sắp xếp, lọc và in.

Giả sử chúng ta có file tên bands.txt với nội dung như dưới:

Rolling Stones
Lady Gaga
Jackson Browne
Maroon 5
Arijit Singh
Elton John
John Mayer
CCR
Eagles
Pink
Aerosmith
Adele
Taylor Swift

Chúng ta sẽ dùng Files.lines() để tạo stream của chúng ta, cho ta một stream của string cho mỗi dòng của file.

Một khi có stream, chúng ta sẽ sắp xếp chúng và lọc ra các mục lớn hơn 13 ký tự và in các mục còn lại.

Cuối cùng, chúng ta phải đóng file nên làm bands.close().

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        Stream bands = Files.lines(Paths.get("bands.txt"));
        bands
            .sorted()
            .filter(x -> x.length() > 13)
            .forEach(System.out::println);
        bands.close();
    }
}

Đầu ra:

Jackson Browne
Rolling Stones

Chúng ta có hai band có hơn 13 ký tự.

Stream Các Dòng Từ File Text Và Lưu Vào List

Đối với ví dụ này, chúng ta sẽ dùng cùng file text như trên.

Chúng ta muốn lọc ra các mục chứa chữ cái jit, dùng x.contains() chỉ là hàm string.

Dùng .collect() chúng ta thêm tất cả những cái có chữ jit vào một list.

Một khi có list, chúng ta có thể dùng toán tử forEach để in các mục.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        List bands2 = Files.lines(Paths.get("bands.txt"))
            .filter(x -> x.contains("jit"))
            .collect(Collectors.toList());
        bands2.forEach(x -> System.out.println(x));
    }
}

Đầu ra:

Arijit Singh

Stream Các Dòng Từ File CSV Và Đếm

Trong ví dụ này, chúng ta stream các dòng từ một file CSV và đếm các dòng tốt (good rows).

Giả sử chúng ta có file tên data.txt với nội dung sau:

A,12,3.7
B,17,2.8
C,14,1.9
D,23,2.7
E
F,18,3.4

Ở đây, dòng E không có dữ liệu, nên chúng ta muốn loại bỏ cái đó khỏi stream.

Trong code sau, chúng ta đọc mỗi dòng rồi cần tách ở dấu phẩy thành mảng nên mỗi dòng sẽ thành mảng các mục.

Sau đó áp dụng filter để lọc ra các dòng không có ba mục.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class JavaStreams {
    public static void main(String[] args) throws IOException {
        Stream rows1 = Files.lines(Paths.get("data.txt"));
        int rowCount = (int) rows1
            .map(x -> x.split(","))
            .filter(x -> x.length == 3)
            .count();
        System.out.println(rowCount + " rows.");
        rows1.close();
    }
}

Đầu ra:

5 rows

Reduction - Tổng (Sum)

Ví dụ này chỉ cho bạn cách dùng reduction. Chúng ta sẽ reduce thành tổng. Ở đây, chúng ta có một double stream dùng hàm Stream.of(). Chúng ta định nghĩa ba double trong ba đối số khác nhau và dùng hàm reduce.

import java.util.stream.Stream;

public class JavaStreams {
    public static void main(String[] args) {
        double total = Stream.of(7.3, 1.5, 4.8)
            .reduce(0.0, (Double a, Double b) -> a + b);
        System.out.println("Total = " + total);
    }
}

Đầu ra:

13.600000000000001