一、序言
Java8 是一個里程碑式的版本瞒滴,憑借如下新特性曲梗,讓人對其贊不絕口。
- Lambda 表達式給代碼構(gòu)建帶來了全新的風格和能力逛腿;
- Steam API 豐富了集合操作,拓展了集合的能力仅颇;
- 新日期時間 API 千呼萬喚始出來单默;
隨著對 Java8 新特性理解的深入,會被 Lambda 表達式(包含方法引用)忘瓦、流式運算的美所迷戀搁廓,不由驚嘆框架設計的美。
二耕皮、方法引用
Lambda 表達式是匿名函數(shù)境蜕,可以理解為一段可以用參數(shù)傳遞的代碼(代碼像數(shù)據(jù)一樣傳遞)。Lambda 表達式的使用需要有函數(shù)式接口的支持凌停。
方法引用是對特殊 Lambda 表達式的一種簡化寫法粱年,當 Lambda 體中只調(diào)用一個方法,此方法滿足函數(shù)式接口規(guī)范罚拟,此時可以使用::
方法引用語法台诗。
從語法表現(xiàn)力角度來講,方法引用 > Lambda表達式 > 匿名內(nèi)部類
赐俗,方法引用是高階版的 Lambda 表達式拉队,語言表達更為簡潔,強烈推薦使用阻逮。
方法引用表達式無需顯示聲明被調(diào)用方法的參數(shù)慨丐,根據(jù)上下文自動注入慰于。方法引用能夠提高 Lambda 表達式語言的優(yōu)雅性停士,代碼更加簡潔歇父。下面以Comparator
排序為例講述如何借助方法引用構(gòu)建優(yōu)雅的代碼。
(一)方法引用與排序
1界酒、普通數(shù)據(jù)類型
普通數(shù)據(jù)類型相對較容易理解。
// 正向排序(方法引用)
Stream.of(11, 15, 11, 12).sorted(Integer::compareTo).forEach(System.out::println);
// 正向排序
Stream.of(11, 15, 11, 12).sorted(Comparator.naturalOrder()).forEach(System.out::println);
// 逆向排序
Stream.of(11, 15, 11, 12).sorted(Comparator.reverseOrder()).forEach(System.out::println);
2、對象數(shù)據(jù)類型
(1)數(shù)據(jù)完好
數(shù)據(jù)完好有兩重含義珊拼,一是對象本身不為空;二是待比較對象的屬性值不為空流炕,以此為前提進行排序操作澎现。
// 對集合按照年齡排序(正序排列)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge));
// 對集合按照年齡排序(逆序排列)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge).reversed());
此示例是以Integer
類型展開的,同理Double
類型每辟、Long
類型等數(shù)值類型處理方式相同剑辫。其中Comparator是排序過程中重要的類。
(2)數(shù)據(jù)缺失
數(shù)據(jù)缺失的含義是對象本身為空或者待比較對象屬性為空渠欺,如果不進行處理,上述排序會出現(xiàn)空指針異常挠将。
最常見的處理方式是通過流式運算中filter
方法胳岂,過濾掉空指針數(shù)據(jù),然后按照上述策略排序舔稀。
userList.stream().filter(e->e.getAge()!=null).collect(Collectors.toList());
3乳丰、字符串處理
少數(shù)開發(fā)者在構(gòu)建實體類時,String
類型遍地開花内贮,在需要運算或者排序的場景下产园,String 的缺陷逐漸暴露出來。下面講述字符串數(shù)值
類型排序問題夜郁,即不修改數(shù)據(jù)類型的前提下完成期望的操作什燕。
實體類
public class SUser {
private Integer userId;
private String UserName;
// 本應該是Double類型,錯誤的使用為String類型
private String score;
}
正序竞端、逆序排序
// 對集合按照年齡排序(正序排列)
Collections.sort(userList, Comparator.comparingDouble(e -> new Double(e.getScore())));
數(shù)據(jù)類型轉(zhuǎn)換排序時屎即,使用 JDK 內(nèi)置的 API 并不流暢,推薦使用commons-collection4
包中的排序工具類事富。了解更多剑勾,請移步查看ComparatorUtils。
// 對集合按照年齡排序(逆序排列)
Collections.sort(userList, ComparatorUtils.reversedComparator(Comparator.comparingDouble(e -> new Double(e.getScore()))));
小結(jié):通過以排序為例赵颅,實現(xiàn) Comparator 接口虽另、Lambda 表達式、方法引用三種方式相比較饺谬,代碼可讀性逐步提高捂刺。
(二)排序器
內(nèi)置的排序器可以完成大多數(shù)場景的排序需求谣拣,當排序需求更加精細化時,適時引入第三方框架是比較好的選擇族展。
1森缠、單列排序
單列排序包含正序和逆序。
// 正序
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName);
// 逆序
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName).reversed();
2仪缸、多列排序
多列排序是指當待比較的元素有相等的值時贵涵,如何進行下一步排序。
// 默認多列均是正序排序
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName)
.thenComparing(XUser::getScore);
// 自定義正逆序
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName,Comparator.reverseOrder())
.thenComparing(XUser::getScore,Comparator.reverseOrder());
三恰画、Steam API
流的操作包含如下三個部分:創(chuàng)建流宾茂、中間流、關(guān)閉流拴还,篩選
跨晴、去重
、映射
片林、排序
屬于流的中間操作端盆,收集
屬于終止操作。Stream是流操作的基礎關(guān)鍵類费封。
(一)創(chuàng)建流
(1)通過集合創(chuàng)建流
// 通過集合創(chuàng)建流
List<String> lists = new ArrayList<>();
lists.stream();
(2)通過數(shù)組創(chuàng)建流
// 通過數(shù)組創(chuàng)建流
String[] strings = new String[5];
Stream.of(strings);
應用較多的是通過集合創(chuàng)建流焕妙,然后經(jīng)過中間操作,最后終止回集合弓摘。
(二)中間操作
1焚鹊、篩選(filter)
篩選是指從(集合)流中篩選滿足條件的子集,通過 Lambda 表達式生產(chǎn)型接口來實現(xiàn)衣盾。
// 通過斷言型接口實現(xiàn)元素的過濾
stream.filter(x->x.getSalary()>10);
非空過濾
非空過濾包含兩層內(nèi)容:一是當前對象是否為空或者非空寺旺;二是當前對象的某屬性是否為空或者非空爷抓。
篩選非空對象势决,語法stream.filter(Objects::nonNull)
做非空斷言。
// 非空斷言
java.util.function.Predicate<Boolean> nonNull = Objects::nonNull;
查看Objects類了解更詳細信息蓝撇。
2果复、去重(distinct)
去重是指將(集合)流中重復的元素去除,通過 hashcode 和 equals 函數(shù)來判斷是否是重復元素渤昌。去重操作實現(xiàn)了類似于 HashSet 的運算虽抄,對于對象元素流去重,需要重寫 hashcode 和 equals 方法独柑。
如果流中泛型對象使用 Lombok 插件迈窟,使用@Data
注解默認重寫了 hashcode 和 equals 方法,字段相同并且屬性相同忌栅,則對象相等车酣。更多內(nèi)容可查看Lombok 使用手冊
stream.distinct();
3、映射(map)
取出流中元素的某一列,然后配合收集以形成新的集合湖员。
stream.map(x->x.getEmpId());
filter
和map
操作通常結(jié)合使用贫悄,取出流中某行某列的數(shù)據(jù),建議先行后列
的方式定位娘摔。
Optional<MainExportModel> model = data.stream().filter(e -> e.getResId().equals(resId)).findFirst();
if (model.isPresent()) {
String itemName = model.get().getItemName();
String itemType = model.get().getItemType();
return new MainExportVo(itemId, itemName);
}
4窄坦、排序(sorted)
傳統(tǒng)的Collectors
類中的排序支持 List 實現(xiàn)類中的一部分排序,使用 stream 排序凳寺,能夠覆蓋所有的 List 實現(xiàn)類鸭津。
// 按照默認字典順序排序
stream.sorted();
// 按照工資大小排序
stream.sorted((x,y)->Integer.compare(x.getSalary(),y.getSalary()));
(1)函數(shù)式接口排序
基于 Comparator 類中函數(shù)式方法,能夠更加優(yōu)雅的實現(xiàn)對象流的排序读第。
// 正向排序(默認)
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod));
// 逆向排序
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod).reversed());
(2)LocalDate 和 LocalDateTime 排序
新日期接口相比就接口曙博,使用體驗更加,因此越來越多的被應用怜瞒,基于日期排序是常見的操作父泳。
// 準備測試數(shù)據(jù)
Stream<DateModel> stream = Stream.of(new DateModel(LocalDate.of(2020, 1, 1)), new DateModel(LocalDate.of(2019, 1, 1)), new DateModel(LocalDate.of(2021, 1, 1)));
正序、逆序排序
// 正向排序(默認)
stream.sorted(Comparator.comparing(DateModel::getLocalDate)).forEach(System.out::println);
// 逆向排序
stream.sorted(Comparator.comparing(DateModel::getLocalDate).reversed()).forEach(System.out::println);
5吴汪、規(guī)約(reduce)
對流中的元素按照一定的策略計算惠窄。終止操作的底層邏輯都是由 reduce 實現(xiàn)的。
(三)終止操作
收集(collect)將流中的中間(計算)結(jié)果存儲到集合中漾橙,方便后續(xù)進一步使用杆融。為了方便對收集操作的理解,方便讀者掌握收集操作霜运,將收集分為普通收集
和高級收集
脾歇。
1、普通收集
(1)收集為List
默認返回的類型為ArrayList
淘捡,可通過Collectors.toCollection(LinkedList::new)
顯示指明使用其它數(shù)據(jù)結(jié)構(gòu)作為返回值容器藕各。
List<String> collect = stream.collect(Collectors.toList());
由集合創(chuàng)建流的收集需注意:僅僅修改流字段中的內(nèi)容,沒有返回新類型焦除,如下操作直接修改原始集合激况,無需處理返回值。
// 直接修改原始集合
userVos.stream().map(e -> e.setDeptName(hashMap.get(e.getDeptId()))).collect(Collectors.toList());
(2)收集為Set
默認返回類型為HashSet
膘魄,可通過Collectors.toCollection(TreeSet::new)
顯示指明使用其它數(shù)據(jù)結(jié)構(gòu)作為返回值容器乌逐。
Set<String> collect = stream.collect(Collectors.toSet());
2、高級收集
(1)收集為Map
默認返回類型為HashMap
创葡,可通過Collectors.toCollection(LinkedHashMap::new)
顯示指明使用其它數(shù)據(jù)結(jié)構(gòu)作為返回值容器浙踢。
收集為Map
的應用場景更為強大,下面對這個場景進行詳細介紹灿渴。希望返回結(jié)果中能夠建立ID
與NAME
之間的匹配關(guān)系洛波,最常見的場景是通過ID
批量到數(shù)據(jù)庫查詢NAME
呐芥,返回后再將原數(shù)據(jù)集中的ID
替換成NAME
。
ID 到 NAME 映射
@Data
public class ItemEntity {
private Integer itemId;
private String itemName;
}
準備集合數(shù)據(jù)奋岁,此部分通常是從數(shù)據(jù)庫查詢的數(shù)據(jù)
// 模擬從數(shù)據(jù)庫中查詢批量的數(shù)據(jù)
List<ItemEntity> entityList = Stream.of(new ItemEntity(1,"A"), new ItemEntity(2,"B"), new ItemEntity(3,"C")).collect(Collectors.toList());
將集合數(shù)據(jù)轉(zhuǎn)化成 ID 與 NAME 的 Map
// 將集合數(shù)據(jù)轉(zhuǎn)化成ID與NAME的Map
Map<Integer, String> hashMap = entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, ItemEntity::getItemName));
ID
與Object
類映射
@Data
public class ItemEntity {
private Integer itemId;
private String itemName;
private Boolean status;
}
將集合數(shù)據(jù)轉(zhuǎn)化成 ID 與實體類的 Map
// 將集合數(shù)據(jù)轉(zhuǎn)化成ID與實體類的Map
Map<Integer, ItemEntity> hashMap = entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, e -> e));
其中Collectors
類中的toMap
參數(shù)是函數(shù)式接口參數(shù)思瘟,能夠自定義返回值。
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
(2)分組收集
流的分組收集操作在內(nèi)存層次模擬了數(shù)據(jù)庫層面的group by
操作闻伶,下面演示流的分組操作滨攻。Collectors類提供了各種層次的分組操作支撐。
流的分組能力對應數(shù)據(jù)庫中的聚合函數(shù)蓝翰,目前大部分能在數(shù)據(jù)庫中操作的聚合函數(shù)光绕,都能在流中找到相應的能力。
// 默認使用List作為分組后承載容器
Map<Integer, List<XUser>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId));
// 顯示指明使用List作為分組后承載容器
Map<Integer, List<XUser>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId, Collectors.toList()));
映射后再分組
Map<Integer, List<String>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId,Collectors.mapping(XUser::getUserName,Collectors.toList())));
四畜份、Stream 拓展
(一)集合與對象互轉(zhuǎn)
將對象包裝成集合的形式和將集合拆解為對象的形式是常見的操作诞帐。
1、對象轉(zhuǎn)集合
返回默認類型的集合實例
/**
* 將單個對象轉(zhuǎn)化為集合
*
* @param t 對象實例
* @param <T> 對象類型
* @param <C> 集合類型
* @return 包含對象的集合實例
*/
public static <T, C extends Collection<T>> Collection<T> toCollection(T t) {
return toCollection(t, ArrayList::new);
}
用戶自定義返回的集合實例類型
/**
* 將單個對象轉(zhuǎn)化為集合
*
* @param t 對象實例
* @param supplier 集合工廠
* @param <T> 對象類型
* @param <C> 集合類型
* @return 包含對象的集合實例
*/
public static <T, C extends Collection<T>> Collection<T> toCollection(T t, Supplier<C> supplier) {
return Stream.of(t).collect(Collectors.toCollection(supplier));
}
2爆雹、集合轉(zhuǎn)對象
使用默認的排序規(guī)則停蕉,注意此處不是指自然順序排序。
/**
* 取出集合中第一個元素
*
* @param collection 集合實例
* @param <E> 集合中元素類型
* @return 泛型類型
*/
public static <E> E toObject(Collection<E> collection) {
// 處理集合空指針異常
Collection<E> coll = Optional.ofNullable(collection).orElseGet(ArrayList::new);
// 此處可以對流進行排序钙态,然后取出第一個元素
return coll.stream().findFirst().orElse(null);
}
上述方法巧妙的解決兩個方面的異常問題:一是集合實例引用空指針異常慧起;二是集合下標越界異常。
(二)其它
1册倒、并行計算
基于流式計算中的并行流蚓挤,能夠顯著提高大數(shù)據(jù)下的計算效率,充分利用 CPU 核心數(shù)驻子。
// 通過并行流實現(xiàn)數(shù)據(jù)累加
LongStream.rangeClosed(1,9999999999999999L).parallel().reduce(0,Long::sum);
2灿意、序列數(shù)組
生成指定序列的數(shù)組或者集合。
// 方式一:生成數(shù)組
int[] ints = IntStream.rangeClosed(1, 100).toArray();
// 方式二:生成集合
List<Integer> list = Arrays.stream(ints).boxed().collect(Collectors.toList());
五崇呵、其它
(一)新日期時間 API
1缤剧、LocalDateTime
// 獲取當前日期(包含時間)
LocalDateTime localDateTime = LocalDateTime.now();
// 獲取當前日期
LocalDate localDate = localDateTime.toLocalDate();
// 獲取當前時間
LocalTime localTime = localDateTime.toLocalTime();
日期格式化
// 月份MM需要大寫、小時字母需要大寫(小寫表示12進制)
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
// 獲取當前時間(字符串)
String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("dateTime = " + dateTime);
2演熟、Duration
Duration duration = Duration.between(Instant.now(), Instant.now());
System.out.println("duration = " + duration);
3鞭执、獲取當前時間戳
如下方式獲取的是 13 位時間戳司顿,單位是毫秒芒粹。
// 方式一
long now = Timestamp.valueOf(LocalDateTime.now()).getTime();
// 方式二
long now = Instant.now().toEpochMilli();
(二)Optional
在Optional類出現(xiàn)之前,null
異常幾乎折磨著每一位開發(fā)者大溜,為了構(gòu)建健壯的應用程序化漆,不得不使用繁瑣的if
邏輯判斷來回避空指針異常。解鎖Optional
類钦奋,讓你編寫的應用健壯性更上一層樓座云。
1疙赠、先判斷后使用
ifPresent
方法提供了先判斷是否為空,后進一步使用的能力朦拖。
2圃阳、鏈式取值
鏈式取值是指,層層嵌套對象取值璧帝,在上層對象不為空的前提下捍岳,才能讀取其屬性值,然后繼續(xù)調(diào)用睬隶,取出最終結(jié)果值锣夹。有時候只關(guān)心鏈末端的結(jié)果狀態(tài),即使中間狀態(tài)為空苏潜,直接返回空值银萍。如下提供了一種無 if 判斷,代碼簡介緊湊的實現(xiàn)方式:
Optional<Long> optional = Optional.ofNullable(tokenService.getLoginUser(ServletUtils.getRequest()))
.map(LoginUser::getUser).map(SysUser::getUserId);
// 如果存在則返回恤左,不存在返回空
Long userId = optional.orElse(null);
六贴唇、流的應用
(一)列表轉(zhuǎn)樹
傳統(tǒng)方式下構(gòu)建樹形列表需要反復遞歸調(diào)用查詢數(shù)據(jù)庫,效率偏低飞袋。對于一棵結(jié)點較多的樹滤蝠,效率更低。這里提供一種只需調(diào)用一次數(shù)據(jù)庫授嘀,通過流將列表轉(zhuǎn)化為樹的解決方式物咳。
<img src="https://www.altitude.xin/typora/image-20211014133305884.png" alt="image-20211014133305884" style="zoom:50%;" />
/**
* 列表轉(zhuǎn)樹
*
* @param rootList 列表的全部數(shù)據(jù)集
* @param parentId 第一級目錄的父ID
* @return 樹形列表
*/
public List<IndustryNode> getChildNode(List<Industry> rootList, String parentId) {
List<IndustryNode> lists = rootList.stream()
.filter(e -> e.getParentId().equals(parentId))
.map(IndustryNode::new).collect(toList());
lists.forEach(e -> e.setChilds(getChildNode(rootList, e.getId())));
return lists;
}
喜歡本文點個??贊??支持一下,如有需要蹄皱,可通過微信
dream4s
與我聯(lián)系览闰。相關(guān)源碼在GitHub,視頻講解在B站巷折,本文收藏在博客天地压鉴。