Как я понимаю поток Java8

Java

Раньше я читал много статей, посвященных Java8 Stream, но когда я впервые столкнулся со мной, это было действительно сложно понять (у меня низкий уровень понимания). чувствовал, что понял все, чего не понимал раньше. Поэтому я решил использовать простой способ вспомнить, что я думаю о java8 Stream.

лямбда-выражение

грамматика

Лямбда-выражение является краеугольным камнем Stream API, поэтому, если вы хотите научиться использовать Stream API, вы должны сначала понять лямбда-выражение. Вот краткий обзор лямбда-выражения.

Мы часто видим такой код

Arrays.sort(new Integer[]{1, 8, 7, 4}, new Comparator<Integer>() {
   @Override
   public int compare(Integer first, Integer second) {
       return first.compareTo(second);
   }
});

Приведенный выше способ написания заключается в использовании анонимных классов Мы часто используем анонимные классы, потому что мы запускаем их только один раз и не хотим, чтобы они существовали вечно. Хоть и говорят, что лямбда-выражения для так называемого функционального программирования, и все к ним призывают в сообществе, но на мой взгляд, это для удобства (воровство) и удобства (лень).

Приведенный выше код написан с трудом, но как насчет преобразования его в следующий?

Arrays.sort(new Integer[]{1, 8, 7, 4},
	(first,second) -> first.compareTo(second));

Это выглядит так освежающе, и некоторые ненужные детали заблокированы. Для интерфейса, содержащего только один абстрактный метод, можно создать объект интерфейса через лямбда-интерфейс, который называется функциональным интерфейсом.

лямбда-выражения вводят новый оператор:->, который делит лямбда-выражение на две части

(n) -> n*n

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

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

(n) -> {
	if( n <= 10) 
		return n*n;
	return n * 10;
}

Ссылки на методы и конструкторы

ссылка на метод

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

button.setOnAction(event -> System.out.println(event));

Я хочу быть ленивым в это время, я не хочу писать параметр события, потому что есть только один параметр, не может ли jvm мне помочь? Ниже приведен модифицированный код


button.setOnAction(System.out::println);

выражениеSystem.out::printlnссылка на метод, эквивалентная лямбда-выражениюx -> System.out.println(x). Оператор **::** отделяет имя метода от имени объекта или класса. Существует три основных варианта использования:

  1. объект :: метод экземпляра
  2. класс:: статический метод
  3. класс:: метод экземпляра

В первых двух случаях ссылка на метод эквивалентна лямбда-выражению, предоставляющему параметры метода. НапримерMath::pow ==== (x,y) -> Math.pow(x,y).

В третьем случае первым параметром будет называться объект, который выполняет метод. НапримерString::compareToIgnoreCase ==== (x,y) -> x.compareToIgnoreCase(y).

а такжеthis::equals ==== x -> this.equals(x),super::equals ==== super.equals(x).

Ссылка на конструктор

List<String> strList = Arrays.asList("1","2","3");
Stream<Integer> stream =  strList.stream().map(Integer::new);

код вышеInteger::newЭто ссылка на конструктор, разница в том, что имя метода в ссылке на конструктор новое. Если имеется несколько конструкторов, компилятор сделает вывод из контекста и найдет подходящий.

StreamAPI

Слово Stream переводится как значение потока, течения ручьев, потока воды.

Stream

На мой взгляд, ручей как на картинке выше.Исходные данные - мелкие капли воды.После обработки различными "перехватчиками" часть мелких капель воды отбрасывается, часть становится крупнее, часть окрашивается, а часть становится треугольники. В итоге все они превращаются в цветные круги. Наконец, мы помещаем его в набор результатов. Мы часто пишем такой код: просматриваем коллекцию, затем оцениваем или преобразовываем элементы коллекции и добавляем те, которые соответствуют условиям, в новую коллекцию.Этот метод обработки такой же, как на рисунке выше. Первый взгляд на кусок кода

Map<String,Map<String,Integer>> resultMap = new HashMap<>();
Map<String,Integer> maleMap = new HashMap<>();
Map<String,Integer> femaleMap = new HashMap<>();

resultMap.put("male", maleMap);
resultMap.put("female",femaleMap);

for(int i = 0; i < list.size(); i++) {
    Person person = list.get(i);
    String gender = person.getGender();
    String level = person.getLevel();
    switch (gender) {
        case "male":
            Integer maleCount;
            if("gold".equals(level)) {
                maleCount = maleMap.get("gold");
                maleMap.put("gold", null != maleCount ? maleCount + 1 : 1);
            } else if("soliver".equals(level)){
                maleCount = maleMap.get("soliver");
                maleMap.put("soliver", null != maleCount ? maleCount + 1 : 1);
            }
            break;

        case "female":
            Integer femaleCount;
            if("gold".equals(level)) {
                femaleCount = femaleMap.get("gold");
                femaleMap.put("gold", null != femaleCount ? femaleCount + 1 : 1);
            } else if("soliver".equals(level)){
                femaleCount = femaleMap.get("soliver");
                femaleMap.put("soliver", null != femaleCount ? femaleCount + 1 : 1);
            }
            break;

    }
}

Функция вышеприведенного кода — подсчет количества инженеров разного пола.До выхода Java Stream API такие похожие бизнес-коды должны были быть повсюду в системе.Мне потребовалось около двух минут, чтобы набрать приведенный выше код вручную , я украл ленивый

