使用Java8進行分組(多個字段的組合分組)
在SQL中經(jīng)常會用到分組,我們也常常遇到一些組合分組的場景。
有下面的一個User類
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Builder
public class User {
private String name;
private int id;
private String city;
private String sex;
private LocalDateTime birthDay;
}
java8分組 傳統(tǒng)寫法(單個字段分組)
場景:根據(jù) 城市 進行分組
使用的是方法引用:User::getCity 來完成分組
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Demo2 {
public static void main(String[] args) {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// data list
List<User> userList = Arrays.asList(
User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build()
);
Map<String, List<User>> groupMap = userList.stream()
.collect(Collectors.groupingBy(User::getCity));
groupMap.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
}
}
java8分組 傳統(tǒng)寫法(多個字段分組)
① 場景:根據(jù) 城市,性別進行分組
一般的寫法會是下面的這種寫法,通過lambda表達(dá)式將key的生成邏輯傳入進去:u -> u.getCity() + "|" + u.getSex() 來實現(xiàn)分組的效果。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Demo2 {
public static void main(String[] args) {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// data list
List<User> userList = Arrays.asList(
User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build()
);
Map<String, List<User>> groupMap = userList.stream()
.collect(Collectors.groupingBy(u -> u.getCity() + "|" + u.getSex()));
groupMap.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
}
}
分析:多個分組條件 與 單個分組條件 兩種寫法
單個條件的分組用的比較多,userList.stream().collect(Collectors.groupingBy(User::getCity));
這種方法引用的方式看起來很清爽。
在我們遇到多個字段的分組的時候,我并不太想使用前面那種傳統(tǒng)的寫法①。
我在想,既然單個字段的分組寫法是:
userList.stream().collect(Collectors.groupingBy(User::getCity));
那么多個字段的寫法可否是下面這種( 類推 ),傳入多個方法引用!
userList.stream().collect(Collectors.groupingBy(User::getCity,User::getSex));
很可惜 jdk 類庫中Collectors 沒有提供這種寫法
多個字段的優(yōu)雅寫法
因為jdk沒有提供這種寫法,于是自己就想寫了一個Util來幫助我們使用多個方法引用的方式完成組合分組
MyBeanUtil groupingBy(userList, User::getSex, User::getCity);
Demo:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class MyBeanUtil {
public static void main(String[] args) {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// data list
List<User> userList = Arrays.asList(
User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build()
);
// 進行分組,根據(jù)名字和城市分組
Map<String, List<User>> groupMap = groupingBy(userList, User::getSex, User::getCity);
//打印分組結(jié)果
groupMap.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
}
/**
* 將數(shù)據(jù)分組,根據(jù)方法引用(bean的get方法)
*
* @param list 為分組的數(shù)據(jù)
* @param functions get方法數(shù)組
*/
@SafeVarargs
public static <T, R> Map<String, List<T>> groupingBy(List<T> list, Function<T, R>... functions) {
return list.stream().collect(Collectors.groupingBy(t -> groupingBy(t, functions)));
}
/**
* 分組工具根據(jù)函數(shù)式接口使用分組,將數(shù)據(jù)根據(jù)分組結(jié)果進行拆分
*/
@SafeVarargs
public static <T, R> String groupingBy(T t, Function<T, R>... functions) {
if (functions == null || functions.length == 0) {
throw new NullPointerException("functions數(shù)組不可以為空");
} else if (functions.length == 1) {
return functions[0].apply(t).toString();
} else {
return Arrays.stream(functions).map(fun -> fun.apply(t).toString()).reduce((str1, str2) -> str1 + "|" + str2).get();
}
}
}
再度優(yōu)化
依然不是很滿足這種寫法,因為這種寫法需要借助 Util 類,不夠接地氣!
我更希望是下面這種接地氣的寫法:能夠完全集成在jdk類庫中
userList.stream().collect(Collectors.groupingBy(User::getCity,User::getSex));
為了達(dá)到上述的效果,那么顯然我們是需要修改jdk源代碼的;
于是我就將java.util.stream.Collectors源碼完整copy出來,然后加入下面3個方法
public static <T, K> Collector<T, ?, HashMap<K, List<T>>>
groupingBy(Function<? super T, ? extends K>... classifier) {
return groupingBy("|", classifier);
}
public static <T, K> Collector<T, ?, HashMap<K, List<T>>>
groupingBy(String split, Function<? super T, ? extends K>... classifier) {
return groupingBy(split, classifier, HashMap::new, toList());
}
public static <T, K, D, A, M extends Map<? super K, D>>
Collector<T, ?, M> groupingBy(String split,
Function<? super T, ? extends K>[] classifierArr,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
String key = Arrays.stream(classifierArr).map(classifier -> Objects.requireNonNull(classifier.apply(t))).map(String::valueOf).reduce((s1, s2) -> s1 + split + s2).get();
A container = m.computeIfAbsent((K) key, k -> downstreamSupplier.get());
downstreamAccumulator.accept(container, t);
};
BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
@SuppressWarnings("unchecked")
Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;
if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
} else {
@SuppressWarnings("unchecked")
Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
Function<Map<K, A>, M> finisher = intermediate -> {
intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
@SuppressWarnings("unchecked")
M castResult = (M) intermediate;
return castResult;
};
return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);
}
}
就達(dá)到了我們預(yù)期的效果,為了方便大家也一起體驗一下,我已經(jīng)將demo完整的放到了github上
源碼地址:https://github.com/1015770492/CollectorsDemo
下載好源碼后,找到下面這個類

