1. Danh sách các phương thức của Streamlớp

Lớp này Streamđược tạo ra để giúp dễ dàng xây dựng các chuỗi luồng dữ liệu. Để đạt được điều này, Stream<T>lớp có các phương thức trả về Streamcác đối tượng mới.

Mỗi luồng dữ liệu này thực hiện một hành động đơn giản nhưng nếu bạn kết hợp chúng thành chuỗi và thêm các hàm lambda thú vị thì bạn sẽ có một cơ chế mạnh mẽ để tạo đầu ra mong muốn. Bạn sẽ sớm thấy cho chính mình.

Dưới đây là các phương thức của Streamlớp (chỉ những phương thức cơ bản nhất):

phương pháp Sự miêu tả
Stream<T> of()
Tạo một luồng từ một tập hợp các đối tượng
Stream<T> generate()
Tạo luồng theo quy tắc đã chỉ định
Stream<T> concat()
Nối hai luồng
Stream<T> filter()
Lọc dữ liệu, chỉ truyền dữ liệu phù hợp với quy tắc đã chỉ định
Stream<T> distinct()
Loại bỏ trùng lặp. Không truyền dữ liệu đã gặp phải
Stream<T> sorted()
Sắp xếp dữ liệu
Stream<T> peek()
Thực hiện một hành động trên từng phần tử trong luồng
Stream<T> limit(n)
Trả về một luồng bị cắt bớt để nó không dài hơn giới hạn đã chỉ định
Stream<T> skip(n)
Bỏ qua n phần tử đầu tiên
Stream<R> map()
Chuyển đổi dữ liệu từ loại này sang loại khác
Stream<R> flatMap()
Chuyển đổi dữ liệu từ loại này sang loại khác
boolean anyMatch()
Kiểm tra xem có ít nhất một phần tử trong luồng khớp với quy tắc đã chỉ định hay không
boolean allMatch()
Kiểm tra xem tất cả các phần tử trong luồng có khớp với quy tắc đã chỉ định hay không
boolean noneMatch()
Kiểm tra xem không có phần tử nào trong luồng khớp với quy tắc đã chỉ định
Optional<T> findFirst()
Trả về phần tử đầu tiên được tìm thấy phù hợp với quy tắc
Optional<T> findAny()
Trả về bất kỳ phần tử nào trong luồng phù hợp với quy tắc
Optional<T> min()
Tìm kiếm phần tử tối thiểu trong luồng dữ liệu
Optional<T> max()
Trả về phần tử lớn nhất trong luồng dữ liệu
long count()
Trả về số phần tử trong luồng dữ liệu
R collect()
Đọc tất cả dữ liệu từ luồng và trả về dưới dạng bộ sưu tập

2. Hoạt động trung gian và đầu cuối theo Streamlớp

Như bạn có thể thấy, không phải tất cả các phương thức trong bảng trên đều trả về một Stream. Điều này liên quan đến thực tế là các phương thức của Streamlớp có thể được chia thành các phương thức trung gian (còn được gọi là phương thức không đầu cuối ) và phương thức đầu cuối .

phương pháp trung gian

Các phương thức trung gian trả về một đối tượng cài đặt Streamgiao diện và chúng có thể được kết nối với nhau.

phương thức đầu cuối

Các phương thức đầu cuối trả về một giá trị khác với Stream.

Đường ống gọi phương thức

Do đó, bạn có thể xây dựng một đường dẫn luồng bao gồm bất kỳ số lượng phương thức trung gian nào và một lệnh gọi phương thức đầu cuối duy nhất ở cuối. Cách tiếp cận này cho phép bạn triển khai logic khá phức tạp, đồng thời tăng khả năng đọc mã.

Đường ống gọi phương thức

Dữ liệu bên trong luồng dữ liệu hoàn toàn không thay đổi. Một chuỗi các phương thức trung gian là một cách khéo léo (khai báo) để chỉ định một đường ống xử lý dữ liệu sẽ được thực thi sau khi phương thức đầu cuối được gọi.

Nói cách khác, nếu phương thức đầu cuối không được gọi, thì dữ liệu trong luồng dữ liệu sẽ không được xử lý theo bất kỳ cách nào. Chỉ sau khi phương thức đầu cuối được gọi, dữ liệu mới bắt đầu được xử lý theo các quy tắc được chỉ định trong đường dẫn luồng.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Hình thức chung của một đường ống

So sánh các phương pháp trung gian và đầu cuối:

trung cấp phần cuối
loại trả lại Stream không phải là mộtStream
Có thể kết hợp với nhiều phương thức cùng loại để tạo thành một đường ống dẫn Đúng KHÔNG
Số phương thức trong một đường ống đơn bất kì không nhiều hơn một
Tạo ra kết quả cuối cùng KHÔNG Đúng
Bắt đầu xử lý dữ liệu trong luồng KHÔNG Đúng

Hãy xem một ví dụ.

Giả sử chúng ta có một câu lạc bộ dành cho những người yêu động vật. Ngày mai câu lạc bộ kỷ niệm Ngày Ginger Cat. Câu lạc bộ có những người nuôi thú cưng, mỗi người có một danh sách thú cưng. Chúng không giới hạn ở mèo.

