12/08/2018, 13:10

Các trường hợp phổ biến sử dụng Stream trong java 8 - Common use cases of Stream in Java 8

Ở biết viết trước mình đã giới thiệu với mọi người các khái niệm và đặc điểm của Stream trong java 8: https://viblo.asia/atula/posts/WApGxNnoR06y Bài viết này là bài viết thứ 2 trong chuỗi series về Stream trong Java 8. Trong bài viết này mình sẽ giới thiệu và ...

Ở biết viết trước mình đã giới thiệu với mọi người các khái niệm và đặc điểm của Stream trong java 8: https://viblo.asia/atula/posts/WApGxNnoR06y

Bài viết này là bài viết thứ 2 trong chuỗi series về Stream trong Java 8. Trong bài viết này mình sẽ giới thiệu và hướng dẫn về các trường hợp phổ biến sử dụng với Stream.

Để mô tả cách sử dụng Stream chúng ta có thể lấy ví dụ 1 vài câu lệnh SQL. Giả sử ta có bảng student với 2 cột là name và point. Muốn lấy ra sinh viên có điểm cao nhất, câu lệnh SQL sẽ như sau

select MAX(point), name from student;

Như ta thấy ở đây chúng ta chỉ gần gọi hàm MAX của sql để lấy ra được điểm cao nhất mà không cần quan tâm đến cách SQL thực hiện tím kiếm nó như thế nào.

Trong java trước đây để tìm được giá trị lớn nhất, đơn giản nhất là dùng 1 vòng lặp, lặp từ đầu đến cuối, sử dụng 1 biến tạm để lưu giá trị lớn nhất so sanh biến này với giá trị ở từng vòng lặp. Bài toán xử lý thao tác với 1 collection là bài toán cơ bản, mọi ứng dụng đều có và thực hiện nhiều lần cho điều này. Câu hỏi là có thể thực hiện điều tương tự SQL với Java? Câu trả lời là có và Stream trong java 8 sẽ giúp ta thực hiện nó.

Một tác dụng khác đó là xử lý đa luồng, song song, để implement 1 đoạn code tạo, chạy, quản lý đa luồng là không dễ với những người mới Nhưng với Stream trong java 8 sẽ support điều này khá dễ dàng.

Lưu ý: mình vẫn muốn nhắc lại 1 điều: không phải cứ song song và đa luồng là nhanh. Ví dụ: nếu nhiều luồng chạy song song và phải đợi kết quả của các luồng khác (phụ thuộc chéo) thì có khả năng là sẽ chậm hơn so với sử dụng 1 luồng Hoặc sử dụng 1 luồng đơn nhưng thao tác đơn giản trực tiếp có thể sẽ nhanh hơn là dùng nhiều luồng nhưng thực hiện phức tạp, gián tiếp phải qua nhiều trung gian để cùng đạt được 1 kết quả.

Một ví dụ trong đời sống cho vui thôi: Khi bạn muốn hoàn thiện giấy tờ cho 1 hồ sơ đấu thầu hoặc 1 thủ tục gì đó bên nhà nước mà nhiều khâu nhiều cửa. Mình sẽ thuê hẳn 1 ông cò làm từ A đến Z sẽ nhanh hơn là thuê nhiều vị, mỗi vị lại lo, chạy 1 cửa . Vì điều này sẽ mất thời gian đợi nhau, trao đổi qua lại này kia.

Oki, bây giờ chúng ta sẽ đi vào các common use case của Stream

**1. Filtering - lọc **

  • filter(Predicate): trả về 1 stream gồm các phần tử thỏa mãn điều kiện được chỉ ra ở Predicate

Example

List<Student> studentList = new ArrayList<>();
// some code for add data to studentList
// get all student which have name is Nam
studentList.parallelStream().filter(t -> t.getName().equals("Nam"));
  • distinct: trả về 1 stream với các phần tử duy nhất, không trùng lặp. Việc xác định như thế là là trùng lặp để loại bỏ JVM sẽ dựa vào method equal. Thông thường khi sử dụng hàm này, chúng ta thường phải override hàm equal để định nghĩa ra quy tắc loại bỏ đối tượng trùng lặp.

Example:

public static void testDistinct() { List<Integer> numbers = Arrays.asList(1, 2, 3, 3, 5, 1, 7, 2);

	System.out.println("Before distinct: " + numbers);
	List<Integer> results = numbers.stream().distinct().collect(Collectors.toList());

	System.out.println("Result of distinct: " + results);
}
  • limit(n): trả về 1 stream với số lượng phần tử tối đa là n phần tử. Ý nghĩa tương tự từ khóa limit trong SQL

Example:

public static void testLimit()
{
	List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
	List<Integer> results = numbers.stream().filter(i -> {
		System.out.println("Filter: " + i);
		return i > 4;
	}).limit(3).collect(Collectors.toList());

	System.out.println("Result of limit: " + results);
}
  • skip(n): trả về 1 stream nhưng bỏ qua n phần tử đầu tiên từ stream ban đầu. Example:
public static void testSkip()
{
	List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
	List<Integer> results = numbers.stream().filter(i -> {
		System.out.println("Filter: " + i);
		return i > 4;
	}).skip(2).collect(Collectors.toList());

	System.out.println("Result of skip: " + results);
}

2. Matching - khớp

  • anyMatch: sử dụng để kiểm tra xem các phần tử của stream, có bất kỳ phần tử nào thỏa mãn 1 điều kiện nào đó hay không

Example:

