關注我更多精彩文章第一時間推送給你
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 接口就是一個函數式接口:
/** * @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體。有時候,當咱們想要實現一個函數式接口的那個抽象方法,可是已經有類實現了咱們想要的功能,這個時候咱們就能夠用方法引用來直接使用現有類的功能去實現。
引用方法
- 對象引用 :: 實例方法名
- 類名 :: 靜態方法名
- 類名 :: 實例方法名
-
對象引用 :: 實例方法名
private static void method() { Consumer<String> consumer = System.out::println; consumer.accept("方法引用 1 之對象引用::實例方法名"); }
System.out
就是一個PrintStream
類型的對象引用,而println
則是一個實例方法名,須要注意的是沒有括號的喲。其中Consumer
是Java內置函數式接口,下面的例子用到的都是Java內置函數式接口。Consumer
中的惟一抽象方法accept
方法參數列表與println
方法的參數列表相同,都是接收一個String
類型參數。 -
類名::靜態方法名
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
類型參數。 -
類名::實例方法名
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流的特色:
- Stream 本身不會存儲元素
- Stream 不會改變源對象,相反,他們會返回一個持有結果的新的Stream
- Stream 操做是延遲執行的,這意味着他們會等到須要結果的時候纔去執行
- 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); }
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
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); }
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