Подпишитесь на официальный аккаунт JavaStorm, чтобы узнать больше интересного
В Java 8 появилась большая новая функция потока лямбда-выражений (Stream), когда поток используется в сочетании с лямбда-выражением, код становится довольно раздражающим и лаконичным.
Супер большой шаг, релиз кода
Если есть требование, информация о счете, запрашиваемая в базе данных, должна быть обработана:
- Снять счета на сумму менее 10 000.
- Отсортируйте отфильтрованные данные.
- Получите отсортированное имя продавца счета-фактуры.
Модель счета
@Builder
@Data
public class Invoice implements Serializable {
/**
* 销方名称
*/
private String saleName;
/**
* 是否作废
*/
private Boolean cancelFlag;
/**
* 开票金额
*/
private BigDecimal amount;
/**
* 发票类型
*/
private Integer type;
/**
* 明细条数
*/
private Integer detailSize;
}
Мы используем традиционный способ реализации, прежде чем мы инициализируем тестовые данные.
public class StreamTest {
private List<Invoice> invoiceList;
@Before
public void initData() {
Invoice invoice = Invoice.builder().amount(BigDecimal.valueOf(100.02)).cancelFlag(false).detailSize(10)
.saleName("广西制药").type(1).build();
Invoice invoice2 = Invoice.builder().amount(BigDecimal.valueOf(89032478.9)).cancelFlag(false).detailSize(2)
.saleName("深圳电子科技").type(1).build();
Invoice invoice3 = Invoice.builder().amount(BigDecimal.valueOf(2077777889)).cancelFlag(true).detailSize(6)
.saleName("宇宙心空").type(1).build();
Invoice invoice4 = Invoice.builder().amount(BigDecimal.valueOf(356.8)).cancelFlag(false).detailSize(10)
.saleName("孟达餐厅").type(2).build();
Invoice invoice5 = Invoice.builder().amount(BigDecimal.valueOf(998.88)).cancelFlag(false).detailSize(0)
.saleName("网红餐厅").type(2).build();
Invoice invoice6 = Invoice.builder().amount(BigDecimal.valueOf(9009884.09)).cancelFlag(false).detailSize(1)
.saleName("机动车").type(3).build();
invoiceList = Stream.of(invoice, invoice2, invoice3, invoice4, invoice5, invoice6).collect(Collectors.toList());
System.out.println("原始数据:" + invoiceList.toString());
}
Реализация до Java 8
/**
* 筛选出金额小于 10000 的发票,根据金额排序,获取排序后的销方名称列表
*/
@Test
public void testJava7() {
ArrayList<Invoice> lowInvoiceList = new ArrayList<>();
//筛选出 金额小于 10000 的发票
for (Invoice invoice: invoiceList) {
if (invoice.getAmount().compareTo(BigDecimal.valueOf(10000)) < 0) {
lowInvoiceList.add(invoice);
}
}
// 对筛选出的发票排序
lowInvoiceList.sort(new Comparator<Invoice>() {
@Override
public int compare(Invoice o1, Invoice o2) {
return o1.getAmount().compareTo(o2.getAmount());
}
});
// 获取排序后的销方名字
ArrayList<String> nameList = new ArrayList<>();
for (Invoice invoice : lowInvoiceList) {
nameList.add(invoice.getSaleName());
}
}
Операция высокомерия после Java8 завершается за один раз. Больше не нужно работать сверхурочно, чтобы писать вонючий и длинный код
@Test
public void testJava8() {
List<String> nameList = invoiceList.stream()
.filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000)) < 0)// 过滤数据
.sorted(Comparator.comparing(Invoice::getAmount))// 对金额升序排序
.map(Invoice::getSaleName)//提取名称
.collect(Collectors.toList());//转换成list
}
Набор ощущений службы дракона, отправляющий вас в небо одним махом. Значительно сокращает объем кода.
Теперь другая потребность
Классифицируйте запрошенные данные счета-фактуры и верните данные Map
.
Оглядываясь назад на метод написания Java7, есть ли какой-нибудь, который я стираю, что слишком хлопотно. Могу ли я уйти с работы пораньше и вернуться, чтобы обнять свою девушку?
@Test
public void testGroupByTypeJava7() {
HashMap<Integer, List<Invoice>> groupMap = new HashMap<>();
for (Invoice invoice : invoiceList) {
//存在则追加
if (groupMap.containsKey(invoice.getType())) {
groupMap.get(invoice.getType()).add(invoice);
} else {
// 不存在则初始化添加
ArrayList<Invoice> invoices = new ArrayList<>();
invoices.add(invoice);
groupMap.put(invoice.getType(), invoices);
}
}
System.out.println(groupMap.toString());
}
Затем мы используем код операции Sao потока для достижения вышеуказанных требований.
группировкаПо группировке
@Test
public void testGroupByTypeJava8() {
Map<Integer, List<Invoice>> groupByTypeMap = invoiceList.stream().collect(Collectors.groupingBy(Invoice::getType));
}
Это так просто и грубо, что одна строка кода идет прямо в Huanglong.
Что такое поток?
Поток представляет собой очередь элементов из источника данных и поддерживает операции агрегации, не является структурой данных и не хранит данные, его основное назначение — расчет.
Элементы — это объекты определенного типа, образующие очередь. Потоки в Java не хранят элементы, а вычисляют по требованию. Источник потока источника данных. Может быть коллекцией, массивом, каналом ввода-вывода, генератором и т. д. Операции агрегирования аналогичны операциям в операторах SQL, таким как фильтрация, сопоставление, сокращение, поиск, сопоставление, сортировка и т. д. В отличие от предыдущих операций Collection, у операций Stream есть две основные характеристики:
- Конвейерная обработка: Промежуточные операции возвращают сам объект потока. Таким образом, несколько операций могут быть объединены в конвейер, как в свободном стиле. Это позволяет оптимизировать такие операции, как ленивость и короткое замыкание.
- Внутренняя итерация: ранее обход коллекции выполнялся с помощью Iterator или For-Each, который явно выполняет итерацию за пределами коллекции, что называется внешней итерацией. Stream предоставляет внутренний метод итерации, который реализуется через шаблон посетителя (Visitor).
Как создать поток
Есть в основном пять способов
1. Генерируется коллекцией
Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();
2. Генерируется массивом
int[] intArr = new int[]{1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(intArr);
Поток генерируется методом Arrays.stream, а поток, сгенерированный этим методом, является числовым потоком [то есть IntStream] вместоStream<Integer>
. Добавьте, что использование числовых потоков позволяет избежать распаковки во время вычислений и повысить производительность.
Stream API предоставляет три способа mapToInt, mapToDouble, mapToLong для преобразования потока объектов [т.е. Stream] в соответствующий числовой поток, а также предоставляет упакованный метод для преобразования числового потока в поток объектов.
3. Генерация по значению
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Поток генерируется методом of Stream, а пустой поток может быть сгенерирован пустым методом Stream.
4. Создать из файла
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
Поток получается с помощью метода Files.line, и каждый полученный поток является строкой в заданном файле.
5. Генерируется функцией,Два статических метода выполняют итерацию и генерируют потоки из функций.
iterator: метод iterate принимает два параметра, первый — значение инициализации, а второй — выполняемая функция, потому что поток, сгенерированный итератором, является бесконечным потоком, поток усекается методом limit, и только Генерируются 5 четных чисел.
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5);
генератор: принимает один параметр, тип параметра метода — Поставщик, который предоставляет значение для потока. Поток, сгенерированный generate, также является бесконечным потоком, поэтому поток усекается по limit
Stream<Double> stream = Stream.generate(Math::random).limit(5);
тип операции потока
В основном делится на два типа
1. Промежуточная операция
За потоком может следовать ноль или более промежуточных операций. Его цель в основном состоит в том, чтобы открыть поток, выполнить некоторую степень отображения/фильтрации данных, а затем вернуть новый поток для использования следующей операцией.
Этот тип операции является ленивым, и только вызов этого типа метода на самом деле не запускает обход потока.Настоящий обход должен дождаться терминальной операции.Обычные промежуточные операции включают фильтр, карту и т. д., которые будут представлены ниже.
2. Работа терминала
Поток имеет одну и только одну терминальную операцию.При выполнении этой операции поток закрывается и больше не может управляться.Поэтому поток можно пройти только один раз.Если вы хотите пройти, вам нужно сгенерировать поток через исходные данные. Выполнение терминальной операции действительно запустит обход потока. Такие, как подсчет, сбор и т. д., которые будут представлены ниже.
Промежуточный операционный API
фильтрфильтр
Stream<Invoice> invoiceStream = invoiceList.stream().filter(invoice -> invoice.getDetailSize() < 10);
отличительный удаляет повторяющиеся элементы
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().distinct();
limit возвращает указанное количество потоков
Stream<Invoice> invoiceStream = invoiceList.stream().limit(3);
Укажите количество возвращаемых потоков через метод limit.Значение параметра limit должно быть >= 0, иначе будет выброшено исключение
skip пропускает элементы в потоке
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = integerList.stream().skip(2);
Пропустите элементы в потоке с помощью метода skip. В приведенном выше примере пропускаются первые два элемента, поэтому результат печати равен 2, 3, 4, 5. Значение параметра skip должно быть >= 0, иначе будет выдано исключение. .
карта потока карта
Так называемое отображение потока заключается в сопоставлении принятого элемента с другим элементом.
List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");
Stream<Integer> stream = stringList.stream().map(String::length);
Сопоставление может быть выполнено с помощью метода map.В этом примере завершается сопоставление String -> Integer.В предыдущем примере завершено сопоставление Invoice->String с помощью метода map.
Преобразование потока flatMap
Преобразование каждого значения в одном потоке в другой
List<String> wordList = Arrays.asList("Hello", "World");
List<String> strList = wordList.stream()
.map(w -> w.split(""))// 将元素根据 空格分隔字符的Stream<String[]>
.flatMap(Arrays::stream)// 将Stream<String[]> 转换成 Stream<String>
.distinct() //去重
.collect(Collectors.toList());
System.out.println(strList.toString());
Возвращаемое значение map(w -> w.split(" ")) равноStream<String[]>
, мы хотим получитьStream<String>
, преобразование Stream -> Stream можно выполнить методом flatMap. Таким образом, окончательный результат печати[H, e, l, o, W, r, d]
совпадение элементов
- allMatch соответствует всем
if (invoiceList.stream().allMatch(Invoice::getCancelFlag)) {
System.out.println("发票全是作废");
}
- anyMatch соответствует одному из
Распечатать, если есть недействительные счета-фактуры
if (invoiceList.stream().anyMatch(Invoice::getCancelFlag)) {
System.out.println("存在作废发票");
}
Эквивалентно
for (Invoice invoice : invoiceList) {
if (invoice.getCancelFlag()) {
System.out.println("存在作废发票");
break;
}
}
- noneMatch не соответствует всем
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
if (integerList.stream().noneMatch(i -> i > 3)) {
System.out.println("值都小于3");
}
терминальная операция
подсчитать количество элементов в потоке
- использовать количество
long count = invoiceList.stream()
.filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000)) < 0)
.count();
- использовать счет
long count = invoiceList.stream()
.filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000)) < 0)
.collect(Collectors.counting());
Последний метод подсчета элементов особенно полезен при использовании в сочетании с сбором.
найти
- findFirst найти первый
Optional<Invoice> first = invoiceList.stream()
.filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000)) < 0)
.findFirst();
Найдите первый элемент с количеством меньше 10000 с помощью findFirst
- findAny находит случайный
Optional<Invoice> any = invoiceList.stream()
.filter(item -> item.getAmount().compareTo(BigDecimal.valueOf(10000)) < 0)
.findAny();
Найдите один из элементов меньше 10000 с помощью метода findAny и выведите его.Из-за внутренней оптимизации он заканчивается, когда будет найден первый элемент, который удовлетворяет более чем трем.Результат этого метода такой же, как и результат метода findFirst . Метод findAny предназначен для более эффективного использования параллельных потоков, а метод findFirst имеет больше ограничений на параллелизм [в этой статье параллельные потоки не рассматриваются]
сокращение объединяет элементы в потоке
Предположим, мы суммируем значения в наборе
До jdk8
int sum = 0;
for (int i : integerList) {
sum += i;
}
После того, как jdk8 обработан с помощью сокращения
int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
//还可以用方法引用写
int sum = integerList.stream().reduce(0, Integer::sum);
Например, сумма статистики суммы счета
BigDecimal reduce = invoiceList.stream().map(Invoice::getAmount).reduce(BigDecimal.ZERO, (a, b) -> (a.add(b)));
Продолжайте использовать упрощение ссылки на метод
BigDecimal reduce = invoiceList.stream().map(Invoice::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
reduce принимает два параметра, начальное значение здесь равно 0, aBinaryOperator<T> accumulator
объединить два элемента для получения нового значения,
Кроме того, метод сокращения имеет перегруженный метод без значения инициализации.
Получить минимальное и максимальное значения в потоке
Получить мин и макс по мин/макс
Optional<BigDecimal> min = invoiceList.stream().map(Invoice::getAmount).min(BigDecimal::compareTo);
Optional<BigDecimal> max = invoiceList.stream().map(Invoice::getAmount).max(BigDecimal::compareTo);
также можно записать как
OptionalInt min1 = invoiceList.stream().mapToInt(Invoice::getDetailSize).min();
OptionalInt max1 = invoiceList.stream().mapToInt(Invoice::getDetailSize).max();
min получает минимальное значение в потоке, max получает максимальное значение в потоке, а параметры методаComparator<? super T> comparator
Получить минимум и максимум по minBy/maxBy
invoiceList.stream().map(Invoice::getAmount).collect(Collectors.minBy(BigDecimal::compareTo)).get();
Получите минимум и максимум, уменьшив
Optional<BigDecimal> max = invoiceList.stream().map(Invoice::getAmount).reduce(BigDecimal::max);
сумма
по summingInt
Integer sum = invoiceList.stream().collect(Collectors.summingInt(Invoice::getDetailSize));
Если тип данных double, long, то суммирование методами summingDouble, summingLong
путем уменьшения
Integer sum = invoiceList.stream().map(Invoice::getDetailSize).reduce(0, Integer::sum);
По сумме, лучший способ написать
//推荐写成
Integer sum = invoiceList.stream().mapToInt(Invoice::getDetailSize).sum();
В приведенном выше суммировании, максимальном значении и минимальном значении существуют разные методы для выполнения одной и той же операции. Вы можете выбрать методы сбора, уменьшения, минимума/максимума/суммы, и рекомендуются методы минимума, максимума и суммы. Потому что он самый лаконичный и удобный для чтения, и в то же время преобразует поток объектов в числовой поток через mapToInt, избегая операций упаковки и распаковки.
Среднее по усреднениюInt
Double avg = invoiceList.stream().collect(Collectors.averagingInt(Invoice::getDetailSize));
Если тип данных double, long, усреднение по averagingDouble, averagingLong
Для BigDecimal вам нужно сначала суммировать, а затем разделить на общее количество баров.
List<BigDecimal> sumList = invoiceList.stream().map(Invoice::getAmount).collect(Collectors.toList());
BigDecimal average = average(sumList, RoundingMode.HALF_UP);
// 求平均值
public BigDecimal average(List<BigDecimal> bigDecimals, RoundingMode roundingMode) {
BigDecimal sum = bigDecimals.stream()
.map(Objects::requireNonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return sum.divide(new BigDecimal(bigDecimals.size()), roundingMode);
}
Одновременная сумма, среднее, максимальное, минимальное значение по summarizingInt
IntSummaryStatistics statistics = invoiceList.stream().collect(Collectors.summarizingInt(Invoice::getDetailSize));
double average1 = statistics.getAverage();
int max1 = statistics.getMax();
int min1 = statistics.getMin();
long sum = statistics.getSum();
Обход элемента с помощью foreach
invoiceList.forEach(item -> {
System.out.println(item.getAmount());
});
Объединяйте элементы в потоке путем объединения
String result = invoiceList.stream().map(Invoice::getSaleName).collect(Collectors.joining(", "));
Группировать по группировкеПо
Map<Integer, List<Invoice>> groupByTypeMap = invoiceList.stream().collect(Collectors.groupingBy(Invoice::getType));
В методе Collect, пройдите в Groupingby для группировки, где параметр метода GroupingBy является функцией классификации. Вы также можете использовать Groupingby для многоуровневой классификации, вложенные
Map<String, Map<String, List<RzInvoice>>> = invoiceList.stream().collect(Collectors.groupingBy(Invoice::getType, Collectors.groupingBy(invoice -> {
if (invoice.getAmount().compareTo(BigDecimal.valueOf(10000)) <= 0) {
return "low";
} else if (invoice.getAmount().compareTo(BigDecimal.valueOf(80000)) <= 0) {
return "mi";
} else {
return "high";
}
})));
Сначала сгруппируйте по типу счета, а затем сгруппируйте по сумме счета. Тип возвращаемых данных: Map
Расширенное разбиение с помощью partitioningBy
Специальная группировка, она классифицируется в соответствии с истинным и ложным, поэтому возвращаемые результаты можно разделить не более чем на две группы.
Map<Boolean, List<Dish>> = invoiceList.stream().collect(Collectors.partitioningBy(RzInvoice::getCancelFlag));
Эквивалентно
Map<Boolean, List<Dish>> = invoiceList.stream().collect(Collectors.groupingBy(RzInvoice::getCancelFlag));
Этот пример может не увидеть разницы между партиционированием и классификацией и даже подумать, что партиционирование вообще не нужно, давайте изменим очевидный пример:
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Map<Boolean, List<Integer>> result = integerList.stream().collect(partitioningBy(i -> i < 3));
Ключ возвращаемого значения по-прежнему логического типа, но его классификация основана на диапазоне, и партиция больше подходит для обработки классификации по диапазону.
Вот пример, с которым я столкнулся в своей работе.
// 过滤T-1至T-12 近12月数据,根据省份分组求和开票金额,使用金额进行倒序,产生LinkedHashMap
LinkedHashMap<String, BigDecimal> areaSortByAmountMaps =
invoiceStatisticsList.stream().filter(FilterSaleInvoiceUtil.filterSaleInvoiceWithRange(1, 12, analysisDate)) //根据时间过滤数据
.collect(Collectors.groupingBy(FkSalesInvoiceStatisticsDO::getBuyerAdministrativeAreaCode
, Collectors.reducing(BigDecimal.ZERO, FkSalesInvoiceStatisticsDO::getInvoiceAmount, BigDecimal::add)))// 根据开票地区分组,并同时将每个分组数据的开票金额求和
.entrySet().stream().sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed()) // 根据金额大小倒序
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new)); //收集数据生成LinkedHashMap
Суммировать
Используя Stream API, вы можете упростить код и улучшить его читаемость, чтобы вы могли быстро использовать его в проекте. Честно говоря, до того, как я изучил Stream API, любой, кто написал много Lambda и Stream API в моем приложении, хотел дать ему пинка.
Думаю, теперь я могу в него влюбиться [хи-хи]. Будьте осторожны, чтобы не смешивать декларативное и императивное программирование при их одновременном использовании.