public static boolean checkAnyMatch()
{
	List<Integer> numbers = Arrays.asList(1, 2, 23, 4, 7, 61, 72, 88);
	System.out.println("---------- Input -----------");
	System.out.println(numbers);
	return numbers.stream().anyMatch(t -> t > 10);
}
  • allMatch: chỉ trả về true khi tất cả các phần tử trong stream đều thỏa mãn điều kiện

Example:

public static boolean checkAllMatch()
{
	List<Integer> numbers = Arrays.asList(11, 12, 23, 44, 57, 61, 72, 88);
	System.out.println("---------- Input -----------");
	System.out.println(numbers);
	return numbers.stream().allMatch(t -> t > 10);
}
  • noneMatch: trả về true khi không có bất kỳ phần tử nào thỏa mãn điều kiện Example:
public static boolean checkNoneMatch()
{
	List<Integer> numbers = Arrays.asList(1, 2, 8, 4, 7, 6, 9, 8);
	System.out.println("---------- Input -----------");
	System.out.println(numbers);
	return numbers.stream().noneMatch(t -> t > 10);
}

**3. Finding ** Cả 2 method trên đều trả về đối tượng Optional nếu stream có dữ liệu. 2 method trên thường dùng kết hợp các method như filter Optinal là class dùng để mô tả 1 giá trị có tồn tại hay không tồn tại trong 1 list. Hay được sử dụng với Enum class để kiểm tra 1 giá trị có nằm trong list các giá trị của 1 kiểu enum hay không.

-findFirst: trả về Optinal mô tả phần tử đầu tiên của stream

Example

public static void testFindFirst()
{
	List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
	numbers.stream()
			.filter(t -> t > 4)
			.findFirst()
			.ifPresent(System.out::println);
}
  • findAny: trả về Optinal mô tả 1 vài phần tử của stream

Example:

public static void testFindAny()
{
	List<Integer> numbers = Arrays.asList(1, 8, 3, 6, 4, 7, 0, 2);
	numbers.stream()
			.filter(t -> t > 4)
			.findAny()
			.ifPresent(System.out::println);
}

**4. Mapping ** Stream support phương thức map để chuyển các element trong 1 stream thành kiểu khác. Nó gần tương tự với việc liệt kê các column muốn lấy dữ liệu và thêm các hàm tính toán với các column này trong câu lệnh SELECT của SQL

Example:

public static void testMap()
{
	List<String> words = Arrays.asList("Oracle", "Java", "Magazine");
	List<Integer> wordLengths =
			words.stream()
					.map(String::length)
					.collect(Collectors.toList());
	System.out.println(words);
	System.out.println(wordLengths);
}

5. Reduce

  • sử dụng khi muốn thao tác giữa các phần tử trong stream để thực hiện phép tính nào đó Ví dụ tính tổng các số nguyên của 1 list collection, hay tìm ra giá trị max của 1 collection
  • Cách sử dụng: khi sử dụng method reduce thường truyền vào 1 tham số đó là toán tử 2 ngôi(BinaryOperator) cái mà sử dụng 2 phần tử để thao tác
public static void checkReduce()
{
	List<Integer> numbers = Arrays.asList(1, 2, 23, 4, 7, 61, 72, 88);
	int sum = numbers.stream().reduce(0, (a, b) -> a + b);
	System.out.println("Sum of all list: " + sum);
}

Như đã nói ở trên cách sử dụng reduce ở đây sẽ làm cho việc lấy giá trị max trong java 8 sẽ có giống như trong SQL

public static void useReduceForGetMaximum()
{
	List<Integer> numbers = Arrays.asList(1, 2, 23, 4, 7, 61, 72, 88);
	int max = numbers.stream().reduce(0, Integer::max);
	System.out.println("Maximum is: " + max);
}

6. Stream of primitive type - number stream

  • Java 8 có tạo 1 vài stream chuyên support cho stream kiểu số. Ví dụ như: IntStream, LongStream, DoubleStream Number stream có sẵn các method cho tính toán với kiểu số như method: sum, count, average, max, min
public static void testNumericStream()
    {
        List<Student> studentList = Arrays.asList(new Student("Linh", 8), new Student("Nam", 4), new Student("Viet", 2), new Student("Hoang", 7));
        int sum = studentList.stream().mapToInt(Student::getPoint).sum();
        System.out.println("Sum of all student's point: " + sum);
    }

  • Sử dụng các method mapInt, mapLong, mapDouble để convert stream của 1 object sang number stream tương ứng
public static void createNumberStream()
    {
        //
        Stream<Integer> numberStreamFromValues = Stream.of(1, 2, 23, 4, 7, 61, 72, 88);

        numberStreamFromValues.max(Comparator.<Integer>naturalOrder());

        List<Integer> numbers = Arrays.asList(1, 2, 23, 4, 7, 61, 72, 88);

        int[] intArray = {1, 2, 23, 4, 7, 61, 72, 88};
        IntStream intStream = Arrays.stream(intArray);
    }

7. Infinite stream Java 8 hỗ trợ 2 hàm tạo stream từ 1 function: Stream.iterate và Stream.generate. Tức là số lượng element trong các stream này là không xác định và tùy vào thời điểm chạy

public static List<Integer> getDataFromDB()
{
    List<Integer> integerList = new ArrayList<>();
    // do something to get data from db
    return integerList;
}

public static void infiniteStreamExample()
{
    Stream<Integer> numbers = Stream.iterate(0, n -> n + 10);

    Stream.generate(StreamExample::getDataFromDB);
}

References: http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html

0