Стримы и коллекции чем-то похожи друг на друга, но у них разное назначение. Коллекции обеспечивают эффективный доступ к одиночным объектам, а стримы, наоборот, для прямого доступа и обработки отдельных элементов не используются. Стримы предназначены для параллельных и последовательных агрегаций, выполняемых через цепочку методов.

Давайте сразу перейдем к примерам.

Для начала создадим класс User — основной класс для наших экспериментов.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
@Data
@AllArgsConstructor
@ToString
public class User {
    private long id;
    private String firstName;
    private String lastName;
    private int age;
    private String nationality;
}

Далее напишем класс Sample, в котором будем запускать наш код.

import java.util.Arrays;
import java.util.List;
public class Sample {
    private final List<User> userList = Arrays.asList(
            new User(1, "Michael", "Robert", 37, "TR"),
            new User(2, "Mary", "Patricia", 11, "EN"),
            new User(3, "John", "Michael", 7, "FR"),
            new User(4, "Jennifer", "Linda", 77, "TR"),
            new User(5, "William", "Elizabeth", 23, "US"),
            new User(6, "Sue", "Jackson", 11, "IT"),
            new User(7, "Michael", "Tommy", 37, "EN")
);
    
    public static void main(String... args) {
        Sample sample = new Sample();
    }
}

1. Перебор всех элементов userList с помощью forEach() и вывод их в консоль.

public static void main(String... args) {
    Sample sample = new Sample();
    sample.test1();
}
private void test1() {
    System.out.println("Test 1");
   userList.stream()
           .forEach(System.out::println);
}

Результат:

Test 1
User(id=1, firstName=Michael, lastName=Robert, age=37, nationality=TR)
User(id=2, firstName=Mary, lastName=Patricia, age=11, nationality=EN)
User(id=3, firstName=John, lastName=Michael, age=7, nationality=FR)
User(id=4, firstName=Jennifer, lastName=Linda, age=77, nationality=TR)
User(id=5, firstName=William, lastName=Elizabeth, age=23, nationality=US)
User(id=6, firstName=Sue, lastName=Jackson, age=11, nationality=IT)
User(id=7, firstName=Michael, lastName=Tommy, age=37, nationality=EN)

Так как userList — это ArrayList, то элементы выводятся в консоль в порядке добавления их в список.

2. Перебор всех элементов с выполнением некоторой операции над каждым элементом списка и вывод их на консоль.

private void test2() {
     System.out.println("Test 2");
     userList.stream()
           .map(u -> {
              return new User(
                      u.getId(),
                      "X " + u.getFirstName(),
                      "Y " + u.getLastName(),
                      u.getAge() + 10),
                      u.getNationality());
            })
           .collect(Collectors.toList())
           .forEach(System.out::println);
}

Результат:

Test 2
User(id=1, firstName=X Michael, lastName=Y Robert, age=47, nationality=TR)
User(id=2, firstName=X Mary, lastName=Y Patricia, age=21, nationality=EN)
User(id=3, firstName=X John, lastName=Y Michael, age=17, nationality=FR)
User(id=4, firstName=X Jennifer, lastName=Y Linda, age=87, nationality=TR)
User(id=5, firstName=X William, lastName=Y Elizabeth, age=33, nationality=US)
User(id=6, firstName=X Sue, lastName=Y Jackson, age=21, nationality=IT)
User(id=7, firstName=X Michael, lastName=Y Tommy, age=47, nationality=EN)

3. Сортировка списка по свойству age.

private void test3() {
    System.out.println("Test 3");
    userList.stream()
            .sorted(Comparator.comparing(User::getAge))
            .collect(Collectors.toList())
            .forEach(System.out::println);
}

Результат:

Test 3
User(id=3, firstName=John, lastName=Michael, age=7, nationality=FR)
User(id=2, firstName=Mary, lastName=Patricia, age=11, nationality=EN)
User(id=6, firstName=Sue, lastName=Jackson, age=11, nationality=IT)
User(id=5, firstName=William, lastName=Elizabeth, age=23, nationality=US)
User(id=1, firstName=Michael, lastName=Robert, age=37, nationality=TR)
User(id=7, firstName=Michael, lastName=Tommy, age=37, nationality=EN)
User(id=4, firstName=Jennifer, lastName=Linda, age=77, nationality=TR)

