JDK都更新到15了,你肯定不來學一下JDK8嗎?

關注我更多精彩文章第一時間推送給你

Java之康莊大道

JDK 新特性

Oracle 對 Java 8 的官方支持時間持續到 2020 年 12 月,以後將再也不爲我的桌面用戶提供 Oracle JDK 8 的修復更新。java

不過,還會有不少第三方會經過 openjdk8 繼續維護 jdk8.python

Java 11 僅將提供長期支持服務(LTS, Long-Term-Support),還將做爲 Java 平臺的默認支持版本,而且會提供技術支持直至 2023 年 9 月,對應的補丁和安全警告等支持將持續至 2026 年。程序員

目前 Oracle 官方最近版本是 JDK15,也就是不用註冊 Oracle 帳戶就能下載的最新版,固然這是一個短時間版本,下一個長期 LTS 版本將是今年 9 月份將要發佈的 JDK17。sql

雖然官方已經不在修復更新 JDK8,可是 JDK8 仍然是具備里程碑意義的一個重要的 JDK 版本,也是多數人仍在使用的一個版本,因此我要講的內容是JDK八、JDK11的新特性。編程

JDK8 新特性

1. Lambda表達式&函數式接口

Lambda 表達式,也可稱爲閉包,它是推進 Java 8 發佈的最重要新特性。數組

Lambda 容許把函數做爲一個方法的參數(函數做爲參數傳遞進方法中)。安全

使用Lambda 表達式可使代碼變的更加簡潔緊湊。多線程

什麼是函數式接口?

Lambda表達式須要函數式接口的支持。閉包

函數式接口是指只有一個抽象方法的接口。app

JDK8提供了註解@FunctionInterface在編譯時校驗函數式接口。

JDK內置的函數式接口在 java.util.function;

例如:Runnable 接口就是一個函數式接口:

圖1

/**
 * @author yx
 * @since 2021/2/2 15:18
 */
public class JDKRunnable {
    public static void main(String[] args) {
        // 線程 1
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("JDK8以前的寫法");
            }
        };
        // 線程 2
        Runnable r2 = () -> System.out.println("JDK8以後的寫法");

        new Thread(r1).start();
        new Thread(r2).start();
    }
}
/**
 * 實現 treeSet 的比較器排序
 */
private void comparator() {
    // TreeSet 是一個有序的集合,它的做用是提供有序的Set集合。這是使用比較器排序。
    TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.length() - o2.length();
        }
    });
    // Lambda表達式寫法
    TreeSet<String> treeSet1 = new TreeSet<>((m, n) -> m.length() - n.length());
    // 方法引用寫法
    TreeSet<String> treeSet2 = new TreeSet<>(Comparator.comparingInt(String::length));
}

方法引用介紹

方法引用經過方法的名字來指向一個方法。

方法引用可使語言的構造更緊湊簡潔,減小冗餘代碼。

方法引用使用一對冒號 ::

在咱們使用Lambda表達式的時候,-> 右邊部分是要執行的代碼,即要完成的功能,能夠把這部分稱做Lambda體。有時候,當咱們想要實現一個函數式接口的那個抽象方法,可是已經有類實現了咱們想要的功能,這個時候咱們就能夠用方法引用來直接使用現有類的功能去實現。

引用方法

  • 對象引用 :: 實例方法名
  • 類名 :: 靜態方法名
  • 類名 :: 實例方法名
  1. 對象引用 :: 實例方法名

    private static void method() {
        Consumer<String> consumer = System.out::println;
        consumer.accept("方法引用 1 之對象引用::實例方法名");
    }

    System.out就是一個PrintStream類型的對象引用,而println則是一個實例方法名,須要注意的是沒有括號的喲。其中ConsumerJava內置函數式接口,下面的例子用到的都是Java內置函數式接口。Consumer中的惟一抽象方法accept方法參數列表與println方法的參數列表相同,都是接收一個String類型參數。

  2. 類名::靜態方法名

    private static void method() {
        Function<Integer, Integer> f = Math::abs;
        final Integer apply = f.apply(-3);
        System.out.println(apply);
    }

    Math是一個類而abs爲該類的靜態方法。Function中的惟一抽象方法apply方法參數列表與abs方法的參數列表相同,都是接收一個Integer類型參數。

  3. 類名::實例方法名

    private static void method() {
        BiPredicate<String, String> n = String::equals;
        final boolean test = n.test("aaa", "bbb");
        System.out.println(test);
    }

    String是一個類而equals爲該類的定義的實例方法。BiPredicate中的惟一抽象方法test方法參數列表與equals方法的參數列表相同,都是接收兩個String類型參數。