Demo:
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class MultiGroupByDemo {
public static void main(String[] args) {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// data list
List<User> userList = Arrays.asList(
User.builder().id(123456).name("Zhang, San").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(777777).name("Zhang, San").city("ShangHai").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(888888).name("Li, Si").city("ShangHai").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(999999).name("Zhan, San").city("HangZhou").sex("woman").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build(),
User.builder().id(555555).name("Li, Si").city("NaJin").sex("man").birthDay(LocalDateTime.parse("2022-07-01 12:00:00", df)).build()
);
/*
* maybe we can
*/
// 1.Use the default vertical separator
System.out.println("Use the default vertical separator:");
HashMap<String, List<User>> defaultSpilt = userList.stream().collect(Collectors.groupingBy(User::getName, User::getCity));
printMap(defaultSpilt);
System.out.println();
// 2.Use custom delimiters
System.out.println("Use custom delimiters:");
userList.stream().collect(Collectors.groupingBy("--", User::getName, User::getCity, User::getId));
HashMap<? extends Serializable, List<User>> collect = userList.stream().collect(Collectors.groupingBy("--", User::getName, User::getCity, User::getId));
printMap(collect);
System.out.println();
// 3.Use custom delimiters
System.out.println("Use custom delimiters:");
userList.stream().collect(Collectors.groupingBy("--", User::getName, User::getCity, User::getId));
HashMap<? extends Serializable, List<User>> collect2 = userList.stream().collect(Collectors.groupingBy(User::getName, User::getCity, User::getBirthDay));
printMap(collect2);
}
public static <T> void printMap(Map<? extends Serializable, List<T>> map){
map.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
}
}

最后我希望這個特性能被JDK所吸收,這樣可以方便大家更好的使用這些好用的特性
到此這篇關(guān)于使用Java8進行分組(多個字段的組合分組)的文章就介紹到這了,更多相關(guān)Java8 分組內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 并發(fā)編程學(xué)習(xí)筆記之核心理論基礎(chǔ)
編寫優(yōu)質(zhì)的并發(fā)代碼是一件難度極高的事情。Java語言從第一版本開始內(nèi)置了對多線程的支持,這一點在當(dāng)年是非常了不起的,但是當(dāng)我們對并發(fā)編程有了更深刻的認(rèn)識和更多的實踐后,實現(xiàn)并發(fā)編程就有了更多的方案和更好的選擇。本文是對并發(fā)編程的核心理論做了下小結(jié)2016-05-05
總結(jié)一下Java回調(diào)機制的相關(guān)知識
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Java回調(diào)機制展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
springboot基于IDEA環(huán)境熱加載與熱部署教程
這篇文章主要為大家介紹了springboot在IDEA環(huán)境下的熱加載與熱部署教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03
高分面試從Hotspot源碼層面剖析java多態(tài)實現(xiàn)原理
這篇文章主要為大家介紹了在面試中從Hotspot源碼層面來剖析java多態(tài)的實現(xiàn)原理,這樣回答薪資隨你開,有需要的朋友可以借鑒參考下,希望大家多多加薪2022-01-01

