Tìm hiểu Stream API là gì? Stream API trong Java

Trong bài viết này Stanford sẽ giới thiệu đến các bạn những kiến thức liên quan đến stream được giới thiệu từ java 8, giúp cho xử lý, lấy thông tin trong danh sách bằng lập trình java được dễ dàng.

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 Employees
Câu lệnh trên tự động trả về tổng lương của tất cả các nhân viên trong bảng Employees mà không cần phải thực hiện bất kì tính toán gì ở phía đầu cuối.
Tương tự vấn đề trên, khi sử dụng Collections (danh sách) 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 nhân viên thuộc phòng = 'CNTT' 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ó mã phòng = 'CNTT' 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_IT = "CNTT";
    public double tongLuongNhanVien(List<NhanVien> listEmployee) {
        double tongLuong = 0;
        for (NhanVien emp : listEmployee) {
            if (emp.getMaPhong().equals(ROLE_IT)) {
                tongLuong += emp.getLuong();
            }
        }
        return tongLuong;
    }
Để giải quyết vấn đề đó, Java 8 giới thiệu Stream API giúp các lập trình java 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 double tongLuongNhanVien1(List<NhanVien> listEmployee) {
        return listEmployee.stream().filter(p -> p.getMaPhong().equals(ROLE_IT))
                .mapToDouble(p -> p.getLuong())
                .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 stream từ danh sách

Để sử dụng các phương thức trong Stream chúng ta cần khai báo thư viện sau:

import java.util.stream.Stream;

2.1. Tạo 1 empty stream

Phương thức empty() được sử dụng để tạo 1 empty stream để tránh trả về null trong trường hợp không có phần tử nào. Cú pháp sử dụng:

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

2.2. Tạo stream từ collection (Stream of Collection)
Stream cũng có thể được tạo từ bất kì từ kiểu danh sách (collection nào) như Collection, List, Set,..:

Collection<String> collection = Arrays.asList("Java", "CSharp", "Python", "Kotlin", "Swift");

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("Java", "CSharp", "Python", "Kotlin", "Swift");
Hoặc sử dụng theo cách sau:

String[] arr = new String[]{"Java", "CSharp", "Python", "Kotlin", "Swift"};
Stream<String> streamOfArray1 = Arrays.stream(arr);
Stream<String> streamOfArray2 = 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("Java").add("CSharp").add("Python").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à "stanford dạy kinh nghiệm lập trình"

Stream<String> streamGenerated =Stream.generate(() -> "stanford dạy kinh nghiệm lập trình").limit(10);

2.6. Stream.iterate()

1 cách khác để tạo ra 1 stream vô hạn là sử dụng phương thức iterate(). Ví dụ sau sẽ đưa ra được 5 thông tin với chuỗi cho trước cộng với giá trị của n:

Stream<String> streamIterated = Stream.iterate("Stanford.com.vn", n -> n + 1).limit(5);
List<String> listIterated = streamIterated.collect(Collectors.toList());
System.out.println(listIterated);
Kết quả hiển thị là:

[Stanford.com.vn, Stanford.com.vn1, Stanford.com.vn11, Stanford.com.vn111, Stanford.com.vn1111]

2.7. Stream of Primitives

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

2.8. Stream of String

//Đưa về stream từ việc split chuỗi theo từng dấu ,
Stream<String> streamOfString =Pattern.compile(", ").splitAsStream("Java, C#, Python");

2.9. Stream of File

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

3. Một số ví dụ làm việc với stream

Sau đây là một số ví dụ sử dụng stream trong trong lập trình java.

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

Sử dụng hàm collect() để tạo 1 List, Set, Map từ stream:

Stream<Integer> intStream = Stream.of(1,2,3,4, 5);
List<Integer> intList = intStream.collect(Collectors.toList());
System.out.println(intList); //prints [1, 2, 3, 4, 5]
intStream = Stream.of(1,2,3,4, 5); //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+20));
System.out.println(intMap); //prints {1=21, 2=22, 3=23, 4=24, 5=25}
Dùng stream toArray() để tạo 1 array từ stream

Stream<Integer> intStream = Stream.of(1,2,3,4,5);
Integer[] intArray = intStream.toArray(Integer[]::new);
System.out.println(Arrays.toString(intArray)); //prints [1, 2, 3, 4, 5]
3.2. Stream.filter()
Stream.filter() Trả về 1 stream gồm các phần tử khớp với điều kiện 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ả hiển thị là 4, 6

Sử dụng để phân trang lấy một tập dữ liệu để tối ưu với dữ liệu lớn:

List<Film> filmsSortedByLengthPage2 = films.stream()
    .sorted(Film.LENGTH)
    .skip(25 * 1)
    .limit(25)
    .collect(toList());
Ví dụ trên là lấy các bộ phim sắp xếp theo length và lấy từ vị trí 25 với 25 thông tin cần lấy từ cơ sở dữ liệu phim.

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. 

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));
Và kết quả hiển thị:

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("S","T","A","N", "F");
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ả hiển thị là:

