Подробно объясните Stream API функций Java8.

Java API

предисловие

В Java 8 есть два наиболее важных изменения. первыйЛямбда-выражения, если вы не знаете, вы можете пойти сюдаПодробно объясните лямбда-выражение функций Java8.

ДругойStream API,существуетjava.util.streamВниз

что

Что касается Stream, то во многих местах говорится, что это ключевое абстрактное понятие для обработки коллекций, что слишком абстрактно. Этот поток — не тот поток ввода-вывода, который мы знали раньше, а канал данных для манипулирования последовательностью элементов, сгенерированных источником данных (коллекция, массив и т. д.).Коллекции сосредоточены на данных, потоки сосредоточены на вычислениях..

Какая польза

Это позволяет найти очень сложный набор операций выполнения, фильтрации данных и сопоставления. Stream API с использованием набора данных операций, аналогичных выполнению запросов к базе данных SQL, Stream API можно использовать для параллельного выполнения операций.Короче говоря, Stream API обеспечивает эффективный и простой в использовании способ обработки данных..

Сказав так много, давайте возьмем пример и испытаем его непосредственно.

@Test
public void test() {
    List<Integer> list = Arrays.asList(2, 3, 5, 4, 1, 8, 10, 9, 7, 6);

    // 传统方式
    for (Integer num : list) {
        if (num > 5)
            System.out.println(num);
    }

    System.out.println("-------------");

    // Stream方式
    list.stream()//
            .filter((e) -> e > 5)//
            .forEach(System.out::println);
}

выходной результат

8
10
9
7
6
-------------
8
10
9
7
6

В этом примере я использую числа от 1 до 10 в качестве элементов набора, а затем распечатываю числа больше 5 с помощью традиционной итерации и потока соответственно.

Вам не нужно знать, почему метод Stream делает это, я расскажу об этом позже, просто сначала кратко продемонстрирую набор операций Stream.

Есть также, когда мы работаем в режиме потока, мы можем использоватьцепное программирование, что такое цепочка, то есть несколько операций соединяются без промежуточного объявления переменных. как наш самый знакомыйStringBuffer.

StringBuffer buffer = new StringBuffer();
buffer.append("aaa").append("bbb").append("ccc");

РучейПромежуточная операцияВсе возвращает поток, поэтому вы можете использовать цепное программирование для использования потока, что и для промежуточных операций, мы поговорим об этом позже.

В eclipse тоже есть, можно добавить строчку кода позади//Чтобы предотвратить форматирование цепного программирования в одну строку при форматировании кода, если это не добавлено, вышеприведенное цепное программирование может быть отформатировано следующим образом, что неудобно для чтения.

list.stream().filter((e) -> e > 5).forEach(System.out::println);

как пользоваться

Три шага к использованию потоковых операций

  • Создать поток: получить поток из источника данных (например, коллекции, массива).
  • Промежуточные операции: одна или несколько промежуточных операций, которые обрабатывают данные из источника данных.
  • Завершить операцию: выполнить цепочку промежуточных операций и произвести результат

Создать поток

Создать поток из коллекции

Интерфейс Collection в Java 8 был расширен и теперь предоставляет два метода получения потоков:

  • Stream stream() по умолчанию: возвращает последовательный поток
  • Stream по умолчанию parallelStream() : возвращает параллельный поток

Эти два метода являются методами интерфейса по умолчанию.Если вы не понимаете, вы можете перейти сюда.Подробно объясните метод интерфейса функции Java8 по умолчанию.

Его можно вызвать напрямую следующим образомstream()метод получения потока

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

создать поток из массива

в Java8Arraysстатический методstream()Поток может быть получен из массива:

  • статический поток потока (массив T []): возвращает поток

Существуют также перегруженные формы, которые могут обрабатывать массивы соответствующих примитивных типов:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

пример:

Integer[] nums = new Integer[8];
Stream<Integer> stream = Arrays.stream(nums);

создать поток из значения

Статический метод Stream.of() можно использовать для создания потока путем отображения значений. Он может принимать любое количество аргументов.

  • public static Stream of(T… values): возвращает поток