引用構造器

private static void method() {
    Function<Integer, StringBuffer> is = StringBuffer::new;
    final StringBuffer sb = is.apply(10);
    System.out.println(sb.capacity());
}

Function接口的apply方法接收一個參數,而且有返回值。在這裏接收的參數是Integer類型,與StringBuffer類的一個構造方法StringBuffer(int capacity)對應,而返回值就是StringBuffer類型。上面這段代碼的功能就是建立一個Function實例,並把它apply方法實現爲建立一個指定初始大小的StringBuffer對象。

引用數組

private static void method() {
    Function<Integer, int[]> fii = int[]::new;
    final int[] apply1 = fii.apply(20);
    System.out.println(apply1.length);

    Function<Integer, Double[]> fid = Double[]::new;
    final int[] apply2 = fii.apply(30);
    System.out.println(apply2.length);
}

引用數組和引用構造器很像,格式爲 類型[]::new,其中類型能夠爲基本類型也能夠是類。

2. Stream流式編程

Stream 流是 java8 中處理數組、集合的抽象概念,他能夠指定你但願對集合進行的操做,能夠執行很是複雜的查找、過濾和映射數據等操做。

一個Stream表面上與一個集合很相似,可是集合中保存的是數據,而流設置的是對數據的操做

Stream流的特色:

  1. Stream 本身不會存儲元素
  2. Stream 不會改變源對象,相反,他們會返回一個持有結果的新的Stream
  3. Stream 操做是延遲執行的,這意味着他們會等到須要結果的時候纔去執行
  4. Stream 遵循作什麼,而不是怎麼作的原則,只須要描述作什麼,而不用考慮程序是怎麼實現的
private static void stream() {
        int[] arr = {4, 1, 2, 5, 0, 8, 6, 5};
        // 獲取最大值
        final int max = Arrays.stream(arr).max().getAsInt();
        System.out.println(max);
        // 數組中大於3的元素的數量
        final long count = Arrays.stream(arr).filter(e -> e > 3).count();
        System.out.println(count);

        List<Student> list = Arrays.asList(
                new Student(1, "花木蘭", 25, 66.0),
                new Student(2, "李白", 21, 90.0),
                new Student(3, "諸葛亮", 21, 80.0),
                new Student(4, "公孫離", 18, 100d),
                new Student(5, "不知火舞", 21, 90d),
                new Student(5, "不知火舞", 21, 90d)
        );

        list.stream().filter(e -> e.getScore() >= 90)
                .findFirst()
                .ifPresent(System.out::println);
        Console.log("-----------filter--------------");

        list.stream().skip(1).limit(2).forEach(System.out::println);
        Console.log("------------limit-------------");

        list.stream().skip(3).distinct().forEach(System.out::println);
        Console.log("-------------distinct------------");

        list.stream().mapToInt(Student::getAge).min().ifPresent(System.out::println);
        Console.log("-------------map------------");

        final Set<String> collect = list.stream().map(Student::getName)
            .collect(Collectors.toSet());
        System.out.println(collect);
        Console.log("-------------collect------------");

        final List<Student> collect1 = list.stream()
            	.sorted(Comparator.comparingDouble(Student::getScore).reversed()
                .thenComparing(Student::getAge)
                .thenComparing(Student::getId))
                .collect(Collectors.toList());
        System.out.println(collect1);
        Console.log("----------sort---------------");

        final boolean b = list.stream().allMatch(e -> e.getAge() < 25);
        Assert.isFalse(b);
    	// 正則匹配
        final boolean b1 = list.stream()
            .anyMatch(e -> ReUtil.isMatch("^[1-9]\\d*$", e.getId().toString()));
        Assert.isTrue(b1);
        final boolean b2 = list.stream().noneMatch(e -> e.getScore() == 100d);
        Assert.isFalse(b2);
		Console.log("----------------match------------------");
        list.stream().mapToInt(Student::getAge)
                .reduce(Integer::sum)
                .ifPresent(System.out::println);
        Console.log("----------------reduce------------------");

        final DoubleSummaryStatistics collect2 = list.stream()
            .collect(Collectors.summarizingDouble(Student::getScore));
        Console.log(collect2);

    }