FindAny: S
FindFirst: S
3.5. Stream.distinct()
Stream.distinct() trả về 1 stream với các phần tử không trùng lặp
Ví dụ:

List<Integer> list = Arrays.asList(3,5,6,6,5);
System.out.print("Các phần tử không trùng lặp là: ");
list.stream().distinct().forEach(p -> System.out.print(p + ", "));
Kết quả:

Các phần tử không trùng lặp là: 3, 5, 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 danh sách sang chữ hoa:

Stream<String> names = Stream.of("Stanford", "Lập trình", "stanford.com.vn");
System.out.println(names.map(s -> {
    return s.toUpperCase();
  }).collect(Collectors.toList()));
//prints [STANFORD, LẬP TRÌNH, STANFORD.COM.VN]
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.

List<String> list = Arrays.asList("S","T","A","N", "F");
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:T, Min:A

4. Một số chú ý khi sử dụng stream

4.1. Stream đã dùng thì không thể sử dụng lại

Stream<String> names = Stream.of("Stanford", "Java", "stanford.com.vn");
names.forEach(p -> System.out.print(p + " ")); // print: Stanford Java stanford.com.vn
names.forEach(p -> System.out.print(p + " ")); // exception: Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
4.2. Khởi tạo lại stream hoặc dùng  Supplier:

Stream<String> names = Stream.of("Stanford", "java", "stanford.com.vn");
names.forEach(p -> System.out.print(p + " ")); // print: Stanford java stanford.com.vn
// Khởi tạo lại stream
names = Stream.of("Stanford", "java", "stanford.com.vn");
names.forEach(p -> System.out.print(p + " ")); // print: Stanford java stanford.com.vn
 
//Dùng supplier:
Supplier<Stream<String>> streamSupplier = () -> Stream.of("Stanford", "java", "stanford.com.vn");
streamSupplier.get().forEach(p -> System.out.print(p + " ")); // print: Stanford java stanford.com.vn
streamSupplier.get().forEach(p -> System.out.print(p + " ")); // print: Stanford java stanford.com.vn
Hy vọng qua bài viết này các bạn học lập trình java đã hiểu rõ hơn về stream cũng như sẽ ứng dụng hiệu quả vào trong dự án, công việc của mình khi cần. Nếu bạn muốn được đào tạo java từ cơ bản đến nâng cao cùng chuyên gia Stanford có thể tham khảo khóa học java fullstack của chúng tôi tại đây: Khóa lập trình java web fullstack

=============================
☎ STANFORD – ĐÀO TẠO VÀ PHÁT TRIỂN CÔNG NGHỆ
Hotline: 0963 723 236 - 0866 586 366
Website: https://stanford.com.vn
Facebook: http://bit.ly/2FN0TYb
Youtube: http://bit.ly/2TkKT7I


Tags: học stream trong java, học java, học lập trình java, học java nâng cao, học java cùng chuyên gia