1.JDK8的新特性
1.1 Lambda表達式
1.1.1 舉例
public class LambdaTest {@Testpublic void test1(){Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("test1");}};r1.run();//Lambda表達式的寫法Runnable r2 = () ->{System.out.println("test2");};r2.run();}
}
1.1.2 格式
->:lambda操作符或箭頭操作符
->的左邊:lambda形參列表,對應著要重寫的接口中的抽象方法的形參列表。
->的右邊:lambda體,對應著接口的實現類要重寫的方法的方法體。
//1.無參且無返回值@Testpublic void test2(){Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("test1");}};//Lambda表達式的寫法Runnable r2 = () ->{System.out.println("test2");};}//2.需要一個參數但無返回值@Testpublic void test3(){Consumer<String> con = (String s) ->{System.out.println(s);};}//3.數據類型可以省略,因為可由編譯器通過類型推斷得出@Testpublic void test4(){Consumer<String> con = (s) ->{System.out.println(s);};}//4.lambda若只需要一個參數,參數的小括號可以省略@Testpublic void test5(){Consumer<String> con = s ->{System.out.println(s);};}//5.lambda需要兩個或以上的參數,多條執行語句并且具有返回值@Testpublic void test6(){Comparator<Integer> com = (o1,o2) ->{System.out.println(o1);System.out.println(o2);return o1.compareTo(o2);};}//6.當lambda體只有一條語句時,return與大括號若有,都可以省略@Testpublic void test7(){Comparator<Integer> com = (o1,o2) -> o1.compareTo(o2);}
1.1.3 lambda表達式的本質
- lambda表達式作為接口的實現類的對象。
- lambda表達式是一個匿名函數。
1.2 函數式(Functional)接口
1.2.1 介紹
如果接口中只聲明有一個抽象方法,則此接口就稱為函數式接口。
因為只有給函數式接口提供實現類的對象時,我們才可以使用lambda表達式。
1.2.2 位置
JDK8中聲明的函數式接口都在java.util.function包下。
1.2.3 4個基本的函數式接口
- 消費型接口:Consumer 對應的抽象方法:void accept(T t)。
- 供給型接口:Supplier 對應的抽象方法:T get()。
- 函數型接口:Function<T,R> 對應的抽象方法:R apply(T t)。
- 判斷型接口:Predicate 對應的抽象方法:boolean test(T t)。
1.3 方法引用
Integer :: compare;
1.3.1 理解
- 可以看做是基于lambda表達式的進一步刻畫。
- 當需要提供一個函數式接口的實例時,我們可以使用lambda表達式提供此實例。
- 當滿足一定的條件的情況下,我們還可以使用方法引用或構造器引用替換lambda表達式。
1.3.2 本質
- 方法引用作為了函數式接口的實例。
1.3.3 格式
類(或對象) :: 方法名
1.3.4 說明
- 對象 :: 實例方法
- 要求:函數式接口中的抽象方法a與其內部實現時調用的對象的某個方法b的形參列表和返回值類型相同或一致。
- 注意:方法b必須是非靜態方法,需要對象調用。
//方法引用1 對象 :: 實例方法@Testpublic void test1() {PrintStream ps = System.out;Consumer<String> con = ps::println;Supplier<String> sup = emp::getName;}
- 類 :: 靜態方法
- 要求:函數式接口中的抽象方法a與其內部實現時調用的類的某個靜態方法b的形參列表和返回值類型相同或一致。此時,可以考慮使用方法b實現對方法a的替換、覆蓋,即方法引用。
- 注意:方法b必須是靜態方法,需要類調用。
//方法引用2 類 :: 靜態方法@Testpublic void test2() {Comparator<Integer> com = Integer::compare;Function<Double,Long> fun = Math::round;}
- 類 :: 實例方法
- 要求:函數式接口中的抽象方法a與其內部實現時調用的對象的某個方法b的返回值類型。同時,抽象方法a中有n個參數,方法b中有n-1個參數,且抽象方法a的第一個參數作為方法b的調用者,且抽象方法a的后n-1個參數與方法b的n-1個參數的類型相同或一致。此時,可以考慮使用方法b實現對方法a的替換、覆蓋,即方法引用。
- 注意:方法b必須是非靜態方法,需要對象調用。但形式上寫成對象a所屬的類。
//方法引用3 類 :: 實例方法@Testpublic void test4() {Comparator<String> com = String::compareTo;BiPredicate<String,String> bp = String::equals;}
1.4 構造器引用
1.4.1 格式
類名 :: new
@Testpublic void test1(){//調用的是Employee類中的空參構造器Supplier<Employee> sup = Employee::new;//調用的是Employee類中參數是Integer/int的構造器Function<Integer,Employee> fun = Employee::new;//調用的是Employee類中參數是Integer/int,String的構造器BiFunction<Integer,String,Employee> bfun = Employee::new;}
1.4.2 說明
- 調用了類名對應的類中的某一個確定的構造器。
- 具體調用類中哪一個構造器取決于函數式接口的抽象方法的形參列表。
1.5 數組引用
1.5.1 格式
數組名[] :: new
@Testpublic void test2(){Function<Integer,Employee[]> fun = Employee[]::new;}
1.6 StreamAPI
1.6.1 介紹
Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常復雜的查找、過濾和映射數據等操作。 使用 Stream API 對集合數據進行操作,就類似于使用 SQL 執行的數據庫查詢。也可以使用 Stream API 來并行執行操作。簡言之,Stream API 提供了一種高效且易于使用的處理數據的方式。
1.6.2 使用說明
- Stream 自己不會存儲元素。
- Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream。
- Stream 操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。即一旦執行終止操作,就執行中間操作鏈,并產生結果。
- Stream 一旦執行了終止操作,就不能再調用其它中間操作或終止操作了。
1.6.3 執行流程
- Stream的實例化
- 通過集合實例化
- 通過數組實例化
- 通過Stream的of()方法實例化
//方式1:通過集合@Testpublic void test1(){List<Employee> list = EmployeeData.getEmployees();//返回一個順序流Stream<Employee> stream = list.stream();//返回一個并行流Stream<Employee> stream1 = list.parallelStream();}//方式2:通過數組@Testpublic void test2(){//返回一個流Integer[] arr = new Integer[]{1,2,3};Stream<Integer> stream = Arrays.stream(arr);int[] arr1 = new int[]{1,2,3};IntStream stream1 = Arrays.stream(arr1);}//方式3:通過Stream的of()@Testpublic void test3(){Stream<String> stream = Stream.of("AA", "BB");}
- 一系列的中間操作
- 篩選與切片
- 映射
- 排序
//1-篩選與切片@Testpublic void test1(){//filter(Predicate p)-接收Lambda,從流中排除某些元素//練習:查詢員工表中薪資大于7000的員工信息List<Employee> list = EmployeeData.getEmployees();Stream<Employee> stream = list.stream();stream.filter(employee -> emp.getSalary() > 7000).forEach(System.out::println);//limit(n)-使其元素不超過指定的數量Stream<Employee> stream1 = list.stream();stream1.limit(5).forEach(System.out::println);//skip(n)-跳過元素,返回一個去掉前n個元素的流,若流中元素不足n個,返回一個空流Stream<Employee> stream2 = list.stream();stream2.skip(5).forEach(System.out::println);//distinct()-篩選元素,通過流所生成元素的hashCode()和equals()去除重復元素Stream<Employee> stream3 = list.stream();stream3.distinct().forEach(System.out::println);}//2-映射@Testpublic void test2(){//map(Function f)-接收一個函數作為參數,將元素轉換為其他形式或提取信息,該函數會被應用到每一個元素上//練習:轉換為大寫List<String> list = Arrays.asList("aa", "bb", "cc");Stream<String> stream = list.stream();//方式1stream.map(str -> str.toUpperCase()).forEach(System.out::println);//方式2stream.map(String :: toUpperCase()).forEach(System.out::println);}//3-排序@Testpublic void test3(){//sorted()-自然排序Integer[] arr = new Integer[]{213,432,5435,6456};Arrays.stream(arr).sorted().forEach(System.out::println);//sorted(Comparator com)-定制排序List<Employee> list = EmployeeData.getEmployees();list.stream().sorted((e1,e2)->e1.getAge() - e2.getAge()).forEach(System.out::println);}
- 執行終止操作
- 匹配與查找
- 歸約
- 收集
//1-匹配與查找@Testpublic void test1(){//allMatch(Predicate p)-檢查是否匹配所有元素//練習:是否所有員工的年齡都大于18List<Employee> list = EmployeeData.getEmployees();System.out.println(list.stream().allMatch(emp -> emp.getAge() > 18));//anyMatch(Predicate p)-檢查是否至少匹配一個元素//練習:是否存在員工的工資大于10000System.out.println(list.stream().anyMatch(emp->emp.getSalary()>10000));//findFirst-返回第一個元素System.out.println(list.stream().findFirst());//count-返回流中元素的總個數List<Employee> list = EmployeeData.getEmployees();System.out.println(list.stream().count());//max(Comparator c)/min(Comparator c)-返回流中的最大值/最小值//練習:返回最高的工資System.out.println(list.stream().map(emp -> emp.getSalary()).max((salary1, salary2) -> Double.compare(salary1, salary2)).get());//forEach(Consumer c)-內部迭代list.stream().forEach(System.out::println)}//2-歸約@Testpublic void test2(){//reduce(T identity,BinaryOperator)-可以將流中的元素反復結合起來,得到一個值,返回T。//練習:計算1-10自然數的和List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);System.out.println(list.stream().reduce(0, (x1, x2) -> x1 + x2));//reduce(BinaryOperator)-可以將流中的元素反復結合起來,得到一個值。返回Optional<T>//練習:計算公司所有員工工資的總和List<Employee> list = EmployeeData.getEmployees();System.out.println(list.stream().map(emp -> emp.getSalary()).reduce(Double::sum));}//3-收集@Testpublic void test3(){//collect(Collector c)-將流轉換為其他形式。接收一個Collector接口的實現,用于給Stream中元素做匯總的方法//練習:查找工資大于6000的員工,結果返回為一個list或setList<Employee> list = EmployeeData.getEmployees();List<Boolean> list1 = list.stream().map(emp -> emp.getSalary() > 6000).collect(Collectors.toList());list1.forEach(System.out::println);}
2.JDK8之后的新特性
2.1 jShell命令
JDK9 的新特性。jShell。以交互式的方式對語句和表達
式進行求值。即寫即得、快速運行。
2.2 異常處理之try-catch資源關閉
2.2.1 JDK7的新特性
在 try 的后面可以增加一個(),在括號中可以聲明流對象并初化。try 中的代碼執行完畢,會自動把流對象釋放,就不用寫 finally了。
try(資源對象的聲明和初始化){業務邏輯代碼,可能會產生異常
}catch(異常類型 1 e){處理異常代碼
}catch(異常類型 2 e){處理異常代碼
}//JDK7@Testpublic void test1() {try (FileWriter fw = new FileWriter("hello.txt"); BufferedWriter bw = new BufferedWriter(fw);) {bw.write("hello");} catch (IOException e) {e.printStackTrace();}}
2.2.2 JDK9的新特性
try 的前面可以定義流對象,try 后面的()中可以直接引用流對象的名稱。在 try代碼執行完畢后,流對象也可以釋放掉,也不用寫 finally 了。
A a = new A();
B b = new B();
try(a;b){可能產生的異常代碼
}catch(異常類名 變量名){異常處理的邏輯
}//JDK9@Testpublic void test2() {InputStreamReader reader = new InputStreamReader(System.in);OutputStreamWriter writer = new OutputStreamWriter(System.out);try (reader; writer) {} catch (IOException e) {e.printStackTrace();}}
2.3 局部變量類型推斷
JDK10的新特性。局部變量的顯示類型聲明,常常被認為是不必須的,給一個好聽的名字反而可以很清楚的表達出下面應該怎樣繼續。本新特性允許開發人員省略通常不必要的局部變量類型聲明,以增強 Java 語言的體驗性、可讀性。
//1.局部變量的實例化
var list = new ArrayList<String>();
var set = new LinkedHashSet<Integer>();
//2.增強 for 循環中的索引
for (var v : list) {System.out.println(v);
}
//3.傳統 for 循環中
for (var i = 0; i < 100; i++) {System.out.println(i);
}
//4. 返回值類型含復雜泛型結構
var iterator = set.iterator();
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
不適用場景:
- 聲明一個成員變量:var i;
- 聲明一個數組變量,并為數組靜態初始化(省略 new 的情況下):var arr = {1,2,3};
- 方法的返回值類型
- 方法的參數類型
- 沒有初始化的方法內的局部變量聲明
- 作為 catch 塊中異常類型
- Lambda 表達式中函數式接口的類型
- 方法引用中函數式接口的類型
2.4 instanceof模式匹配
JDK14中的預覽特性,在JDK15中未進行修改,在JDK16中正式使用。instanceof 模式匹配通過提供更為簡便的語法,來提高生產力。有了該功能,可以減少 Java 程序中顯式強制轉換的數量,實現更精確、簡潔的類型安全的代碼。
@Testpublic void test1() {Object obj = new String("hello");if (obj instanceof String str) {System.out.println(str.contains("Java"));} else {System.out.println("error");}}
}class Computer {private String model;private double price;@Overridepublic boolean equals(Object obj) {return obj instanceof Computer other &&this.model.equals(other.model) && this.price == other.price;}
}
2.5 Switch表達式
2.5.1 JDK12-14中Switch表達式
- JDK12中:
- Java 12 將會對 switch 聲明語句進行擴展,使用 case L ->來替代以前的 break;,省去了 break 語句,避免了因少寫 break 而出錯。
- 同時將多個 case 合并到一行,顯得簡潔、清晰,也更加優雅的表達邏輯分支。
- 為了保持兼容性,case 條件語句中依然可以使用字符: ,但是同一個 switch 結構里不能混用-> 和: ,否則編譯錯誤。
public class SwitchTest2 {public static void main(String[] args) {Fruit fruit = Fruit.GRAPE;int numberOfLetters = switch(fruit){case PEAR -> 4;case APPLE,MANGO,GRAPE -> 5;case ORANGE,PAPAYA -> 6;default -> throw new IllegalStateException("No Such Fruit:
" + fruit);};System.out.println(numberOfLetters);}
}
- JDK13中:JDK13 中引入了 yield 語句,用于返回值。這意味著,switch 表達式(返回值)應該使用 yield,switch 語句(不返回值)應該使用 break。yield 和 return 的區別在于:return 會直接跳出當前循環或者方法,而 yield 只會跳出當前 switch 塊。
@Test
public void testSwitch3() {String x = "3";int i = switch (x) {case "1":yield 1;case "2":yield 2;default:yield 3;};System.out.println(i);
}
2.5.2 JDK17中Switch的模式匹配
static String formatter(Object o) {String formatted = switch (o) {case Integer i:yield "int" + i;case Long l:yield "long" + l;case Double d:yield "double" + d;default:yield o.toString();};return formatted;}
2.6 文本塊的使用
在 Java 中,通常需要使用 String 類型表達 HTML,XML,SQL 或 JSON 等格式的字符串,在進行字符串賦值時需要進行轉義和連接操作,然后才能編譯該代碼,這種表達方式難以閱讀并且難以維護。
2.6.1 JDK13的新特性
使用"""作為文本塊的開始符和結束符,在其中就可以放置多行的字符串,不需要進行任何轉義。因此,文本塊將提高 Java 程序的可讀性和可寫性。
String text2 = """The Sound of silenceHello darkness, my old friendI've come to talk with you againBecause a vision softly creepingLeft its seeds while I was sleepingAnd the vision that was planted in my brainStill remainsWithin the sound of silence""";
System.out.println(text2);
2.6.2 JDK14的新特性
JDK14 的版本主要增加了兩個 escape sequences,分別是
- \ :取消換行操作
- \s:表示一個空格
String sql2 = """SELECT id,NAME,email \FROM customers\s\WHERE id > 4 \ORDER BY email DESC""";System.out.println(sql2);
2.7 record
record 是一種全新的類型,它本質上是一個 final 類,同時所有的屬性都是final 修飾,它會自動編譯出 public get 、hashcode 、equals、toString、構造器等結構,減少了代碼編寫量。
public record Order1(int orderId,String orderName) {
}
用 record 聲明一個類時,該類將自動擁有以下功能:
- 獲取成員變量的簡單方法,比如例題中的 name() 和 partner() 。注意區別于我們平常 getter()的寫法。
- 一個 equals 方法的實現,執行比較時會比較該類的所有成員屬性。
- 重寫 hashCode() 方法。
- 一個可以打印該類所有成員屬性的 toString() 方法。
- 只有一個構造方法。
此外:
- 還可以在 record 聲明的類中定義靜態字段、靜態方法、構造器或實例方法。
- 不能在 record 聲明的類中定義實例字段;類不能聲明為 abstract;不能聲明顯式的父類等。
2.8 密封類
在 Java 中如果想讓一個類不能被繼承和修改,這時我們應該使用 final 關鍵字對類進行修飾。不過這種要么可以繼承,要么不能繼承的機制不夠靈活,有些時候我們可能想讓某個類可以被某些類型繼承,但是又不能隨意繼承,是做不到的。Java 15 嘗試解決這個問題,引入了 sealed 類,被 sealed 修飾的類可以指定子類。這樣這個類就只能被指定的類繼承。
具體使用:
- 使用修飾符 sealed,可以將一個類聲明為密封類。密封的類使用保留關鍵字permits 列出可以直接擴展(即 extends)它的類。
- sealed 修飾的類的機制具有傳遞性,它的子類必須使用指定的關鍵字進行修飾,且只能是 final、sealed、non-sealed 三者之一。
3.API的變化
3.1 Optional類
JDK8的新特性。
到目前為止,臭名昭著的空指針異常是導致 Java 應用程序失敗的最常見原因。以前,為了解決空指針異常,Google 在著名的 Guava 項目引入了 Optional類,通過檢查空值的方式避免空指針異常。受到 Google 的啟發,Optional 類已經成為 Java 8 類庫的一部分。
3.1.1 引入的原因
為了避免代碼中出現空指針異常。
3.1.2 介紹
Optional 類(java.util.Optional) 是一個容器類,它可以保存類型 T 的值,代表這個值存在。或者僅僅保存 null,表示這個值不存在。如果值存在,則isPresent()方法會返回 true,調用 get()方法會返回該對象。
3.1.3 實例化
- static Optional empty() :用來創建一個空的 Optional 實例。
- static Optional of(T value) :用來創建一個 Optional 實例,value 必須非空。
- static Optional ofNullable(T value) :用來創建一個Optional 實例,value 可能是空,也可能非空。
3.1.3 常用方法
- 判斷 Optional 容器中是否包含對象:
- boolean isPresent() : 判斷 Optional 容器中的值是否存在。
- void ifPresent(Consumer<? super T> consumer) :判斷 Optional 容器中的值是否存在,如果存在,就對它進行Consumer 指定的操作,如果不存在就不做。
- 獲取 Optional 容器的對象:
- T get(): 如果調用對象包含值,返回該值。否則拋異常。T get()與 of(T value)配合使用。
- T orElse(T other):orElse(T other) 與 ofNullable(T value)配合使用,如果Optional 容器中非空,就返回所包裝值,如果為空,就用 orElse(T other)other 指定的默認值(備胎)代替。
- T orElseGet(Supplier<? extends T> other) :如果 Optional 容器中非空,就返回所包裝值,如果為空,就用 Supplier 接口的 Lambda 表達式提供的值代替。
- T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果 Optional 容器中非空,就返回所包裝值,如果為空,就拋出你指定的異常類型代替原來的NoSuchElementException。
@Testpublic void test1(){String str = "hello";str = null;//使用Optional避免空指針異常Optional<String> optional = Optional.ofNullable(str);String other = "你好";String finalStr = optional.orElse(other);System.out.println(finalStr.toString());}