Nhiệm vụ: bạn cần xác định tất cả tên của tất cả những chú mèo gừng để tạo thiệp chúc mừng được cá nhân hóa cho chúng trong "kỳ nghỉ nghề nghiệp" ngày mai. Thiệp chúc mừng nên được sắp xếp theo tuổi của mèo, từ lớn nhất đến nhỏ nhất.

Đầu tiên, chúng tôi cung cấp một số lớp để giúp giải quyết nhiệm vụ này:

public enum Color {
   WHITE,
   BLACK,
   DARK_GREY,
   LIGHT_GREY,
   FOXY,
   GREEN,
   YELLOW,
   BLUE,
   MAGENTA
}
public abstract class Animal {
   private String name;
   private Color color;
   private int age;

   public Animal(String name, Color color, int age) {
      this.name = name;
      this.color = color;
      this.age = age;
   }

   public String getName() {
      return name;
   }

   public Color getColor() {
      return color;
   }

   public int getAge() {
      return age;
   }
}
public class Cat extends Animal {
   public Cat(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Dog extends Animal {
   public Dog(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Parrot extends Animal {
   public Parrot(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Pig extends Animal {
   public Pig(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Snake extends Animal {
   public Snake(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Owner {
   private String name;
   private List<Animal> pets = new ArrayList<>();

   public Owner(String name) {
      this.name = name;
   }

   public List<Animal> getPets() {
      return pets;
   }
}

Bây giờ hãy xem Selectorlớp, nơi lựa chọn sẽ được thực hiện theo các tiêu chí đã chỉ định:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Selector {
   private static List<Owner> owners;

   private static void initData() {
      final Owner owner1 = new Owner("Ronan Turner");
      owner1.getPets().addAll(List.of(
            new Cat("Baron", Color.BLACK, 3),
            new Cat("Sultan", Color.DARK_GREY, 4),
            new Dog("Elsa", Color.WHITE, 0)
      ));

      final Owner owner2 = new Owner("Scarlet Murray");
      owner2.getPets().addAll(List.of(
            new Cat("Ginger", Color.FOXY, 7),
            new Cat("Oscar", Color.FOXY, 5),
            new Parrot("Admiral", Color.BLUE, 3)
      ));

      final Owner owner3 = new Owner("Felicity Mason");
      owner3.getPets().addAll(List.of(
            new Dog("Arnold", Color.FOXY, 3),
            new Pig("Vacuum Cleaner", Color.LIGHT_GREY, 8)
      ));

      final Owner owner4 = new Owner("Mitchell Stone");
      owner4.getPets().addAll(List.of(
            new Snake("Mr. Boa", Color.DARK_GREY, 2)
      ));

      final Owner owner5 = new Owner("Jonathan Snyder");
      owner5.getPets().addAll(List.of(
            new Cat("Fisher", Color.BLACK, 16),
            new Cat("Zorro", Color.FOXY, 14),
            new Cat("Margo", Color.WHITE, 3),
            new Cat("Brawler", Color.DARK_GREY, 1)
      ));

      owners = List.of(owner1, owner2, owner3, owner4, owner5);
   }
}

Điều còn lại là thêm mã vào mainphương thức. Hiện tại, trước tiên chúng tôi gọi initData()phương thức này sẽ điền danh sách chủ sở hữu vật nuôi trong câu lạc bộ. Sau đó, chúng tôi chọn tên của những chú mèo gừng được sắp xếp theo độ tuổi của chúng theo thứ tự giảm dần.

Trước tiên, hãy xem mã không sử dụng luồng để giải quyết tác vụ này:

public static void main(String[] args) {
   initData();

   List<String> findNames = new ArrayList<>();
   List<Cat> findCats = new ArrayList<>();
   for (Owner owner : owners) {
      for (Animal pet : owner.getPets()) {
         if (Cat.class.equals(pet.getClass()) && Color.FOXY == pet.getColor()) {
            findCats.add((Cat) pet);
         }
      }
   }

   Collections.sort(findCats, new Comparator<Cat>() {
      public int compare(Cat o1, Cat o2) {
         return o2.getAge() - o1.getAge();
      }
   });

   for (Cat cat : findCats) {
      findNames.add(cat.getName());
   }

   findNames.forEach(System.out::println);
}

Bây giờ hãy xem xét một giải pháp thay thế:

public static void main(String[] args) {
   initData();

   final List<String> findNames = owners.stream()
           .flatMap(owner -> owner.getPets().stream())
           .filter(pet -> Cat.class.equals(pet.getClass()))
           .filter(cat -> Color.FOXY == cat.getColor())
           .sorted((o1, o2) -> o2.getAge() - o1.getAge())
           .map(Animal::getName)
           .collect(Collectors.toList());

   findNames.forEach(System.out::println);
}

Như bạn có thể thấy, mã này nhỏ gọn hơn nhiều. Ngoài ra, mỗi dòng của đường dẫn luồng là một hành động đơn lẻ, vì vậy chúng có thể được đọc giống như các câu trong tiếng Anh:

.flatMap(owner -> owner.getPets().stream())
Di chuyển từ a Stream<Owner>đến aStream<Pet>
.filter(pet -> Cat.class.equals(pet.getClass()))
Chỉ giữ lại mèo trong luồng dữ liệu
.filter(cat -> Color.FOXY == cat.getColor())
Chỉ giữ lại những con mèo gừng trong luồng dữ liệu
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
Sắp xếp theo độ tuổi giảm dần
.map(Animal::getName)
Lấy tên
.collect(Collectors.toList())
Đặt kết quả vào một danh sách

3. Tạo luồng

Lớp Streamcó ba phương thức mà chúng ta chưa đề cập đến. Mục đích của ba phương pháp này là để tạo chủ đề mới.

Stream<T>.of(T obj)phương pháp

Phương of()thức tạo một luồng bao gồm một phần tử duy nhất. Điều này thường cần thiết khi một hàm lấy một Stream<T>đối tượng làm đối số, nhưng bạn chỉ có một Tđối tượng. Sau đó, bạn có thể sử dụng phương pháp này một cách dễ dàng và đơn giản of()để lấy luồng bao gồm một phần tử duy nhất .

Ví dụ:

Stream<Integer> stream = Stream.of(1);

Stream<T> Stream.of(T obj1, T obj2, T obj3, ...)phương pháp

Phương thức này of()tạo ra một luồng bao gồm các phần tử đã truyền . Bất kỳ số phần tử nào cũng được cho phép. Ví dụ:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

Stream<T> Stream.generate(Supplier<T> obj)phương pháp

Phương thức này generate()cho phép bạn đặt quy tắc sẽ được sử dụng để tạo phần tử tiếp theo của luồng khi được yêu cầu. Ví dụ: bạn có thể đưa ra một số ngẫu nhiên mỗi lần.

Ví dụ:

Stream<Double> s = Stream.generate(Math::random);

Stream<T> Stream.concat(Stream<T> a, Stream<T> b)phương pháp

Phương concat()thức nối hai luồng đã truyền thành một . Khi dữ liệu được đọc, nó sẽ được đọc đầu tiên từ luồng đầu tiên và sau đó từ luồng thứ hai. Ví dụ:

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = Stream.of(10, 11, 12, 13, 14);
Stream<Integer> result = Stream.concat(stream1, stream2);

4. Lọc dữ liệu

6 phương thức khác tạo luồng dữ liệu mới, cho phép bạn kết hợp các luồng thành chuỗi (hoặc đường dẫn) có độ phức tạp khác nhau.

Stream<T> filter(Predicate<T>)phương pháp

Phương thức này trả về một luồng dữ liệu mới lọc luồng dữ liệu nguồn theo quy tắc đã thông qua . Phương thức này phải được gọi trên một đối tượng có kiểu là Stream<T>.

Bạn có thể chỉ định quy tắc lọc bằng cách sử dụng hàm lambda , sau đó trình biên dịch sẽ chuyển đổi thành Predicate<T>đối tượng.

Ví dụ:

luồng xích Giải trình
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Chỉ giữ lại các số nhỏ hơn ba
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Chỉ giữ lại các số lớn hơn 0

Stream<T> sorted(Comparator<T>)phương pháp

Phương thức này trả về một luồng dữ liệu mới sắp xếp dữ liệu trong luồng nguồn . Bạn chuyển vào một bộ so sánh , đặt quy tắc so sánh hai phần tử của luồng dữ liệu.

Stream<T> distinct()phương pháp

Phương thức này trả về một luồng dữ liệu mới chỉ chứa các phần tử duy nhất trong luồng dữ liệu nguồn . Tất cả dữ liệu trùng lặp sẽ bị loại bỏ. Ví dụ:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.distinct(); // 1, 2, 3, 4, 5

Stream<T> peek(Consumer<T>)phương pháp

Phương thức này trả về một luồng dữ liệu mới , mặc dù dữ liệu trong đó giống như trong luồng nguồn. Nhưng khi phần tử tiếp theo được yêu cầu từ luồng, hàm bạn đã truyền cho peek()phương thức sẽ được gọi cùng với phần tử đó.

Nếu bạn truyền hàm System.out::printlncho peek()phương thức, thì tất cả các đối tượng sẽ được hiển thị khi chúng đi qua luồng.

Stream<T> limit(int n)phương pháp

Phương thức này trả về một luồng dữ liệu mới chỉn chứa các phần tử đầu tiên trong luồng dữ liệu nguồn . Tất cả các dữ liệu khác bị loại bỏ. Ví dụ:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.limit(3); // 1, 2, 3

Stream<T> skip(int n)phương pháp

Phương thức này trả về một luồng dữ liệu mới chứa tất cả các phần tử giống như luồng nguồn , nhưng bỏ qua (bỏ qua) các phần tử đầu tiên n. Ví dụ:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.skip(3); // 4, 5, 2, 2, 2, 3, 4