文章目錄
- 5,Optional
- 5.1,概述
- 5.2,使用
- 5.2.1,創建對象
- 5.2.2,安全消費值
- 5.2.3,獲取值
- 5.2.4,安全獲取值
- 5.2.5,過濾
- 5.2.6,判斷
- 5.2.7,數據轉換
- 6,方法引用
- 6.1 推薦用法
- 6.2 基本格式
- 6.3 語法詳解(了解)
- 6.3.1 引用類的靜態方法
- 6.3.2 引用對象的實例方法
- 6.3.3 引用類的實例方法
- 6.3.4 構造器引用
- 7,高級用法
- 7.1,基本數據類型優化
- 7.2,并行流
5,Optional
5.1,概述
? 我們在編寫代碼的時候出現最多的就是空指針異常。所以在很多情況下我們需要做各種非空的判斷。
? 例如:
Author author = getAuthor();if(author!=null){System.out.println(author.getName());}
? 尤其是對象中的屬性還是一個對象的情況下。這種判斷會更多。
? 而過多的判斷語句會讓我們的代碼顯得臃腫不堪。
? 所以在JDK8中引入了Optional,養成使用Optional的習慣后你可以寫出更優雅的代碼來避免空指針異常。
? 并且在很多函數式編程相關的API中也都用到了Optional,如果不會使用Optional也會對函數式編程的學習造成影響。
5.2,使用
5.2.1,創建對象
? Optional就好像是包裝類,可以把我們的具體數據封裝Optional對象內部。然后我們去使用Optional中封裝好的方法操作封裝進去的數據就可以非常優雅的避免空指針異常。
? 我們一般使用Optional的靜態方法ofNullable來把數據封裝成一個Optional對象。無論傳入的參數是否為null都不會出現問題。
Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);
? 你可能會覺得還要加一行代碼來封裝數據比較麻煩。但是如果改造下getAuthor方法,讓其的返回值就是封裝好的Optional的話,我們在使用時就會方便很多。
? 而且在實際開發中我們的數據很多是從數據庫獲取的。Mybatis從3.5版本可以也已經支持Optional了。我們可以直接把dao方法的返回值類型定義成Optional類型,MyBastis會自己把數據封裝成Optional對象返回。封裝的過程也不需要我們自己操作。
? 如果你確定一個對象不是空的則可以使用Optional的靜態方法of來把數據封裝成Optional對象。
Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);
? 但是一定要注意,如果使用of的時候傳入的參數必須不為null。(嘗試下傳入null會出現什么結果)
? 如果一個方法的返回值類型是Optional類型。而如果我們經判斷發現某次計算得到的返回值為null,這個時候就需要把null封裝成Optional對象返回。這時則可以使用Optional的靜態方法empty來進行封裝。
Optional.empty()
?
? 所以最后你覺得哪種方式會更方便呢?ofNullable
5.2.2,安全消費值
? 我們獲取到一個Optional對象后肯定需要對其中的數據進行使用。這時候我們可以使用其ifPresent方法對來消費其中的值。
? 這個方法會判斷其內封裝的數據是否為空,不為空時才會執行具體的消費代碼。這樣使用起來就更加安全了。
? 例如,以下寫法就優雅的避免了空指針異常。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());authorOptional.ifPresent(author -> System.out.println(author.getName()));
5.2.3,獲取值
? 如果我們想獲取值自己進行處理可以使用get()方法獲取,但是不推薦。因為當Optional內部的數據為空的時候會出現異常。
5.2.4,安全獲取值
? 如果我們期望安全的獲取值。我們不推薦使用get方法,而是使用Optional提供的以下方法。
-
orElseGet
獲取數據并且設置數據為空時的默認值。如果數據不為空就能獲取到該數據。如果為空則根據你傳入的參數來創建對象作為默認值返回。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor()); Author author1 = authorOptional.orElseGet(() -> new Author());
-
orElseThrow
獲取數據,如果數據不為空就能獲取到該數據。如果為空則根據你傳入的參數來創建異常拋出。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor()); try {Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("author為空"));System.out.println(author.getName()); } catch (Throwable throwable) {throwable.printStackTrace(); }
5.2.5,過濾
? 我們可以使用filter方法對數據進行過濾。如果原本是有數據的,但是不符合判斷,也會變成一個無數據的Optional對象。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());authorOptional.filter(author -> author.getAge()>100).ifPresent(author -> System.out.println(author.getName()));
5.2.6,判斷
? 我們可以使用isPresent方法進行是否存在數據的判斷。如果為空返回值為false,如果不為空,返回值為true。但是這種方式并不能體現Optional的好處,更推薦使用ifPresent方法。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());if (authorOptional.isPresent()) {System.out.println(authorOptional.get().getName());
}
5.2.7,數據轉換
? Optional還提供了map可以讓我們的對數據進行轉換,并且轉換得到的數據也還是被Optional包裝好的,保證了我們的使用安全。
例如我們想獲取作家的書籍集合。
private static void testMap() {Optional<Author> authorOptional = getAuthorOptional();Optional<List<Book>> optionalBooks = authorOptional.map(author -> author.getBooks());optionalBooks.ifPresent(books -> System.out.println(books));}
6,方法引用
6.1 推薦用法
? 我們在使用lambda時不需要考慮什么時候用方法引用,用哪種方法引用,方法引用的格式是什么。我們只需要在寫完lambda方法發現方法體只有一行代碼,并且是方法的調用時使用快捷鍵嘗試是否能夠轉換成方法引用即可。
6.2 基本格式
類名或者對象名::方法名
6.3 語法詳解(了解)
6.3.1 引用類的靜態方法
其實就是引用類的靜態方法
格式
類名::方法名
使用前提
? 如果我們在重寫方法的時候,方法體中只有一行代碼,并且這行代碼是調用了某個類的靜態方法,并且我們把要重寫的抽象方法中所有的參數都按照順序傳入了這個靜態方法中,這個時候我們就可以引用類的靜態方法。
例如:
如下代碼就可以用方法引用進行簡化
List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.map(author -> author.getAge()).map(age->String.valueOf(age));
注意,如果我們所重寫的方法是沒有參數的,調用的方法也是沒有參數的也相當于符合以上規則。
優化后如下:
List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.map(author -> author.getAge()).map(String::valueOf);
6.3.2 引用對象的實例方法
格式
對象名::方法名
使用前提
? 如果我們在重寫方法的時候,方法體中只有一行代碼,并且這行代碼是調用了某個對象的成員方法,并且我們把要重寫的抽象方法中所有的參數都按照順序傳入了這個成員方法中,這個時候我們就可以引用對象的實例方法
例如:
傳進去一個參數name,并且按順序傳入了append的sb的成員方法中。
List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(name->sb.append(name));
優化后:
List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(sb::append);
6.3.3 引用類的實例方法
格式
類名::方法名
使用前提
? 如果我們在重寫方法的時候,方法體中只有一行代碼,并且這行代碼是調用了第一個參數的成員方法,并且我們把要重寫的抽象方法中剩余的所有的參數都按照順序傳入了這個成員方法中,這個時候我們就可以引用類的實例方法。
例如:
調用了第一個參數str,且重寫的抽象方法use剩余參數start和length按照順序依次傳入成員方法substring()中。
interface UseString{String use(String str,int start,int length);
}public static String subAuthorName(String str, UseString useString){int start = 0;int length = 1;return useString.use(str,start,length);
}
public static void main(String[] args) {subAuthorName("三更草堂", new UseString() {@Overridepublic String use(String str, int start, int length) {return str.substring(start,length);}});}
優化后如下:
public static void main(String[] args) {subAuthorName("三更草堂", String::substring);}
6.3.4 構造器引用
如果方法體中的一行代碼是構造器的話就可以使用構造器引用。
格式
類名::new
使用前提
? 如果我們在重寫方法的時候,方法體中只有一行代碼,并且這行代碼是調用了某個類的構造方法,并且我們把要重寫的抽象方法中的所有的參數都按照順序傳入了這個構造方法中,這個時候我們就可以引用構造器。
例如:
List<Author> authors = getAuthors();
authors.stream().map(author -> author.getName()).map(name->new StringBuilder(name)).map(sb->sb.append("-三更").toString()).forEach(str-> System.out.println(str));
優化后:
List<Author> authors = getAuthors();
authors.stream().map(author -> author.getName()).map(StringBuilder::new).map(sb->sb.append("-三更").toString()).forEach(str-> System.out.println(str));
7,高級用法
7.1,基本數據類型優化
? 我們之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的參數和返回值都是引用數據類型。
? 即使我們操作的是整數小數,但是實際用的都是他們的包裝類。JDK5中引入的自動裝箱和自動拆箱讓我們在使用對應的包裝類時就好像使用基本數據類型一樣方便。但是你一定要知道裝箱和拆箱肯定是要消耗時間的。雖然這個時間消耗很下。但是在大量的數據不斷的重復裝箱拆箱的時候,你就不能無視這個時間損耗了。
? 所以為了讓我們能夠對這部分的時間消耗進行優化。Stream還提供了很多專門針對基本數據類型的方法。
? 例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。
List<Author> authors = getAuthors();
authors.stream().map(author -> author.getAge()).map(age -> age + 10).filter(age->age>18).map(age->age+2).forEach(System.out::println);authors.stream().mapToInt(author -> author.getAge()).map(age -> age + 10).filter(age->age>18).map(age->age+2).forEach(System.out::println);
優化后:
使用.mapToInt(author -> author.getAge())后,流當中的元素都是int類型了,節省了時間。
authors.stream().mapToInt(author -> author.getAge()).map(age -> age + 10).filter(age->age>18).map(age->age+2).forEach(System.out::println);
7.2,并行流
當流中有大量元素時,我們可以使用并行流去提高操作的效率。其實并行流就是把任務分配給多個線程去完全。如果我們自己去用代碼實現的話其實會非常的復雜,并且要求你對并發編程有足夠的理解和認識。而如果我們使用Stream的話,我們只需要修改一個方法的調用就可以使用并行流來幫我們實現,從而提高效率。
parallel()
將順序流轉換為并行流,使得后續的操作(如peek
、filter
、reduce
)可能在多個線程中并行執行。
private static void test() {Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);Integer sum = stream.parallel().peek(new Consumer<Integer>() {@Overridepublic void accept(Integer num) {System.out.println(num+Thread.currentThread().getName());}}).filter(num -> num > 5).reduce((result, ele) -> result + ele).get();System.out.println(sum);
}
執行結果:
7main
2ForkJoinPool.commonPool-worker-11
8ForkJoinPool.commonPool-worker-4
9ForkJoinPool.commonPool-worker-18
6main
1ForkJoinPool.commonPool-worker-11
5ForkJoinPool.commonPool-worker-29
4ForkJoinPool.commonPool-worker-4
10ForkJoinPool.commonPool-worker-18
3ForkJoinPool.commonPool-worker-25
40
為什么會打印多次num+mian?
主線程也被分配到任務,main
線程參與了并行流的處理,主線程處理了元素 7 和 6,說明這兩個元素被分配給了主線程執行。
并行流執行流程示例:
- 主線程將 10 個元素分成 4 個子任務(例如每組 2~3 個元素)。
- 工作線程
worker-1
到worker-4
各領取一個子任務。 - 主線程發現還有剩余元素(如最后 2 個),直接處理它們。
- 所有線程并行執行
peek
、filter
和reduce
操作。
也可以通過parallelStream直接獲取并行流對象。
parallelStream()
是集合(如 List
、Set
等)提供的一個方法,用于創建并行流。它與 stream().parallel()
的效果相同,但寫法更簡潔。并行流的核心特點是將流的處理任務分解到多個線程上并行執行,從而提高處理大量數據時的性能。
List<Author> authors = getAuthors();
authors.parallelStream().map(author -> author.getAge()).map(age -> age + 10).filter(age->age>18).map(age->age+2).forEach(System.out::println);