圖2

3. 接口默認方法

Java 8 新增了接口的默認方法。

簡單說,默認方法就是接口能夠有實現方法,並且不須要實現類去實現其方法。

只需在方法名前面加個 default 關鍵字便可實現默認方法。

爲何要有這個特性?

首先,以前的接口是個雙刃劍,好處是面向抽象而不是面向具體編程,缺陷是,當須要修改接口時候,須要修改所有實現該接口的類,目前的 java 8 以前的集合框架沒有 foreach 方法,一般能想到的解決辦法是在JDK裏給相關的接口添加新的方法及實現。然而,對於已經發布的版本,是無法在給接口添加新方法的同時不影響已有的實現。因此引進的默認方法。他們的目的是爲了解決接口的修改與現有的實現不兼容的問題。

interface Cat {
    /**
     * 接口默認方法
     */
    default void eat() {
        System.out.println("一隻小貓愛吃魚");
    }

    /**
     * Java 8 的另外一個特性是接口能夠聲明(而且能夠提供實現)靜態方法。
     */
    static void voice() {
        System.out.println("一隻小貓喵喵叫");
    }

}

interface Dog {
    default void eat() {
        System.out.println("一隻小狗啃骨頭");
    }
}

class Animal implements Cat, Dog{

    @Override
    public void eat() {
        // 第一種使用默認實現
        Dog.super.eat();
        // 第二種本身實現
        System.out.println("我是一隻新動物");
        // 調用接口的靜態方法
        Cat.voice();
    }
}

4.日期時間處理

Java 8經過發佈新的Date-Time API (JSR 310)來進一步增強對日期與時間的處理。

在舊版的 Java 中,日期時間 API 存在諸多問題,其中有:

  • 非線程安全 − java.util.Date 是非線程安全的,全部的日期類都是可變的,這是Java日期類最大的問題之一。
  • 設計不好 − Java的日期/時間類的定義並不一致,在java.util和java.sql的包中都有日期類,此外用於格式化和解析的類在java.text包中定義。java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期,將其歸入java.sql包並不合理。另外這兩個類都有相同的名字,這自己就是一個很是糟糕的設計。
  • 時區處理麻煩 − 日期類並不提供國際化,沒有時區支持,所以Java引入了java.util.Calendar和java.util.TimeZone類,但他們一樣存在上述全部的問題。

新的日期/時間API的一些設計原則是:

  • 不變性:新的日期/時間API中,全部的類都是不可變的,這對多線程有好處。
  • 關注點分離:新的API將人可讀的日期/時間和機器的日期/時間明確分離,它爲日期Date、時間Time、日期時間DateTime、時間戳unix timestamp以及時區定義了不一樣的類。
  • 清晰:在全部的類中,方法都被明肯定義 用以完成相同的行爲。舉個例子,在全部的類中都定義了now()方法、format()方法、parse()方法,而不是像之前那樣專門有一個獨立的類。爲了更好的處理問題,全部的類都使用了工廠模式和策略模式,一旦你使用了其中某個類的方法,與其餘類協同工做並不困難。
  • 實用操做:全部新的日期 時間 API 類都實現了一系列方法用以完成通用的任務,如:加、減、格式化、解析、從日期時間中提取單獨部分,等等。
  • 可擴展性:新的日期/時間API是工做在ISO-801日曆系統上的,但咱們也能夠將其應用在非ISO的日曆上。

LocalDate、LocalTime、LocalDateTime

圖3

