jdk8 java8 新特性 一篇文章總結


雖然現在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爲空,非法數據");
	        }
        }

除此之外,其他方法解析入下:

  1. 三個靜態方法創建
    • Optional.empty()
    • Optional.of() 如果傳入對象爲空直接拋NPE
    • Optional.ofNullable() 爲空返回空 不拋異常
  2. 實例方法-獲取
    • boolean optional.isPresent() 返回是否 不爲空
    • T get() 獲得內部元素 爲空拋異常
    • T orElse(T) 獲得內部元素 爲空 使用傳入默認值
    • T orElseGet(S) 獲得內部元素 爲空使用 生產者接口 生產默認值
    • Optional filter(Predicate<? super T> predicate) 篩選元素,傳入篩選接口
    • Optional flatMap(Function<? super T, Optional> mapper) 扁平化映射,獲取內部的元素映射結果

Stream流

Java 8 中的 Stream 是對集合(Collection , Map ) 對象功能的增強,它專注於對集合對象進行各種便利、高效的操作,它與 java.io 包裏的 InputStream 和 OutputStream 是完全不同的概念。

Stream API 藉助於同樣新出現的 Lambda 表達式,極大的提高編程效率和程序可讀性。同時它提供串行和並行兩種模式進行匯聚操作,併發模式能夠充分利用多核處理器的優勢,使用 fork/join 並行方式來拆分任務和加速處理過程。通常編寫並行代碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼,就可以很方便地寫出高性能的併發程序。所以java中首次出現的 java.util.stream 是一個函數式語言+多核時代綜合影響的產物。

Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。而和迭代器又不同的是,Stream 可以並行化操作,迭代器只能命令式地、串行化操作

流分類

  • 有限流 , 有限個元素
  • 無限流 , 無限個元素

流創建

  1. 從集合創建流

    • collection.stream
    • collection.parallelStream
  2. 靜態方法創建

    • Stream.of(array); 數組創建
    • Stream.generate(Supplier) 生產者接口創建 無限流
    • Stream iterate(final T seed, final UnaryOperator f) 迭代接口創建,提供一個起始值和後續操作 無限流
    • Pattern.compile().splitAsStream() 根據正則分隔產生流
    • IntStream range(int startInclusive, int endExclusive) 根據開始和結束生成流

流中間操作

流在未消費/未做終結操作之前可以執行中間操作,以達到業務需求的目的,如去重,取數,映射,排序,過濾等,以下是常用的大部分操作

  1. 去重 distinct()
  2. 排序 .sorted() 可以傳入指定的比較順序接口
  3. 過濾 .filter() 傳入指定的過濾接口
  4. 映射 flatXXX() ,這種通常是複雜元素流中提取內部元素流操作 傳入對應取數邏輯的接口
  5. 統計 .count()
  6. 取數
    • 獲取第一位 .findFirst()
    • 獲取任意一位 .findAny()
    • 取最大 .max()
    • 取最小 .min()

流終結操作

在得到我們需要的元素流之後,我們需要調用終結操作,以獲取/消費目標流中元素,常見有,打印,轉換數組,轉換集合對象,經過終結操作之後,無法再操作此流.

  1. 消費類 .forEach() .forEachOrdered() 排序後操作
  2. 轉換數組 .toArray()
  3. 轉換集合對象
    - .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表達式引用實例與實例方法,類與靜態方法
有三種:

  1. 外部實例::方法
  2. 類::靜態方法
  3. 內部實例::實例方法
  4. 構造器引用

如下,使用的前提時,參數個數能夠對應方法,如果不能推斷則不能使用會報編譯期錯誤


 	
	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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章