пример:

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

Создать поток из функции

Может быть создан с использованием статических методов Stream.iterate() и Stream.generate(),неограниченное потоковое вещание.

  • public static Stream iterate(final T seed, final UnaryOperator f)
  • public static Stream generate(Supplier s) :

Так называемый бесконечный поток, как следует из названия, бесконечен.

отiterate()Пример метода, первый параметрseedФактически это начальное значение, а второй параметрUnaryOperatorэкземпляр типа, и этоUnaryOperatorЯвляется функциональным интерфейсом, наследует функциональный интерфейсFunction, так что мы можем напрямую рассматривать его какFunction. Для функциональных интерфейсов вы можете пойти сюдаПодробно объясните лямбда-выражение функций Java8.

Нижеiterate()Конкретные примеры использования метода

@Test
public void test2() {
    Stream.iterate(1, (x) -> x + 1).forEach(System.out::println);
}

Смысл этого кода такой, первый параметр который я указываю это1, значение1В качестве начального значения второй параметр представляет собой лямбда-выражение для созданияUnaryOperatorНа самом деле можно также сказать, чтоFunctioninstance, реализует свой единственный абстрактный метод, который принимает один параметр и возвращает значение этого параметра + 1. Что касаетсяforEach()Он заключается в прекращении операции, о которой речь пойдет позже.

Запустив этот код, вы обнаружите, что консоль выводит числа и не останавливается (Это воплощение бесконечности), и числа всегда растут одинаково, с разницей в 1.

посмотри сноваgenerate()Пример метода, который принимаетSupplierпараметры экземпляра

@Test
public void test2() {
    Stream.generate(() -> Math.random()).forEach(System.out::println);
}

существуетgenerate()В параметре метода я создал лямбда-выражениеSupplierЭкземпляр, функция реализации метода которого должна возвращать случайное число.

Запустив этот код, вы обнаружите, что консоль выводит случайные числа больше или равные 0 и меньше 1.

Промежуточная операция

После создания оттока мы можем выполнять промежуточные операции. Несколько промежуточных операций могут быть связаны для формирования конвейера.Если в конвейере не будет запущена операция завершения, промежуточные операции не будут выполнять никакой обработки! И выполнение всего этого сразу после завершения операции называется «ленивой оценкой», которую я продемонстрирую позже.

Промежуточные операции делятся на четыре категории

  • фильтр
  • резать
  • карта
  • Сортировать

фильтр

Существует два метода фильтрации операций

filter(Predicate p), перениматьPredicateэкземпляр, согласно экземпляруtestспособ фильтрации, например

@Test
public void test() {
    List<Integer> list = Arrays.asList(2, 3, 5, 4, 1, 8, 10, 9, 7, 6);
    list.stream()//
            .filter((e) -> e > 5)//
            .forEach(System.out::println);
}

выходной результат

8
10
9
7
6

Создано с помощью лямбда-выраженийPredicateпример, где(e) -> e > 5являетсяtestРеализация способа, а именно получениеIntegerПараметр типа e, если параметр больше 5, метод проверки возвращает true, иначе false.

distinct(), вы должны увидеть, что этот метод предназначен для дедупликации и удаления повторяющихся элементов в соответствии с hashCode() и equals() элементов, сгенерированных потоком. как

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 4, 4, 5, 6);
    list.stream()//
            .distinct()//
            .forEach(System.out::println);
}

выходной результат

1
2
3
4
5
6

резать

Также есть два способа обрезки

limit(long maxSize), усекает поток, чтобы его элементы не превышали заданное число, как в инструкции SQL. как

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    list.stream()//
            .limit(3)//
            .forEach(System.out::println);
}

выходной результат

1
2
3

skip(long n) , который возвращает поток с удаленными первыми n элементами. Если элемент в потоке
Если меньше n, вернуть пустой поток. как

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
    list.stream()//
            .skip(3)//
            .forEach(System.out::println);
}

выходной результат

4
5
6

Видно, что этот метод просто дополняет метод лимита, лимит идет в конец, а скип идет в начало.