private static void dateTime() {
        LocalDate localDate = LocalDate.now();
        LocalTime localTime = LocalTime.now();
        LocalDateTime localDateTime = LocalDateTime.now();
        Console.log("當前日期是{}, 時間是{}, 日期時間{}", localDate,
                localTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")),
                localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd 			                 HH:mm:ss")));
        // 自定義
        LocalDate date = LocalDate.of(2021, Month.AUGUST, 1);
        Console.log("自定義日期{}", date);
        // 設置地區
        LocalDate seoulDate = LocalDate.now(ZoneId.of("Asia/Seoul"));
        LocalTime seoulTime = LocalTime.now(ZoneId.of("Asia/Seoul"));
        LocalDateTime seoul = LocalDateTime.now(ZoneOffset.of("+9"));
        Console.log("首爾日期{}, 首爾時間{}, 首爾日期時間{}", seoulDate,
                seoulTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")),
                seoul.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        // 時間戳是指格林威治時間1970年01月01日00時00分00秒(北京時間1970年01月01日08時00分00秒)起至現            在的總秒數。
        Console.log("當前時間轉毫秒數{}", localDateTime
                    .toInstant(ZoneOffset.of("+8")).toEpochMilli());

        Console.log("格林威治時間納秒{}, 北京時間{}", Instant.now(), localDateTime);

        /**
         * 時間差值
         */
        Duration duration = Duration.between(localDateTime, seoul);
        System.out.println(duration);
        /**
         * 計算日期間隔
         */
        Period period = Period.between(date, seoulDate);
        System.out.println(period);

    }

圖4

5.Optional類

從 Java 8 引入的一個頗有趣的特性是 Optional 類。Optional 類主要解決的問題是臭名昭著的空指針異常(NullPointerException)這個異常就很少說了,確定是每一個 Java 程序員都很是瞭解的異常。Optional 的完整路徑是 java.util.Optional,使用它是爲了不代碼中的 if (obj != null) { } 這樣範式的代碼,能夠採用鏈式編程的風格。並且經過 Optional 中提供的 filter 方法能夠判斷對象是否符合條件,在符合條件的狀況下才會返回,map 方法能夠在返回對象前修改對象中的屬性。

Optional的用處

本質上,Optional是一個包含有可選值的包裝類,這意味着 Optional 類既能夠含有對象也能夠爲空。咱們要知道,Optional 是 Java 實現函數式編程的強勁一步,而且幫助在範式中實現。可是 Optional 的意義顯然不止於此。咱們知道,任何訪問對象方法或屬性的調用均可能致使 NullPointerException,在這裏,我舉個簡單的例子來講明一下:

String result = test.getName().getTime().getNum().getAnswer();

在上面的這個代碼中,若是咱們須要確保不觸發異常,就得在訪問每個值以前對其進行明確地檢查,就是使用if else對test等值進行判斷是否爲null,這很容易就變得冗長,難以維護。爲了簡化這個過程,Google公司著名的Guava項目引入了Optional類,Guava經過使用檢查空值的方式來防止代碼污染,並鼓勵程序員寫更乾淨的代碼。Optional其實是個容器:它能夠保存類型T的值,或者僅僅保存null。Optional提供不少有用的方法,這樣咱們就不用顯式進行空值檢測。

Optional 的構造函數

Optional 的三種構造方式:Optional.of(obj), Optional.ofNullable(obj) 和明確的 Optional.empty()

  • Optional.of(obj):它要求傳入的 obj 不能是 null 值的, 不然直接報NullPointerException 異常。
  • Optional.ofNullable(obj):它以一種智能的,寬容的方式來構造一個 Optional 實例。來者不拒,傳 null 進到就獲得 Optional.empty(),非 null 就調用 Optional.of(obj).
  • Optional.empty():返回一個空的 Optional 對象

Optional 的經常使用函數

  • isPresent:若是值存在返回true,不然返回false。
  • ifPresent:若是Optional實例有值則爲其調用consumer,不然不作處理
  • get:若是Optional有值則將其返回,不然拋出NoSuchElementException。所以也不常常用。
  • orElse:若是有值則將其返回,不然返回指定的其它值。
  • orElseGet:orElseGet與orElse方法相似,區別在於獲得的默認值。orElse方法將傳入的字符串做爲默認值,orElseGet方法能夠接受Supplier接口的實現用來生成默認值
  • orElseThrow:若是有值則將其返回,不然拋出supplier接口建立的異常。
  • filter:若是有值而且知足斷言條件返回包含該值的Optional,不然返回空Optional。
  • map:若是有值,則對其執行調用mapping函數獲得返回值。若是返回值不爲null,則建立包含mapping返回值的Optional做爲map方法返回值,不然返回空Optional。
  • flatMap:若是有值,爲其執行mapping函數返回Optional類型返回值,不然返回空Optional。

Optional 應該怎樣用

在使用 Optional 的時候須要考慮一些事情,以決定何時怎樣使用它。重要的一點是 Optional 不是 Serializable。所以,它不該該用做類的字段。Optional 類能夠將其與流或其它返回 Optional 的方法結合,以構建流暢的API。咱們來看一個示例,咱們不使用Optional寫代碼是這樣的:

public String getName(User user) {
    if (user == null) {
        return "";
    }
    return user.getName();
}

接着咱們來改造一下上面的代碼,使用Optional來改造,咱們先來舉一個Optional濫用,沒有達到流暢的鏈式API,反而複雜的例子,以下:

public String getName(User user) {
    Optional<User> u = Optional.ofNullable(user);
    if(!u.isPresent()) {
        return "";
    }
    return u.get().getName();
}

這樣改寫非但不簡潔,並且其操做仍是和第一段代碼同樣。無非就是用isPresent方法來替代原先user==null。這樣的改寫並非Optional正確的用法,咱們再來改寫一次。

public String getName(User user) {
    return Optional.ofNullable(user)
        .map(u -> u.getName())
        .orElse("");
}

這樣纔是正確使用Optional的姿式。那麼按照這種思路,咱們能夠安心的進行鏈式調用,而不是一層層判斷了。

6.Base64

在Java8中,Base64編碼已經成爲Java類庫的標準。

Java 8 內置了 Base64 編碼的編碼器和解碼器。

Base64工具類提供了一套靜態方法獲取下面三種BASE64編解碼器:

· 基本:輸出被映射到一組字符A-Za-z0-9+/,編碼不添加任何行標,輸出的解碼僅支持A-Za-z0-9+/。

· URL:輸出映射到一組字符A-Za-z0-9+_,輸出是URL和文件。

· MIME:輸出隱射到MIME友好格式。輸出每行不超過76字符,而且使用'\r'並跟隨'\n'做爲分割。編碼輸出最後沒有行分割。

public static void main(String[] args) {
        //編碼,加密getEncoder()
        String str = Base64.getEncoder()
            .encodeToString("java8_Base64?".getBytes(StandardCharsets.UTF_8));
        Console.log("標準加密以後的字符串是 {}", str);
        //解碼,解密getDecoder()
        byte[] decode = Base64.getDecoder().decode(str);
        Console.log("標準解碼 {}", new String(decode, StandardCharsets.UTF_8));

        //URL編碼
        str = Base64.getUrlEncoder()
            .encodeToString("dksiofdo+/d,s;".getBytes(StandardCharsets.UTF_8));
        Console.log("加密後字符串是:{}", str);
        //URL解碼
        byte[] decode1 = Base64.getUrlDecoder().decode(str);
        Console.log("URL解碼後 {}", new String(decode1, StandardCharsets.UTF_8));


        //Mime編碼
        str = Base64.getMimeEncoder()
            .encodeToString("dksiofdo+/d,s;ddd".getBytes(StandardCharsets.UTF_8));
        Console.log("加密後字符串是:{}", str);
        //Mime解碼
        byte[] decode2 = Base64.getMimeDecoder().decode(str);
        Console.log("Mime解碼後 {}", new String(decode2, StandardCharsets.UTF_8));

        /*
        Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator):
        返回具備給定lineLength的已修改MIME變體的編碼器
        (向下舍入到最接近的4的倍數 - 輸出在lineLength<= 0 時不分紅行)和lineSeparator。
        當lineSeparator包含RFC 2045的表1中列出的任何Base64字母字符時,它會拋出                           java.lang.IllegalArgumentException。

        至關於用lineSeparator隔開加密後的字符串,每lineLength(4的倍數向下取整)隔開
         */
        String s = Base64.getMimeEncoder(6, ".?--".getBytes())
                .encodeToString("jimidssafsaa".getBytes(StandardCharsets.UTF_8));
        Console.log("加密後 {}", s);
        byte[] decode3 = Base64.getMimeDecoder().decode(s);
        Console.log("Mime解碼後 {}", new String(decode3, StandardCharsets.UTF_8));

    }

JDK11新特性

1.類型推斷

private static void jdk11() {
    var s = "world";
    var list = new ArrayList<String>();
    list.add(s);
    list.add("java");
    list.add("python");
    list.stream().map(e -> "Hello, " + e)
        .forEach(System.out::println);
}

局部變量類型推斷就是左邊的類型直接使用 var 定義,而不用寫具體的類型,編譯器能根據右邊的表達式自動推斷類型。

2. 字符串增強

String新增了strip()方法,和trim()相比,strip()能夠去掉Unicode空格,例如,中文空格:

// 判斷字符串是否爲空白
" ".isBlank(); // true
// 去除首尾空格
" Javastack ".strip(); // "Javastack"
// 去除尾部空格
" Javastack ".stripTrailing(); // " Javastack"
// 去除首部空格
" Javastack ".stripLeading(); // "Javastack "
// 複製字符串
"Java".repeat(3);// "JavaJavaJava"
// 行數統計
"A\nB\nC".lines().count(); // 3

3. 集合增強

自 Java 9 開始,Jdk 裏面爲集合(List/ Set/ Map)都添加了 of 和 copyOf 方法,它們兩個都用來建立不可變的集合,來看下它們的使用和區別。

var list = List.of("Java", "Python", "C");
var copy = List.copyOf(list);
System.out.println(list == copy); // true
var list = new ArrayList<String>();
var copy = List.copyOf(list);
System.out.println(list == copy); // false
static <E> List<E> of(E... elements) {
  switch (elements.length) { // implicit null check of elements
    case 0:
        return ImmutableCollections.emptyList();
    case 1:
        return new ImmutableCollections.List12<>(elements[0]);
    case 2:
        return new ImmutableCollections.List12<>(elements[0], elements[1]);
    default:
        return new ImmutableCollections.ListN<>(elements);
  }
}
static <E> List<E> copyOf(Collection<? extends E> coll) {
    return ImmutableCollections.listCopy(coll);
}
static <E> List<E> listCopy(Collection<? extends E> coll) {
    if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) {
        return (List<E>)coll;
    } else {
        return (List<E>)List.of(coll.toArray());
    }
}

能夠看出 copyOf 方法會先判斷來源集合是否是 AbstractImmutableList 類型的,若是是,就直接返回,若是不是,則調用 of 建立一個新的集合。

示例2由於用的 new 建立的集合,不屬於不可變 AbstractImmutableList 類的子類,因此 copyOf 方法又建立了一個新的實例,因此爲false.

注意:使用of和copyOf建立的集合爲不可變集合,不能進行添加、刪除、替換、排序等操做,否則會報 java.lang.UnsupportedOperationException 異常。

上面演示了 List 的 of 和 copyOf 方法,Set 和 Map 接口都有。

4.Stream流處理增強

private static void jdk11() {
    var s = "world";
    var list = new ArrayList<String>();
    list.add(s);
    list.add("java");
    list.add("python");
    list.add("go");
    list.stream().map(e -> "Hello, " + e)
        .forEach(System.out::println);

    /**
         * lambda表達式體爲 true 打印,遇到 false則再也不繼續
         */
    list.stream().takeWhile(e -> !StrUtil.startWith(e, "p"))
        .forEach(System.out::println);

    System.out.println("--------------------------------");
    /**
         * lambda表達式體爲true不打印,一直到遇到false開始打印
         */
    list.stream().dropWhile(e -> !StrUtil.startWith(e, "p"))
        .forEach(System.out::println);
   
}

參考:https://www.jianshu.com/p/84a6050c5391

參考:https://blog.csdn.net/csdnnews/article/details/105236653

關注我更多精彩文章第一時間推送給你

Java之康莊大道

相關文章
相關標籤/搜索