文章目錄
雖然現在jdk已經更新到了14.0.1 , 但是國內大部分用的版本是jdk8 , 很多時候我們只知道jdk有lamda表達式和流,其實除此之外你能夠知道別人不知道的點,那麼這就是你面試中的亮點,瞭解到熟練差距可能就在這裏,同時作爲自己的一個知識積累也是不錯的.
正文
接口默認實現和靜態方法
也就是說在1.8之後可以在接口中提供默認的實現,而靜態方法與默認實現的區別在於調用方式不一樣,而且默認方法可以被子類重寫覆蓋
引入默認實現,那麼在Java中多實現的原則,出現多個默認方法衝突,應該在子類中重寫,解決衝突
默認實現在源碼中也得到了體現,如:
- java.lang.Iterable#forEach
- java.util.Collection#stream
- java.util.Map#forEach
java.util.Optional 類
NPE(NullPointerException)問題是我們常生產的bug,也是讓我們頭疼的問題,有調用就可能產生,java.util.Optional
類則是用來規範解決這一問題的類,同時在現在的框架如,JPA框架也支持提供Optional返回
如下:
public void testNPE(){
Object o = null;
if (o==null){
System.out.println("xxx爲空,非法數據");
return ;
}
Optional optional =
Optional.ofNullable(o);
// 查看爲空
if (!optional.isPresent()){
System.out.println("xxx爲空,非法數據");
}
}
除此之外,其他方法解析入下:
- 三個靜態方法創建
- Optional.empty()
- Optional.of() 如果傳入對象爲空直接拋NPE
- Optional.ofNullable() 爲空返回空 不拋異常
- 實例方法-獲取
- boolean optional.isPresent() 返回是否
不爲空
- T get() 獲得內部元素 爲空拋異常
- T orElse(T) 獲得內部元素 爲空 使用傳入默認值
- T orElseGet(S) 獲得內部元素 爲空使用 生產者接口 生產默認值
- Optional filter(Predicate<? super T> predicate) 篩選元素,傳入篩選接口
- Optional flatMap(Function<? super T, Optional> mapper) 扁平化映射,獲取內部的元素映射結果
- boolean optional.isPresent() 返回是否
Stream流
Java 8 中的 Stream 是對集合(Collection , Map ) 對象功能的增強,它專注於對集合對象進行各種便利、高效的操作,它與 java.io 包裏的 InputStream 和 OutputStream 是完全不同的概念。
Stream API 藉助於同樣新出現的 Lambda 表達式,極大的提高編程效率和程序可讀性。同時它提供串行和並行兩種模式進行匯聚操作,併發模式能夠充分利用多核處理器的優勢,使用 fork/join 並行方式來拆分任務和加速處理過程。通常編寫並行代碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼,就可以很方便地寫出高性能的併發程序。所以java中首次出現的 java.util.stream 是一個函數式語言+多核時代綜合影響的產物。
Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。而和迭代器又不同的是,Stream 可以並行化操作,迭代器只能命令式地、串行化操作
流分類
- 有限流 , 有限個元素
- 無限流 , 無限個元素
流創建
-
從集合創建流
- collection.stream
- collection.parallelStream
-
靜態方法創建
- Stream.of(array); 數組創建
- Stream.generate(Supplier) 生產者接口創建 無限流
- Stream iterate(final T seed, final UnaryOperator f) 迭代接口創建,提供一個起始值和後續操作 無限流
- Pattern.compile().splitAsStream() 根據正則分隔產生流
- IntStream range(int startInclusive, int endExclusive) 根據開始和結束生成流
流中間操作
流在未消費/未做終結操作之前可以執行中間操作,以達到業務需求的目的,如去重,取數,映射,排序,過濾等,以下是常用的大部分操作
- 去重 distinct()
- 排序 .sorted() 可以傳入指定的比較順序接口
- 過濾 .filter() 傳入指定的過濾接口
- 映射 flatXXX() ,這種通常是複雜元素流中提取內部元素流操作 傳入對應取數邏輯的接口
- 統計 .count()
- 取數
- 獲取第一位 .findFirst()
- 獲取任意一位 .findAny()
- 取最大 .max()
- 取最小 .min()
流終結操作
在得到我們需要的元素流之後,我們需要調用終結操作,以獲取/消費目標流中元素,常見有,打印,轉換數組,轉換集合對象,經過終結操作之後,無法再操作此流.
- 消費類 .forEach() .forEachOrdered() 排序後操作
- 轉換數組 .toArray()
- 轉換集合對象
- .collect(Collectors.toSet())
- .collect(Collectors.toList())
- .collect(Collectors.toMap()) / .collect(Collectors.toConcurrentMap()) 此類需要提供 kv 的映射關係
lambda 表達式
lambda 表達式 其實就是語法糖,目的就是簡化代碼,讓代碼更加優雅.在編譯過程中會轉換成實際內部類/方法調用等實現,在編譯期已經替換因此不會影響代碼執行效率,可以放心使用
基礎:簡化方法
我們直接使用代碼+註釋從一個普通內部類的參數到語句到返回值,到最後的各種靜態方法,實例方法的簡化介紹
無參數 簡化
第一個例子很重要!
public void testContext() {
// 無參數
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(1111);
}
};
// 這就是最基本的表達式
// 首先,Java 編譯器 具備類型推斷功能 , 可以知道這個表達式是實現Runnable接口 ,那麼類型可以省略
// 同樣 方法只有一個 方法名自然也可以省略
// () 表示 無參數
// -> 後面接方法體
// System.out.println(1111); 是 實際的方法體
runnable = ()-> System.out.println(1111);
// 本來完全版應該加上大括號 {System.out.println(1111);}
// 但是因爲是單條語句,所以你懂得,省略了
}
含參數,含返回值
public void testMuti() {
// 這是過濾 kv 的一個接口實現
BiPredicate<String, String> biPredicate = new BiPredicate<String, String>() {
@Override
public boolean test(String s, String s2) {
return s.equals(s2);
}
};
// 根據 上面原理可以知道
// 首先 類型可以省略
// 只保留方法
// 中間加上箭頭
biPredicate = (String s, String s2) -> {
return s.equals(s2);
};
// 除此之外 方法只有一條 那麼大括號可以省略,同理 返回值 是不是也可以省略 (其實這裏如果省略大括號,不省略return 會報錯)
biPredicate = (String s, String s2) -> s.equals(s2);
// 上面我們分析,編譯器可以得知接口信息,那麼類型是不是可以從左邊推測出來
biPredicate = (s, s2) -> s.equals(s2);
// 最後就得到了最終版本
// 附: 這中間的命名 可以隨便命名 ,與方法體一致即可
}
基礎:引用方法
除了上述的內部類可以使用lambda簡化方法,還有一部分也可以使用lambda表達式引用實例與實例方法,類與靜態方法
有三種:
- 外部實例::方法
- 類::靜態方法
- 內部實例::實例方法
- 構造器引用
如下,使用的前提時,參數個數能夠對應方法,如果不能推斷則不能使用會報編譯期錯誤
public void testLamda() {
// 常規內部類
IntConsumer intConsumer = new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
};
// 外部實例::方法
intConsumer = System.out::println;
// 類::靜態方法
Stream.generate(Math::random);
// 內部實例::實例方法
class OB {
private double d;
public OB(double d) {
this.d = d;
}
public void print() {
System.out.println(d);
}
}
// 內部實例::實例方法
Stream.generate(() -> new OB(Math.random())).limit(10).forEach(OB::print);
// 構造器引用
Stream.generate(Object::new);
}
函數接口
jdk8中引入函數式接口,也是爲了補充Java中函數編程的不足,函數接口可以使語義更清晰,也符合單一職責原則,可以使代碼中,易變與不變分離開,與lambda表達式可以很好的結合
如果接口中只有一個抽象方法,稱爲函數式接口,,可以使用一個註解 @FunctionalInterface,可以檢查接口是否是函數式接口,同時jdk8提供了4個核心函數以及其他的一些函數接口
4個核心函數接口
- Consumer 消費型 對傳入T 操作,消費 返回void
- Supplier 供給型接口 無參 返回一個T對象 , 如上述的 Stream.generate() 傳入就是供給接口
- Function<T,R> 函數型接口 傳入一個參數 返回任意一種類型 , 可以衍生各種運算等操作
- Predicate 斷言型接口 傳入一個參數 返回布爾值 ,流中常用來過濾元素
其他衍生的函數式接口
- BiConsumer<T, U> 針對 兩個參數的消費接口 ,在Map類的forEach中
- UnaryOperator 一元操作,接收一個參數 運算操作之後 返回同樣的參數
- BiFunction<T, U, R> 二元操作 , 接收兩個參數 返回任意一種類型
- BiPredicate<T, U> 兩個參數的斷言型接口 傳入兩個參數 返回布爾值
日期時間新api
jdk8 日期時間 新 api,主要類 有以下
- LocalDate 日期
- LocalTime 時間
- LocalDateTime 日期時間 上面兩個的綜合
- DateTimeFormatter 格式化
- Instant 時間戳
- Duration 時間間隔
- Period 日期間隔
- TemporalAdjuster 時間校正器 TemporalAdjusters 工具類
除此之外,還有時區等操作類 使用較少就不介紹
日期時間創建
/***
* 創建時間 一般就只有三種需求
* 當前時間 ,
* 指定時間 ,
* 現有時間字符串 ,
*/
public void testCreate(){
// 當前時間
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = LocalDateTime.of(localDate, localTime);
// 指定時間
LocalDateTime of = LocalDateTime.of(2020, 6, 7, 12, 22, 11, 11);
// 指定字符串
LocalDateTime parse = LocalDateTime.parse("2020-06-07 12:22:11:11", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SS"));
System.out.println(localDate);
System.out.println(localTime);
System.out.println(localDateTime);
System.out.println(localDateTime1);
System.out.println(of);
System.out.println(parse);
}
修改
/***
* 業務中時間操作就是計算業務時間有沒有超時 ,
* 常規也是把業務時間加減之後對比另一個業務時間或者當前時間
* 還有一些捨棄精度操作
* 還有計算 時間間隔
*
*
* 時間操作修改,不用以前的計算加減 , 或者新建一堆的Calender
* 在LocalDateTime(其他兩個一樣,下面就按這個討論) 中有提供加減方法,需要注意的是
* 加減之後會形成一個新的對象 得到呃數值是在新的對象中,對於原對象是沒有改變的
*
*
*/
public void testUpdate() throws InterruptedException {
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
// 加減時間
LocalDateTime localDateTime1 = localDateTime
.plusYears(1)
.plusMonths(1)
.plusDays(1)
.plusHours(-1).plusMinutes(1)
.plusSeconds(1)
.plusNanos(2);
System.out.println(localDateTime1);
// 對比 負數小 正數大
System.out.println(localDateTime.compareTo(localDateTime1));
// 當然也可以使用after before equal
boolean after = localDateTime.isAfter(localDateTime1);
boolean before = localDateTime.isBefore(localDateTime1);
boolean equal = localDateTime.isEqual(localDateTime1);
// 捨棄精度 保留到秒
LocalDateTime localDateTime2 = localDateTime1.truncatedTo(ChronoUnit.SECONDS);
System.out.println(localDateTime2);
Instant start = Instant.now();
TimeUnit.SECONDS.sleep(2);
Instant end = Instant.now();
// 得到間隔
Duration between = Duration.between(start, end);
// 顯示
System.out.println(between.getNano());
System.out.println(between.getSeconds());
System.out.println(between.toMillis());
}
獲取特定時間與格式化
/***
* 實際業務中 獲取特殊的 年月分秒 還是比較少的
* 獲取年月分秒也是爲了比較
* 還有就是一些特殊的格式化操作
* 同樣使用LocalDateTime 演示
*
*/
public void testGet(){
LocalDateTime localDateTime = LocalDateTime.now();
int year = localDateTime.getYear();
Month month = localDateTime.getMonth();
int monthValue = localDateTime.getMonthValue();
int dayOfYear = localDateTime.getDayOfYear();
int dayOfMonth = localDateTime.getDayOfMonth();
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
int dayOfWeekValue = dayOfWeek.getValue();
int hour = localDateTime.getHour();
int minute = localDateTime.getMinute();
int second = localDateTime.getSecond();
int nano = localDateTime.getNano();
System.out.println(localDateTime);
System.out.println(year);
System.out.println(monthValue);
System.out.println(dayOfYear);
System.out.println(dayOfMonth);
System.out.println(dayOfWeekValue);
System.out.println(hour);
System.out.println(minute);
System.out.println(second);
System.out.println(nano);
// 格式化採用的是 格式化類
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SS");
System.out.println(dateTimeFormatter.format(localDateTime));
// 針對不同的實例 不存在的值會拋出 UnsupportedTemporalTypeException
LocalDate localDate = LocalDate.now();
System.out.println(dateTimeFormatter.format(localDate));
}
特殊時間需求:時間校準
/***
* 時間校正器
*/
public void testJusters(){
LocalDate localDate = LocalDate.now();
TemporalAdjuster temporalAdjuster = TemporalAdjusters.firstDayOfMonth();
LocalDate with = localDate.with(temporalAdjuster);
System.out.println(localDate);
System.out.println(with);
// 下個週末
System.out.println(localDate.with(TemporalAdjusters.next(DayOfWeek.SATURDAY)));
}
源碼改造HashMap CurrentHashMap
HashMap 底層採用了 紅黑樹 而不是傳統的全部鏈表,制定了鏈表元素大於8則樹化,小於6則反樹化,優化了hash碰撞下的查詢性能
詳細請看,我發佈的 HashMap源碼解讀
CurrentHashMap 底層採用CAS(讀)+synchronized(寫) +數組+鏈表+紅黑樹 , 而不是原來segment的分段鎖,segment繼承自ReentrantLock . 鎖的粒度也從原來對需要進行數據操作的Segment加鎖,調整爲對每個數組元素加鎖(Node)。鏈表轉化爲紅黑樹則與HashMap一樣爲了查詢性能
有機會補上對CurrentHashMap的源碼解讀
重複註解,和註解類型的註解
https://blog.csdn.net/liupeifeng3514/article/details/80722003