карта

map(Function f) Получать одинFunctionпример,Functionабстрактный методapplyПолучать параметр и возвращать значение, возвращаемое значение является результатом сопоставления. как

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3);
    list.stream()//
            .map(x -> x*x)
            .forEach(System.out::println);
}

выходной результат

1
4
9

Можно обнаружить, что новое значение, отображаемое для каждого элемента в потоке, представляет собой квадрат этого элемента.

mapToDouble(ToDoubleFunction f), за этой функцией следуетmapОчень похоже, но результат отображения должен бытьDoubleтип. как

@Test
public void test() {
    List<Integer> list = Arrays.asList(1, 2, 3);
    list.stream()//
            .mapToDouble(x -> x+0.1)
            .forEach(System.out::println);
}

выходной результат

1.1
2.1
3.1

Его можно найтиIntegerЭлементы типа сопоставляются сDoubleНапечатать

Похожие такжеmapToInt(ToIntFunction f)иmapToLong(ToLongFunction f)метод, который здесь не будет продемонстрирован.

flatMap(Function f), который отображает каждый элемент в потоке в поток. как

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.junit.Test;

public class TestStreamAPI {

    @Test
    public void test() {
        List<String> list = Arrays.asList("abc", "efg", "xyz");
        list.stream()//
                .flatMap(TestStreamAPI::string2Stream)//
                .forEach(System.out::println);
    }

    /**
     * 接收一个字符串将其所以的字符添加到list中然后返回stream
     * @param str
     * @return
     */
    public static Stream<Character> string2Stream(String str) {
        List<Character> list = new ArrayList<>();
        char[] charArray = str.toCharArray();
        for (char c : charArray) {
            list.add(c);
        }
        return list.stream();
    }
}

выходной результат

a
b
c
e
f
g
x
y
z

На самом деле это не сложно понять, так же как и вышеmapToDoubleметод сопоставляет каждый элемент в потоке сDoubleЗначение типа такое же, но теперь оно просто сопоставлено с потоком.

Сортировать

sorted(), который сортирует элементы в потоке в естественном порядке. как

@Test
public void test() {
    List<String> list = Arrays.asList("d", "a", "c");
    list.stream()//
            .sorted()//
            .forEach(System.out::println);
}

выходной результат

a
c
d

sorted(Comparator comp), который сортирует элементы в потоке в порядке сравнения. как

@Test
public void test() {
    List<String> list = Arrays.asList("d", "a", "c");
    list.stream()//
            .sorted((x,y) -> -x.compareTo(y))//
            .forEach(System.out::println);
}

выходной результат

d
c
a

Завершить операцию

Операция завершения выдает результаты из конвейера промежуточных операций потока. Результатом может быть любое значение, не являющееся потоком, например:List,Integer,Четноеvoid.

Существует три типа операций прекращения

  • найти и сопоставить
  • уменьшать
  • собирать

найти и сопоставить

Существует так много способов найти и сопоставить, что я продемонстрирую лишь несколько типичных.

  • allMatch(Predicate p) Проверяет соответствие всех элементов
  • anyMatch(Predicate p) проверяет соответствие хотя бы одному элементу
  • noneMatch(Predicate p) проверяет соответствие не всех элементов
  • findFirst() возвращает первый элемент
  • findAny() возвращает любой элемент в текущем потоке
  • count() возвращает общее количество элементов в потоке
  • max(Comparator c) возвращает максимальное значение в потоке
  • min(Comparator c) возвращает минимальное значение в потоке
  • forEach(Consumer c) Внутренняя итерация (использование интерфейса Collection требует, чтобы пользователь выполнял итерацию, которая называется внешней итерацией. В отличие от этого, Stream API использует внутреннюю итерацию)

allMatch(Predicate p)пример

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    boolean allMatch = list.stream()//
            .allMatch(x -> x > 2);//是否全部元素都大于2
    System.out.println(allMatch);
}

выходной результат

true

findFirst()пример

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Optional<Integer> first = list.stream()//
            .findFirst();
    Integer val = first.get();
    System.out.println(val);//输出10
}

