一、lambda表達(dá)式
1、list集合方面
排序
//排序 sorted((第一個(gè)對(duì)象,第二個(gè)對(duì)象)->返回值)
List<Person> sortedList = list.stream().sorted((o1,o2)->o1.getAge()-o2.getAge()).collect(Collectors.toList());
//排序ID
List<User> userList1 = userList.stream().sorted(Comparator.comparingInt(User::getId)).collect(Collectors.toList());
.sorted(Comparator.comparing(Person::getAge))//依年紀(jì)升序排序
.sorted(Comparator.comparing(Person::getAge).reversed())//依年紀(jì)降序排序
map(), 提取對(duì)象中的某一元素. 用每一項(xiàng)來(lái)獲得屬性(也可以直接用 對(duì)象::get屬性
//獲取所有ID集合
List<Integer> idList = userList.stream().map(User::getId).collect(Collectors.toList());
List<String> mapList2 = list.stream().map(item->item.getName()).collect(Collectors.toList());
filter為過(guò)濾
//獲取id大于2的
List<User> userList2 = userList.stream().filter(user -> user.getId() > 2).collect(Collectors.toList());
.findAny()表示將其中任意一個(gè)返回;
.orElse(null)表示如果一個(gè)都沒(méi)找到返回null
User user6 =userList.stream().filter(user -> user.getId() > 2).findAny().orElse(null);
統(tǒng)計(jì)list中的數(shù)據(jù)
//獲取最大
Integer id = userList.stream().map(User::getId).max(Integer::compareTo).get();
//獲取最小
Integer id1 = userList.stream().map(User::getId).min(Integer::compareTo).get();
//獲取id數(shù)量
long count = userList.stream().map(User::getId).count();
//總和
int sum = userList.stream().mapToInt(User::getId).sum();
//獲取平均值
double d = userList.stream().mapToInt(User::getId).average().getAsDouble();
打印list中的信息
list.forEach((s)-> System.out.print(s+"\t"));
list.forEach(System.out::println);
其他方法
//分組統(tǒng)計(jì)
Map<String, Long> map = userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.counting()));
//分組 Collectors.groupingBy(屬性名)
Map<Integer, List<Person>> map = list.stream().collect(Collectors.groupingBy(Person::getAge));
//將名字全轉(zhuǎn)換為大寫(xiě)
List<String> list = userList.stream().map(User::getName).map(String::toUpperCase).collect(Collectors.toList());
//獲取忽略第一個(gè)并取前幾條數(shù)據(jù)
List<User> list1 = userList.stream().skip(1).limit(2).collect(Collectors.toList());
//distinct() 去重;collect(Collectors.toList())。封裝成集合
List<User> collect = userList.stream().distinct().collect(Collectors.toList());
找出所有 長(zhǎng)度>=5的字符串,并且忽略大小寫(xiě)、去除重復(fù)字符串,然后按字母排序,最后用“?”連接成一個(gè)字符串輸出!
String result = list.stream().filter(i->!isNum(i))
.filter(i->i.length()>=5).map(i->i.toLowerCase()).distinct()
.sorted(Comparator.naturalOrder()).collect(Collectors.joining("?"));
從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù),根據(jù)channelName字段分組
//從數(shù)據(jù)庫(kù)中查詢出數(shù)據(jù)
List<Map<String, Object>> realtimelist = eCSaleRealtimeDao.getData(param);
//根據(jù)渠道名稱(chēng)分組
Map<Object, List<Map<String, Object>>> subItemList = realtimelist.stream().collect(Collectors.groupingBy(map -> map.get("channel_nam")));
//遍歷分組
for (Map.Entry<Object, List<Map<String, Object>>> entryItem : subItemList.entrySet()) {
Object key = entryItem.getKey();
List<Map<String, Object>> entryItemList = entryItem.getValue();
}
根據(jù)module和支付額排序
這里是module升序,paymat降序排序的,不知道為什么,結(jié)果module排序也要加上reversed才對(duì)
List<Map<String, Object>> resList = list.stream().sorted(
Comparator.comparing(MapCompare::comparingByModule).reversed()
.thenComparing(MapCompare::comparingByPayMat).reversed())
.collect(Collectors.toList());
MapCompare類(lèi)
public class MapCompare {
public static String comparingByModule(Map map){
return String.valueOf(map.get("module"));
}
public static double comparingByPayMat(Map map){
return Double.parseDouble(map.get("paymentAmount_noround").toString()) ;
}
}
二、.Optional接口
1、使用方法
Optional
本質(zhì)是個(gè)容器,你可以將你的變量交由它進(jìn)行封裝,這樣我們就不用顯式對(duì)原變量進(jìn)行 null
值檢測(cè),防止出現(xiàn)各種空指針異常。舉例:
我們想寫(xiě)一個(gè)獲取學(xué)生某個(gè)課程考試分?jǐn)?shù)的函數(shù):getScore()
public Integer getScore(Student student) {
if (student != null) {
// 第一層 null判空
Subject subject = student.getSubject();
if (subject != null) {
// 第二層 null判空
return subject.score;
}
}
return null;
}
這樣寫(xiě)倒不是不可以,但我們作為一個(gè)“嚴(yán)謹(jǐn)且良心的”后端工程師,這么多嵌套的 if 判空多少有點(diǎn)扎眼!
為此我們必須引入 Optional
:
public Integer getScore(Student student) {
return Optional.ofNullable(student).map(Student::getSubject).map(Subject::getScore).orElse(null);
}
另一個(gè)例子:
Optional<Student> optional = getStudentByIdFromDB();
optional.ifPresent(stu -> {
System.out.println("學(xué)生姓名是:" + stu.getName());
});
}
Optional<Student>
作為結(jié)果,這樣就表明 Student可能存在,也可能不存在,這時(shí)候就可以在 Optional 的 ifPresent()
方法中使用 Lambda 表達(dá)式來(lái)直接打印結(jié)果。
Optional 之所以可以解決 NPE 的問(wèn)題,是因?yàn)樗鞔_的告訴我們,不需要對(duì)它進(jìn)行判空。它就好像十字路口的路標(biāo),明確地告訴你該往哪走。
2、創(chuàng)建 Optional 對(duì)象
1)可以使用靜態(tài)方法 empty()
創(chuàng)建一個(gè)空的 Optional 對(duì)象
Optional<String> empty = Optional.empty();
System.out.println(empty); // 輸出:Optional.empty
2)可以使用靜態(tài)方法 of()
創(chuàng)建一個(gè)非空的 Optional 對(duì)象
Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt); // 輸出:Optional[沉默王二]
當(dāng)然了,傳遞給 of()
方法的參數(shù)必須是非空的,也就是說(shuō)不能為 null,否則仍然會(huì)拋出 NullPointerException。
String name = null;
Optional<String> optnull = Optional.of(name);
3)可以使用靜態(tài)方法 ofNullable()
創(chuàng)建一個(gè)即可空又可非空的 Optional 對(duì)象
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 輸出:Optional.empty
ofNullable()
方法內(nèi)部有一個(gè)三元表達(dá)式,如果為參數(shù)為 null,則返回私有常量 EMPTY;否則使用 new 關(guān)鍵字創(chuàng)建了一個(gè)新的 Optional 對(duì)象——不會(huì)再拋出 NPE 異常了。
3、判斷值是否存在
可以通過(guò)方法 isPresent()
判斷一個(gè) Optional 對(duì)象是否存在,如果存在,該方法返回 true,否則返回 false——取代了 obj != null
的判斷。
Optional<String> opt = Optional.of("沉默王二");
System.out.println(opt.isPresent()); // 輸出:true
Optional<String> optOrNull = Optional.ofNullable(null);
System.out.println(opt.isPresent()); // 輸出:false
4、非空表達(dá)式
Optional 類(lèi)有一個(gè)非常現(xiàn)代化的方法——ifPresent()
,允許我們使用函數(shù)式編程的方式執(zhí)行一些代碼,因此,我把它稱(chēng)為非空表達(dá)式。如果沒(méi)有該方法的話,我們通常需要先通過(guò) isPresent()
方法對(duì) Optional 對(duì)象進(jìn)行判空后再執(zhí)行相應(yīng)的代碼:
Optional<String> optOrNull = Optional.ofNullable(null);
if (optOrNull.isPresent()) {
System.out.println(optOrNull.get().length());
}
有了 ifPresent()
之后,情況就完全不同了,可以直接將 Lambda 表達(dá)式傳遞給該方法,代碼更加簡(jiǎn)潔,更加直觀。
Optional<String> opt = Optional.of("沉默王二");
opt.ifPresent(str -> System.out.println(str.length()));
5、設(shè)置(獲取)默認(rèn)值
有時(shí)候,我們?cè)趧?chuàng)建(獲取) Optional 對(duì)象的時(shí)候,需要一個(gè)默認(rèn)值,orElse()
和 orElseGet()
方法就派上用場(chǎng)了。
orElse()
方法用于返回包裹在 Optional 對(duì)象中的值,如果該值不為 null,則返回;否則返回默認(rèn)值。該方法的參數(shù)類(lèi)型和值得類(lèi)型一致。
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("沉默王二");
System.out.println(name); // 輸出:沉默王二
orElseGet()
方法與 orElse()
方法類(lèi)似,但參數(shù)類(lèi)型不同。如果 Optional 對(duì)象中的值為 null,則執(zhí)行參數(shù)中的函數(shù)。
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()->"沉默王二");
System.out.println(name); // 輸出:沉默王二
從輸出結(jié)果以及代碼的形式上來(lái)看,這兩個(gè)方法極其相似,這不免引起我們的懷疑,Java 類(lèi)庫(kù)的設(shè)計(jì)者有必要這樣做嗎?
假設(shè)現(xiàn)在有這樣一個(gè)獲取默認(rèn)值的方法,很傳統(tǒng)的方式。
public static String getDefaultValue() {
System.out.println("getDefaultValue");
return "沉默王二";
}
然后,通過(guò) orElse()
方法和 orElseGet()
方法分別調(diào)用 getDefaultValue()
方法返回默認(rèn)值。
public static void main(String[] args) {
String name = null;
System.out.println("orElse");
String name2 = Optional.ofNullable(name).orElse(getDefaultValue());
System.out.println("orElseGet");
String name3 = Optional.ofNullable(name).orElseGet(OrElseOptionalDemo::getDefaultValue);
}
注:類(lèi)名 :: 方法名
是 Java 8 引入的語(yǔ)法,方法名后面是沒(méi)有 ()
的,表明該方法并不一定會(huì)被調(diào)用。
輸出結(jié)果如下所示:
orElse
getDefaultValue
orElseGet
getDefaultValue
輸出結(jié)果是相似的,沒(méi)什么太大的不同,這是在 Optional 對(duì)象的值為 null 的情況下。假如 Optional 對(duì)象的值不為 null 呢?
public static void main(String[] args) {
String name = "沉默王三";
System.out.println("orElse");
String name2 = Optional.ofNullable(name).orElse(getDefaultValue());
System.out.println("orElseGet");
String name3 = Optional.ofNullable(name).orElseGet(OrElseOptionalDemo::getDefaultValue);
}
輸出結(jié)果如下所示:
orElse
getDefaultValue
orElseGet
咦,orElseGet()
沒(méi)有去調(diào)用 getDefaultValue()
。哪個(gè)方法的性能更佳,你明白了吧?
6、獲取值
直觀從語(yǔ)義上來(lái)看,get()
方法才是最正宗的獲取 Optional 對(duì)象值的方法,但很遺憾,該方法是有缺陷的,因?yàn)榧偃?Optional 對(duì)象的值為 null,該方法會(huì)拋出 NoSuchElementException 異常。這完全與我們使用 Optional 類(lèi)的初衷相悖。
public class GetOptionalDemo {
public static void main(String[] args) {
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull.get());
}
}
這段程序在運(yùn)行時(shí)會(huì)拋出異常:
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:141)
at com.cmower.dzone.optional.GetOptionalDemo.main(GetOptionalDemo.java:9)
盡管拋出的異常是 NoSuchElementException 而不是 NPE,但在我們看來(lái),顯然是在“五十步笑百步”。建議 orElseGet()
方法獲取 Optional 對(duì)象的值。
7、過(guò)濾值
小王通過(guò) Optional 類(lèi)對(duì)之前的代碼進(jìn)行了升級(jí),完成后又興高采烈地跑去找老馬要任務(wù)了。老馬覺(jué)得這小伙子不錯(cuò),頭腦靈活,又干活積極,很值得培養(yǎng),就又交給了小王一個(gè)新的任務(wù):用戶注冊(cè)時(shí)對(duì)密碼的長(zhǎng)度進(jìn)行檢查。
小王拿到任務(wù)后,樂(lè)開(kāi)了花,因?yàn)樗麆傄獙W(xué)習(xí) Optional 類(lèi)的 filter()
方法,這就派上了用場(chǎng)。
public class FilterOptionalDemo {
public static void main(String[] args) {
String password = "12345";
Optional<String> opt = Optional.ofNullable(password);
System.out.println(opt.filter(pwd -> pwd.length() > 6).isPresent());
}
}
filter()
方法的參數(shù)類(lèi)型為 Predicate(Java 8 新增的一個(gè)函數(shù)式接口),也就是說(shuō)可以將一個(gè) Lambda 表達(dá)式傳遞給該方法作為條件,如果表達(dá)式的結(jié)果為 false,則返回一個(gè) EMPTY 的 Optional 對(duì)象,否則返回過(guò)濾后的 Optional 對(duì)象。
在上例中,由于 password 的長(zhǎng)度為 5 ,所以程序輸出的結(jié)果為 false。假設(shè)密碼的長(zhǎng)度要求在 6 到 10 位之間,那么還可以再追加一個(gè)條件。來(lái)看小王增加難度后的代碼。
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;
password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);
這次程序輸出的結(jié)果為 true,因?yàn)槊艽a變成了 7 位,在 6 到 10 位之間。想象一下,假如小王使用 if-else 來(lái)完成這個(gè)任務(wù),代碼該有多冗長(zhǎng)。
8、轉(zhuǎn)換值
小王檢查完了密碼的長(zhǎng)度,仍然覺(jué)得不夠盡興,覺(jué)得要對(duì)密碼的強(qiáng)度也進(jìn)行檢查,比如說(shuō)密碼不能是“password”,這樣的密碼太弱了。于是他又開(kāi)始研究起了 map()
方法,該方法可以按照一定的規(guī)則將原有 Optional 對(duì)象轉(zhuǎn)換為一個(gè)新的 Optional 對(duì)象,原有的 Optional 對(duì)象不會(huì)更改。
先來(lái)看小王寫(xiě)的一個(gè)簡(jiǎn)單的例子:
public class OptionalMapDemo {
public static void main(String[] args) {
String name = "沉默王二";
Optional<String> nameOptional = Optional.of(name);
Optional<Integer> intOpt = nameOptional
.map(String::length);
System.out.println( intOpt.orElse(0));
}
}
在上面這個(gè)例子中,map()
方法的參數(shù) String::length
,意味著要 將原有的字符串類(lèi)型的 Optional 按照字符串長(zhǎng)度重新生成一個(gè)新的 Optional 對(duì)象,類(lèi)型為 Integer。
搞清楚了 map()
方法的基本用法后,小王決定把 map()
方法與 filter()
方法結(jié)合起來(lái)用,前者用于將密碼轉(zhuǎn)化為小寫(xiě),后者用于判斷長(zhǎng)度以及是否是“password”。
public class OptionalMapFilterDemo {
public static void main(String[] args) {
String password = "password";
Optional<String> opt = Optional.ofNullable(password);
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;
Predicate<String> eq = pwd -> pwd.equals("password");
boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();
System.out.println(result);
}
}
三、.LocalDateTime
自 Java8
開(kāi)始, JDK
中其實(shí)就增加了一系列表示日期和時(shí)間的新類(lèi),最典型的就是 LocalDateTime
。直言不諱,這玩意的出現(xiàn)就是為了干掉之前 JDK
版本中的 Date
老哥!
1、獲取當(dāng)前此刻的時(shí)間
LocalDateTime rightNow = LocalDateTime.now();
System.out.println( "當(dāng)前時(shí)刻:" + rightNow );
System.out.println( "當(dāng)前年份:" + rightNow.getYear() );
System.out.println( "當(dāng)前月份:" + rightNow.getMonth() );
System.out.println( "當(dāng)前日份:" + rightNow.getDayOfMonth() );
System.out.println( "當(dāng)前時(shí):" + rightNow.getHour() );
System.out.println( "當(dāng)前分:" + rightNow.getMinute() );
System.out.println( "當(dāng)前秒:" + rightNow.getSecond() );
// 輸出結(jié)果:
當(dāng)前時(shí)刻:2019-12-13T22:05:26.779
當(dāng)前年份:2019
當(dāng)前月份:DECEMBER
當(dāng)前日份:13
當(dāng)前時(shí):22
當(dāng)前分:5
當(dāng)前秒:26
2、構(gòu)造一個(gè)指定年、月、日的時(shí)間:
比如,想構(gòu)造:2019年10月12月12日9時(shí)21分32秒
LocalDateTime beforeDate = LocalDateTime.of(2019, Month.DECEMBER, 12, 9, 21, 32);
3、修改日期
LocalDateTime rightNow = LocalDateTime.now();
rightNow = rightNow.minusYears( 2 ); // 減少 2 年
rightNow = rightNow.plusMonths( 3 ); // 增加 3 個(gè)月
rightNow = rightNow.withYear( 2008 ); // 直接修改年份到2008年
rightNow = rightNow.withHour( 13 ); // 直接修改小時(shí)到13時(shí)
4、格式化日期
LocalDateTime rightNow = LocalDateTime.now();
String result1 = rightNow.format( DateTimeFormatter.ISO_DATE );
String result2 = rightNow.format( DateTimeFormatter.BASIC_ISO_DATE );
String result3 = rightNow.format( DateTimeFormatter.ofPattern("yyyy/MM/dd") );
System.out.println("格式化后的日期(基本樣式一舉例):" + result1);
System.out.println("格式化后的日期(基本樣式二舉例):" + result2);
System.out.println("格式化后的日期(自定義樣式舉例):" + result3);
// 輸出結(jié)果:
格式化后的日期(基本樣式一舉例):2019-12-13
格式化后的日期(基本樣式二舉例):20191213
格式化后的日期(自定義樣式舉例):2019/12/13
5、時(shí)間反解析
給你一個(gè)陌生的字符串,你可以按照你需要的格式把時(shí)間給反解出來(lái)
LocalDateTime time = LocalDateTime.parse("2002--01--02 11:21",DateTimeFormatter.ofPattern("yyyy--MM--dd HH:mm"));
System.out.println("字符串反解析后的時(shí)間為:" + time);
// 輸出:字符串反解析后的時(shí)間為:2002-01-02T11:21
6、線程安全性問(wèn)題!
其實(shí)上面講來(lái)講去只講了在用法上的,這其實(shí)倒還好,并不致命,可是接下來(lái)要討論的線程安全性問(wèn)題才是致命的!
其實(shí)以前我們慣用的 Date
時(shí)間類(lèi)是可變類(lèi),這就意味著在多線程環(huán)境下對(duì)共享 Date
變量進(jìn)行操作時(shí),必須由程序員自己來(lái)保證線程安全!否則極有可能翻車(chē)。
而自 Java8
開(kāi)始推出的 LocalDateTime
卻是線程安全的,開(kāi)發(fā)人員不用再考慮并發(fā)問(wèn)題,這點(diǎn)我們從 LocalDateTime
的官方源碼中即可看出:
不說(shuō)別的,就光一句:
This class is immutable and thread-safe. (不可變、線程安全!)
除了慣用 Date
來(lái)表示時(shí)間之外,還有一個(gè)用于和 Date
連用的 SimpleDateFormat
時(shí)間格式化類(lèi)大家可能也戒不掉了!
SimpleDateFormat
最主要的致命問(wèn)題也是在于它本身并不線程安全