Map<String,Map<String,Integer>> result = list.stream().collect(
    Collectors.toMap(
	    person -> person.getGender(),
	    person -> Collections.singletonMap(person.getLevel(), 1),
	    (existValue,newValue) -> {
	        HashMap<String,Integer> newMap = new HashMap<>(existValue);
	        newValue.forEach((key,value) ->{
	            if(newMap.containsKey(key)) {
	                newMap.put(key, newMap.get(key) + 1);
	            } else {
	                newMap.put(key, value);
	            }
	        });
	        return newMap;
	    })
);

Или изменить на этот код

Map<String,Map<String,Integer>> result =  stream.collect(
    Collectors.groupingBy(
        Person::getGender, 
        Collectors.toMap(
            person->person.getLevel(), 
            person -> 1,
            (existValue,newValue) -> existValue + newValue
        )
    )
);

Не только блоки кода намного меньше, но и логика стала чище. Это действительно круто — использовать поток некоторое время, и всегда весело использовать его все время.

Как поток, Stream может быть ограниченным или бесконечным.Конечно, мы используем самый ограниченный поток (ибо цикл - это ограниченный поток).Как и на картинке выше, мы можем выполнять различные операции над элементами в потоке.Общая обработка. Общая обработка, такая как суммирование, фильтрация, группировка, максимальное, минимальное и т. д., поэтому начните использовать Stream прямо сейчас.

Особенности потока

  1. Сам поток не хранит элементы, элементы могут храниться в базовой коллекции или создаваться.
  2. Потоковые операторы не изменяют исходный объект, вместо этого они возвращают поток, содержащий новый объект.
  3. Потоковые операторы выполняются лениво и могут не выполняться до тех пор, пока не понадобится результат.

Stream API

функциональный интерфейс Тип параметра возвращаемый тип имя абстрактного метода описывать Другие методы
Runnable никто void run Выполнить операцию без аргументов и возвращаемого значения никто
Supplier<T> никто T get предоставить значение типа T
Counsumer<T> T void accept обрабатывать значение типа T chain
BiConsumer<T,U> T,U void accept Обработка значений типа T и типа U chain
Function<T,R> T R apply функция с параметром типа T compose,andThen,identity
BiFunction<T,U,R> T,U R apply функция с параметрами типа T и U andThen
UnaryOperator<T> T T apply унарная операция над типом T compose,andThen,identity
BinaryOperator<T> T,T T apply бинарная операция над типом T andThen
Predicate<T> T boolean test Функция, которая вычисляет логическое значение And,or,negate,isEqual
BiPredicate<T,U> T,U boolean test Функция, которая принимает два аргумента и вычисляет логическое значение. and,or,negate

Разница между map() и flatMap()

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

Stream<String[]>	-> flatMap ->	Stream<String>
Stream<Set<String>>	-> flatMap ->	Stream<String>
Stream<List<String>>	-> flatMap ->	Stream<String>
Stream<List<Object>>	-> flatMap ->	Stream<Object>

{{1,2}, {3,4}, {5,6} } -> flatMap -> {1,2,3,4,5,6}

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

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

как отлаживать

  1. Пожалуйста, используйте фрагменты кода, такие какIntStream.of(1,2,3,4,5).fiter(i -> {return i%2 == 0;})Просто нажмите точку останова на сегменте кода.
  2. Эталонный метод также можно отладить, установив точку останова в isDouble, напримерIntStream.of(1,2,3,4,5).fiter(MyMath::isDouble)

API, которые плохо изучены

  1. уменьшать() Как мы делали накопление раньше?

int sum = 0;
for(int value in values) {
	sum = sum + value;
}

Теперь измените способ потока для достижения

values.stream().reduce(Integer::sum);

Метод reduce() — это просто бинарная функция: он начинает с первых двух элементов потока и продолжает применять его к другим элементам потока.

Как написать хороший код Stream

Потоковое API разработано для удобства.Данные, которые неудобно обрабатывать на уровне SQL, можно группировать, агрегировать, максимально, минимально, сортировать, суммировать и выполнять другие операции через потоки. Так что не усложняйте, просто напишите. Придет день, когда вы станете достаточно опытны, чтобы писать лаконичный код. Или с этого момента преобразуйте множество циклов for в вашем проекте в потоки.

пример кода

Изначально я хотел написать большой кусок кода для преобразования стиля в потоковый API, но, подумав, понял, что это совершенно не нужно.Я нашел код класса инструмента hutool на github для завершения примера преобразования. (Возможно таким образом улучшить возможности потокового апи)

  1. Подсчитайте, сколько раз появляется каждый элемент (пожалуйста, представьте, как реализован jdk7)
代码效果:[a,b,c,c,c]  -> a:1,b:1,c:3

Arrays.asList("a","b","c","c","c").stream().collect(Collectors.groupingBy(str->str, Collectors.counting()));
  1. Преобразуйте коллекцию в строку с определенным разделителем и добавьте префикс и суффикс (пожалуйста, представьте, как это реализовано в jdk7)
List<String> myList = Arrays.asList("a","b","c","c","c");
myList.stream().collect(Collectors.joining(",","{","}"));

  1. Судя по тому, что список не весь пустой (пожалуйста, представьте, как реализован jdk7)
myList.stream().anyMatch(s -> !s.isEmpty());