вOptionalЭто контейнерный класс, вы можете проверить это здесьПодробное объяснение дополнительного класса функций Java8

max(Comparator c)пример

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Optional<Integer> first = list.stream()//
            .min(Integer::compareTo);
    Integer val = first.get();
    System.out.println(val);//输出3
}

forEach(Consumer c)Пример выше используется много, и там больше не продемонстрирован.

уменьшать

Операция редукции имеет следующие два метода

  • reduce(T iden, BinaryOperator b) может многократно комбинировать элементы в потоке, чтобы получить значение. вернуть Т
  • reduce(BinaryOperator b) может многократно комбинировать элементы в потоке, чтобы получить значение. возврат Необязательный

reduce(T iden, BinaryOperator b)пример

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Integer result = list.stream()//
        .reduce(2, Integer::sum);
    System.out.println(result);//输出27,其实相当于2+10+5+7+3,就是一个累加
}

reduce(BinaryOperator b)пример

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    Optional<Integer> optional = list.stream()//
        .reduce(Integer::sum);
    Integer result = optional.get();
    System.out.println(result);//输出25,其实相当于10+5+7+3,就是一个累加
}

собирать

collect(Collector c) Преобразование потока в другую форму. получитьCollectorинтерфейс
Реализует метод суммирования элементов в потоке.

Ниже приведены конкретные примеры

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);
    // 将流中元素收集到List中
    List<Integer> resultList = list.stream()//
            .collect(Collectors.toList());

    // 将流中元素收集到Set中
    Set<Integer> resultSet = list.stream()//
            .collect(Collectors.toSet());

    System.out.println(resultList);// 输出[10, 5, 7, 3]
    System.out.println(resultSet);// 输出[3, 5, 7, 10]
}

Приведенный выше код собирает элементы в потоке отдельно вListиSetНа самом деле, его также можно собрать в своем типе, напримерMap,OptionalЧетноеIntegerЖдать. Вы также можете выполнять некоторые другие операции в процессе сбора, например, следующий пример, что касается других операций, перейдите к API.

@Test
public void test() {
    List<Integer> list = Arrays.asList(10, 5, 7, 3);

    //计算出流中的元素个数
    Long count = list.stream()//
            .collect(Collectors.counting());
    System.out.println(count);//输出4

    //计算流中Integer类型数据的总和
    Integer sum = list.stream()//
            .collect(Collectors.summingInt(x -> x));
    System.out.println(sum);//输出25
}

Задержка выполнения потока

В разделе «Промежуточная операция» выше, я говорил о «ленивой оценке», то естьПотоковые операции выполняются лениво. Это означает, что они будут ждать, пока результат не понадобится перед выполнением.

Дайте нам знать с примером.

Сначала подготовьте класс сущностиPerson, обратите внимание, что вgetAge()В методе я вывожу предложение


public class Person {
    private String name;
    private int age;

    public Person(String name,int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        System.out.println("getAge()执行");
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}

затем метод проверки

@Test
public void test() {
    List<Person> list = Arrays.asList(//
            new Person("Jason", 18), //
            new Person("Hank", 46), //
            new Person("Alice", 23));
    // 过滤操作,找出age大于20的Person对象,但是没有终止操作
    list.stream()//
            .filter(x -> x.getAge() > 20);
}

Запустив этот тестовый метод, вы увидите, что ничего не выводится, потому что он не завершает операцию, поэтому промежуточная операция не выполняется. Добавьте еще одну операцию завершения ниже

@Test
public void test() {
    List<Person> list = Arrays.asList(//
            new Person("Jason", 18), //
            new Person("Hank", 46), //
            new Person("Alice", 23));
    // 过滤操作,找出age大于20的Person对象,但是没有终止操作
    list.stream()//
            .filter(x -> x.getAge() > 20)//
            .forEach(System.out::println);
}

выходной результат

getAge()执行
getAge()执行
Person [name=Hank, age=46]
getAge()执行
Person [name=Alice, age=23]

Его можно найтиgetAge()Метод выполняется и находит дваageболее 20Personобъект и вывод