Раньше я читал много статей, посвященных 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)
. Оператор **::** отделяет имя метода от имени объекта или класса. Существует три основных варианта использования:
- объект :: метод экземпляра
- класс:: статический метод
- класс:: метод экземпляра
В первых двух случаях ссылка на метод эквивалентна лямбда-выражению, предоставляющему параметры метода. Например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 переводится как значение потока, течения ручьев, потока воды.
На мой взгляд, ручей как на картинке выше.Исходные данные - мелкие капли воды.После обработки различными "перехватчиками" часть мелких капель воды отбрасывается, часть становится крупнее, часть окрашивается, а часть становится треугольники. В итоге все они превращаются в цветные круги. Наконец, мы помещаем его в набор результатов. Мы часто пишем такой код: просматриваем коллекцию, затем оцениваем или преобразовываем элементы коллекции и добавляем те, которые соответствуют условиям, в новую коллекцию.Этот метод обработки такой же, как на рисунке выше. Первый взгляд на кусок кода
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 прямо сейчас.
Особенности потока
- Сам поток не хранит элементы, элементы могут храниться в базовой коллекции или создаваться.
- Потоковые операторы не изменяют исходный объект, вместо этого они возвращают поток, содержащий новый объект.
- Потоковые операторы выполняются лениво и могут не выполняться до тех пор, пока не понадобится результат.
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 по-прежнему является потоком, это промежуточная операция, в противном случае — конечная операция.
как отлаживать
- Пожалуйста, используйте фрагменты кода, такие как
IntStream.of(1,2,3,4,5).fiter(i -> {return i%2 == 0;})
Просто нажмите точку останова на сегменте кода. - Эталонный метод также можно отладить, установив точку останова в isDouble, например
IntStream.of(1,2,3,4,5).fiter(MyMath::isDouble)
API, которые плохо изучены
- уменьшать() Как мы делали накопление раньше?
int sum = 0;
for(int value in values) {
sum = sum + value;
}
Теперь измените способ потока для достижения
values.stream().reduce(Integer::sum);
Метод reduce() — это просто бинарная функция: он начинает с первых двух элементов потока и продолжает применять его к другим элементам потока.
Как написать хороший код Stream
Потоковое API разработано для удобства.Данные, которые неудобно обрабатывать на уровне SQL, можно группировать, агрегировать, максимально, минимально, сортировать, суммировать и выполнять другие операции через потоки. Так что не усложняйте, просто напишите. Придет день, когда вы станете достаточно опытны, чтобы писать лаконичный код. Или с этого момента преобразуйте множество циклов for в вашем проекте в потоки.
пример кода
Изначально я хотел написать большой кусок кода для преобразования стиля в потоковый API, но, подумав, понял, что это совершенно не нужно.Я нашел код класса инструмента hutool на github для завершения примера преобразования. (Возможно таким образом улучшить возможности потокового апи)
- Подсчитайте, сколько раз появляется каждый элемент (пожалуйста, представьте, как реализован 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()));
- Преобразуйте коллекцию в строку с определенным разделителем и добавьте префикс и суффикс (пожалуйста, представьте, как это реализовано в jdk7)
List<String> myList = Arrays.asList("a","b","c","c","c");
myList.stream().collect(Collectors.joining(",","{","}"));
- Судя по тому, что список не весь пустой (пожалуйста, представьте, как реализован jdk7)
myList.stream().anyMatch(s -> !s.isEmpty());