Java8 是一個(gè)里程碑式的版本庄岖,憑借如下新特性童本,讓人對(duì)其贊不絕口炬太。
Lambda 表達(dá)式給代碼構(gòu)建帶來了全新的風(fēng)格和能力;
Steam API 豐富了集合操作壳澳,拓展了集合的能力粉捻;
新日期時(shí)間 API 千呼萬喚始出來刑然;
隨著對(duì) Java8 新特性理解的深入,會(huì)被 Lambda 表達(dá)式(包含方法引用)愉粤、流式運(yùn)算的美所迷戀砾医,不由驚嘆框架設(shè)計(jì)的美。
Lambda 表達(dá)式是匿名函數(shù)如蚜,可以理解為一段可以用參數(shù)傳遞的代碼(代碼像數(shù)據(jù)一樣傳遞)。Lambda 表達(dá)式的使用需要有函數(shù)式接口的支持影暴。
方法引用是對(duì)特殊 Lambda 表達(dá)式的一種簡(jiǎn)化寫法错邦,當(dāng) Lambda 體中只調(diào)用一個(gè)方法,此方法滿足函數(shù)式接口規(guī)范型宙,此時(shí)可以使用::方法引用語法撬呢。
從語法表現(xiàn)力角度來講,方法引用 > Lambda表達(dá)式 > 匿名內(nèi)部類妆兑,方法引用是高階版的 Lambda 表達(dá)式魂拦,語言表達(dá)更為簡(jiǎn)潔毛仪,強(qiáng)烈推薦使用。
方法引用表達(dá)式無需顯示聲明被調(diào)用方法的參數(shù)芯勘,根據(jù)上下文自動(dòng)注入箱靴。方法引用能夠提高 Lambda 表達(dá)式語言的優(yōu)雅性,代碼更加簡(jiǎn)潔荷愕。下面以Comparator排序?yàn)槔v述如何借助方法引用構(gòu)建優(yōu)雅的代碼衡怀。
普通數(shù)據(jù)類型相對(duì)較容易理解安疗。
// 正向排序(方法引用)
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);
(1)數(shù)據(jù)完好
數(shù)據(jù)完好有兩重含義,一是對(duì)象本身不為空荐类;二是待比較對(duì)象的屬性值不為空怖现,以此為前提進(jìn)行排序操作。
// 對(duì)集合按照年齡排序(正序排列)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge));
// 對(duì)集合按照年齡排序(逆序排列)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge).reversed());
此示例是以Integer類型展開的掉冶,同理Double類型真竖、Long類型等數(shù)值類型處理方式相同。其中Comparator是排序過程中重要的類厌小。
(2)數(shù)據(jù)缺失
數(shù)據(jù)缺失的含義是對(duì)象本身為空或者待比較對(duì)象屬性為空恢共,如果不進(jìn)行處理,上述排序會(huì)出現(xiàn)空指針異常璧亚。
最常見的處理方式是通過流式運(yùn)算中filter方法讨韭,過濾掉空指針數(shù)據(jù),然后按照上述策略排序癣蟋。
userList.stream().filter(e->e.getAge()!=null).collect(Collectors.toList());
少數(shù)開發(fā)者在構(gòu)建實(shí)體類時(shí),String類型遍地開花疯搅,在需要運(yùn)算或者排序的場(chǎng)景下濒生,String 的缺陷逐漸暴露出來。下面講述字符串?dāng)?shù)值類型排序問題幔欧,即不修改數(shù)據(jù)類型的前提下完成期望的操作罪治。
實(shí)體類
public classSUser {
??? privateInteger userId;
??? privateString UserName;
??? // 本應(yīng)該是Double類型,錯(cuò)誤的使用為String類型
??? privateString score;
}
正序礁蔗、逆序排序
// 對(duì)集合按照年齡排序(正序排列)
Collections.sort(userList, Comparator.comparingDouble(e -> new Double(e.getScore())));
數(shù)據(jù)類型轉(zhuǎn)換排序時(shí)觉义,使用 JDK 內(nèi)置的 API 并不流暢,推薦使用commons-collection4包中的排序工具類浴井。了解更多晒骇,請(qǐng)移步查看ComparatorUtils。
// 對(duì)集合按照年齡排序(逆序排列)
Collections.sort(userList, ComparatorUtils.reversedComparator(Comparator.comparingDouble(e -> new Double(e.getScore()))));
小結(jié):通過以排序?yàn)槔瑢?shí)現(xiàn) Comparator 接口洪囤、Lambda 表達(dá)式徒坡、方法引用三種方式相比較,代碼可讀性逐步提高箍鼓。
內(nèi)置的排序器可以完成大多數(shù)場(chǎng)景的排序需求崭参,當(dāng)排序需求更加精細(xì)化時(shí)呵曹,適時(shí)引入第三方框架是比較好的選擇款咖。
單列排序包含正序和逆序奄喂。
// 正序
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName);
// 逆序
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName).reversed();
多列排序是指當(dāng)待比較的元素有相等的值時(shí),如何進(jìn)行下一步排序跨新。
// 默認(rèn)多列均是正序排序
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName)
??? .thenComparing(XUser::getScore);
// 自定義正逆序
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName,Comparator.reverseOrder())
??? .thenComparing(XUser::getScore,Comparator.reverseOrder());
流的操作包含如下三個(gè)部分:創(chuàng)建流、中間流域帐、關(guān)閉流赘被,篩選、去重肖揣、映射民假、排序?qū)儆诹鞯闹虚g操作,收集屬于終止操作龙优。Stream是流操作的基礎(chǔ)關(guān)鍵類羊异。
(1)通過集合創(chuàng)建流
// 通過集合創(chuàng)建流
List<String> lists = newArrayList<>();
lists.stream();
(2)通過數(shù)組創(chuàng)建流
// 通過數(shù)組創(chuàng)建流
String[] strings = new String[5];
Stream.of(strings);
應(yīng)用較多的是通過集合創(chuàng)建流,然后經(jīng)過中間操作彤断,最后終止回集合野舶。
篩選是指從(集合)流中篩選滿足條件的子集宰衙,通過 Lambda 表達(dá)式生產(chǎn)型接口來實(shí)現(xiàn)平道。
// 通過斷言型接口實(shí)現(xiàn)元素的過濾
stream.filter(x->x.getSalary()>10);
非空過濾
非空過濾包含兩層內(nèi)容:一是當(dāng)前對(duì)象是否為空或者非空;二是當(dāng)前對(duì)象的某屬性是否為空或者非空供炼。
篩選非空對(duì)象一屋,語法stream.filter(Objects::nonNull)做非空斷言。
// 非空斷言
java.util.function.Predicate<Boolean>
nonNull = Objects::nonNull;
查看Objects類了解更詳細(xì)信息劲蜻。
去重是指將(集合)流中重復(fù)的元素去除,通過 hashcode 和 equals 函數(shù)來判斷是否是重復(fù)元素先嬉。去重操作實(shí)現(xiàn)了類似于 HashSet 的運(yùn)算轧苫,對(duì)于對(duì)象元素流去重,需要重寫 hashcode 和 equals 方法。
如果流中泛型對(duì)象使用 Lombok 插件含懊,使用@Data注解默認(rèn)重寫了 hashcode 和 equals 方法身冬,字段相同并且屬性相同,則對(duì)象相等岔乔。更多內(nèi)容可查看Lombok使用手冊(cè)
stream.distinct();
取出流中元素的某一列,然后配合收集以形成新的集合雏门。
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()) {
??? StringitemName = model.get().getItemName();
??? StringitemType = model.get().getItemType();
??? return newMainExportVo(itemId, itemName);
}
傳統(tǒng)的Collectors類中的排序支持 List 實(shí)現(xiàn)類中的一部分排序,使用 stream 排序募闲,能夠覆蓋所有的 List 實(shí)現(xiàn)類步脓。
// 按照默認(rèn)字典順序排序
stream.sorted();
// 按照工資大小排序
stream.sorted((x,y)->Integer.compare(x.getSalary(),y.getSalary()));
(1)函數(shù)式接口排序
基于 Comparator 類中函數(shù)式方法,能夠更加優(yōu)雅的實(shí)現(xiàn)對(duì)象流的排序浩螺。
// 正向排序(默認(rèn))
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod));
// 逆向排序
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod).reversed());
(2)LocalDate 和 LocalDateTime 排序
新日期接口相比就接口靴患,使用體驗(yàn)更加,因此越來越多的被應(yīng)用要出,基于日期排序是常見的操作鸳君。
// 準(zhǔn)備測(cè)試數(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)));
正序、逆序排序
// 正向排序(默認(rèn))
stream.sorted(Comparator.comparing(DateModel::getLocalDate)).forEach(System.out::println);
// 逆向排序
stream.sorted(Comparator.comparing(DateModel::getLocalDate).reversed()).forEach(System.out::println);
對(duì)流中的元素按照一定的策略計(jì)算相嵌。終止操作的底層邏輯都是由 reduce 實(shí)現(xiàn)的。
收集(collect)將流中的中間(計(jì)算)結(jié)果存儲(chǔ)到集合中况脆,方便后續(xù)進(jìn)一步使用饭宾。為了方便對(duì)收集操作的理解,方便讀者掌握收集操作格了,將收集分為普通收集和高級(jí)收集看铆。
(1)收集為List
默認(rèn)返回的類型為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
默認(rèn)返回類型為HashSet檐嚣,可通過Collectors.toCollection(TreeSet::new)顯示指明使用其它數(shù)據(jù)結(jié)構(gòu)作為返回值容器助泽。
Set<String> collect = stream.collect(Collectors.toSet());
(1)收集為Map
默認(rèn)返回類型為HashMap,可通過Collectors.toCollection(LinkedHashMap::new)顯示指明使用其它數(shù)據(jù)結(jié)構(gòu)作為返回值容器嗡贺。
收集為Map的應(yīng)用場(chǎng)景更為強(qiáng)大隐解,下面對(duì)這個(gè)場(chǎng)景進(jìn)行詳細(xì)介紹。希望返回結(jié)果中能夠建立ID與NAME之間的匹配關(guān)系诫睬,最常見的場(chǎng)景是通過ID批量到數(shù)據(jù)庫查詢NAME煞茫,返回后再將原數(shù)據(jù)集中的ID替換成NAME。
ID 到 NAME 映射
@Data
public classItemEntity {
??? privateInteger itemId;
??? privateString itemName;
}
準(zhǔn)備集合數(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 classItemEntity {
??? privateInteger itemId;
??? privateString itemName;
??? privateBoolean status;
}
將集合數(shù)據(jù)轉(zhuǎn)化成 ID 與實(shí)體類的Map
// 將集合數(shù)據(jù)轉(zhuǎn)化成ID與實(shí)體類的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, ? extendsK> keyMapper,
???????????????????????????????????Function valueMapper) {
??? return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
(2)分組收集
流的分組收集操作在內(nèi)存層次模擬了數(shù)據(jù)庫層面的group by操作架谎,下面演示流的分組操作炸宵。Collectors類提供了各種層次的分組操作支撐。
流的分組能力對(duì)應(yīng)數(shù)據(jù)庫中的聚合函數(shù)谷扣,目前大部分能在數(shù)據(jù)庫中操作的聚合函數(shù),都能在流中找到相應(yīng)的能力捎琐。
// 默認(rèn)使用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())));
將對(duì)象包裝成集合的形式和將集合拆解為對(duì)象的形式是常見的操作。
返回默認(rèn)類型的集合實(shí)例
/**
?*將單個(gè)對(duì)象轉(zhuǎn)化為集合
?*
?* @paramt?? 對(duì)象實(shí)例
?* @param對(duì)象類型
?* @param集合類型
?* @return包含對(duì)象的集合實(shí)例
?*/
public static <T, C extends Collection<T>> Collection<T> toCollection(T t) {
??? return toCollection(t, ArrayList::new);
}
用戶自定義返回的集合實(shí)例類型
/**
?*將單個(gè)對(duì)象轉(zhuǎn)化為集合
?*
?* @paramt??????? 對(duì)象實(shí)例
?* @paramsupplier集合工廠
?* @param????? 對(duì)象類型
?* @param????? 集合類型
?* @return包含對(duì)象的集合實(shí)例
?*/
public static <T, C extends Collection<T>> Collection<T> toCollection(T t, Supplier supplier) {
??? return Stream.of(t).collect(Collectors.toCollection(supplier));
}
使用默認(rèn)的排序規(guī)則,注意此處不是指自然順序排序籽御。
/**
?*取出集合中第一個(gè)元素
?*
?* @paramcollection集合實(shí)例
?* @param??????? 集合中元素類型
?* @return泛型類型
?*/
public static <E> E toObject(Collection collection) {
??? // 處理集合空指針異常
???Collection coll = Optional.ofNullable(collection).orElseGet(ArrayList::new);
??? // 此處可以對(duì)流進(jìn)行排序练慕,然后取出第一個(gè)元素
??? return coll.stream().findFirst().orElse(null);
}
上述方法巧妙的解決兩個(gè)方面的異常問題:一是集合實(shí)例引用空指針異常;二是集合下標(biāo)越界異常技掏。
基于流式計(jì)算中的并行流,能夠顯著提高大數(shù)據(jù)下的計(jì)算效率哑梳,充分利用 CPU 核心數(shù)劲阎。
// 通過并行流實(shí)現(xiàn)數(shù)據(jù)累加
LongStream.rangeClosed(1,9999999999999999L).parallel().reduce(0,Long::sum);
生成指定序列的數(shù)組或者集合鸠真。
// 方式一:生成數(shù)組
int[] ints = IntStream.rangeClosed(1, 100).toArray();
// 方式二:生成集合
List<Integer> list = Arrays.stream(ints).boxed().collect(Collectors.toList());
// 獲取當(dāng)前日期(包含時(shí)間)
LocalDateTime localDateTime = LocalDateTime.now();
// 獲取當(dāng)前日期
LocalDate localDate = localDateTime.toLocalDate();
// 獲取當(dāng)前時(shí)間
LocalTime localTime = localDateTime.toLocalTime();
日期格式化
// 月份MM需要大寫嗽仪、小時(shí)字母需要大寫(小寫表示12進(jìn)制)
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
// 獲取當(dāng)前時(shí)間(字符串)
String dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("dateTime = " + dateTime);
Duration duration = Duration.between(Instant.now(), Instant.now());
System.out.println("duration = " + duration);
如下方式獲取的是 13 位時(shí)間戳祭隔,單位是毫秒货岭。
// 方式一
long now = Timestamp.valueOf(LocalDateTime.now()).getTime();
// 方式二
long now = Instant.now().toEpochMilli();
在Optional類出現(xiàn)之前,null異常幾乎折磨著每一位開發(fā)者,為了構(gòu)建健壯的應(yīng)用程序茴她,不得不使用繁瑣的if邏輯判斷來回避空指針異常寻拂。解鎖Optional類,讓你編寫的應(yīng)用健壯性更上一層樓丈牢。
ifPresent方法提供了先判斷是否為空,后進(jìn)一步使用的能力己沛。
鏈?zhǔn)饺≈凳侵福瑢訉忧短讓?duì)象取值申尼,在上層對(duì)象不為空的前提下垮卓,才能讀取其屬性值,然后繼續(xù)調(diào)用师幕,取出最終結(jié)果值粟按。有時(shí)候只關(guān)心鏈末端的結(jié)果狀態(tài),即使中間狀態(tài)為空霹粥,直接返回空值灭将。如下提供了一種無 if 判斷,代碼簡(jiǎn)介緊湊的實(shí)現(xiàn)方式:
Optional<Long> optional = Optional.ofNullable(tokenService.getLoginUser(ServletUtils.getRequest()))
??????????????????????????????????? .map(LoginUser::getUser).map(SysUser::getUserId);
// 如果存在則返回后控,不存在返回空
Long userId = optional.orElse(null);
傳統(tǒng)方式下構(gòu)建樹形列表需要反復(fù)遞歸調(diào)用查詢數(shù)據(jù)庫,效率偏低浩淘。對(duì)于一棵結(jié)點(diǎn)較多的樹捌朴,效率更低。這里提供一種只需調(diào)用一次數(shù)據(jù)庫张抄,通過流將列表轉(zhuǎn)化為樹的解決方式砂蔽。
/**
?*列表轉(zhuǎn)樹
?*
?* @paramrootList???? 列表的全部數(shù)據(jù)集
?* @paramparentId第一級(jí)目錄的父ID
?* @return樹形列表
?*/
publicList<IndustryNode> getChildNode(List rootList, String parentId) {
???List lists = rootList.stream()
???????????.filter(e -> e.getParentId().equals(parentId))
???????????.map(IndustryNode::new).collect(toList());
??? lists.forEach(e -> e.setChilds(getChildNode(rootList,
e.getId())));
??? returnlists;
}