4. Сортировка списка по нескольким свойствам: age, firstName, lastName.

private void test4() {
    System.out.println("Test 4");
    userList.stream()
            .sorted(Comparator.comparing(User::getAge)
                .thenComparing(User::getFirstName)
                .thenComparing(User::getLastName))
            .collect(Collectors.toList())
            .forEach(System.out::println);
}

Результат:

Test 4
User(id=3, firstName=John, lastName=Michael, age=7, nationality=FR)
User(id=2, firstName=Mary, lastName=Patricia, age=11, nationality=EN)
User(id=6, firstName=Sue, lastName=Jackson, age=11, nationality=IT)
User(id=5, firstName=William, lastName=Elizabeth, age=23, nationality=US)
User(id=1, firstName=Michael, lastName=Robert, age=37, nationality=TR)
User(id=7, firstName=Michael, lastName=Tommy, age=37, nationality=EN)
User(id=4, firstName=Jennifer, lastName=Linda, age=77, nationality=TR)

5. Вычисление среднего возраста (age) и максимальной длины firstName.

private void test5() {
    System.out.println("Test 5");
    double averageAge = userList.stream()
        .mapToInt(User::getAge)
        .summaryStatistics()
        .getAverage();
    System.out.print("averageAge: " + averageAge);
    int maxFirstNameLenght = userList.stream()
        .mapToInt((value) -> {
            return value.getFirstName().length();
        })
        .summaryStatistics()
        .getMax();
    System.out.println(" maxFirstNameLenght: " + maxFirstNameLenght);
}

Результат:

Test 5
averageAge: 29.0 maxFirstNameLenght: 8

6. Проверка, что у всех User возраст (age) больше 6.

private void test6() {
    System.out.println("Test 6");
    boolean isAllAgesGreaterThan6 = userList.stream()
                .allMatch(user -> user.getAge() > 6);
    System.out.println("isAllAgesGreaterThan6: " +     isAllAgesGreaterThan6);
}

Результат:

Test 6
isAllAgesGreaterThan6: true

7. Проверка, есть ли кто-то с firstName, начинающийся с символа S.

private void test7() {
    System.out.println("Test 7");
    boolean isFirstCharS = userList.stream()
        .anyMatch(user -> user.getFirstName().charAt(0) == 'S');
    System.out.println("isFirstCharS " + isFirstCharS);
}

Результат:

Test 7
isFirstCharS: true

8. Преобразование одной коллекцию в другую.

private void test8() {
    System.out.println("Test 8");
    List<User> list = userList.stream()
                .collect(Collectors.toList());
    Set<User> set = userList.stream()
                .collect(Collectors.toSet());
    List<User> linkedList = userList.stream()
                .collect(Collectors.toCollection(LinkedList::new));
    Map<Long, User> map = userList.stream()
                .collect(Collectors.toMap(user -> user.getId(), user -> user));
}

9. Количество разных национальностей (nationality).

private void test9() {
    long countDifferentNationalites = userList.stream()
                .map(User::getNationality)
                .distinct()
                .count();
    System.out.println("countDifferentNationalites: " + countDifferentNationalites);
}

Результат:

Test 9
countDifferentNationalites: 5

10. User старше 10 лет, у которых первый символ firstName не равен M.

private void test10() {
    System.out.println("Test 10");
userList.stream()
            .filter(p -> (p.getFirstName().charAt(0) != 'M'))
            .filter(p -> (p.getAge() > 10))
            .collect(Collectors.toList())
            .forEach(System.out::println);
}

Результат:

Test 10
User(id=4, firstName=Jennifer, lastName=Linda, age=77, nationality=TR)
User(id=5, firstName=William, lastName=Elizabeth, age=23, nationality=US)
User(id=6, firstName=Sue, lastName=Jackson, age=11, nationality=IT)

Резюмируя, Java Stream — это не структура хранения данных. Стримы передают элементы из источника (какой-либо структуры данных) через конвейер операций и возвращают некоторое значение, не модифицируя источник.

Все примеры находятся в репозитории GitHub.


Приглашаем всех желающих на открытый урок «Переопределение, скрытие, передекларация». Будет рассмотрено переопределение и скрытие методов в Java, а также передекларация и скрытие переменных. Познакомимся с четырьмя правилами, а потом ещё и с пятым. Регистрация здесь.