本系列文章均爲尚硅谷資源!如有侵權,我將立即刪除!
Java基礎知識圖解
Java 8新特性簡介
Java 8 (又稱爲 jdk 1.8) 是 Java 語言開發的一個主要版本。Java 8 是oracle公司於2014年3月發佈,可以看成是自Java 5 以來最具革命性的版本。Java 8爲Java語言、編譯器、類庫、開發工具與JVM帶來了大量新特性。
- 速度更快
- 代碼更少(增加了新的語法:Lambda 表達式) 強大的 Stream API
- 便於並行
- 最大化減少空指針異常:Optional
- Nashorn引擎,允許在JVM上運行JS應用
並行流與串行流
並行流就是把一個內容分成多個數據塊,並用不同的線程分別處理每個數據塊的流。相比較串行的流,並行的流可以很大程度上提高程序的執行效率。
Java 8 中將並行進行了優化,我們可以很容易的對數據進行並行操作。Stream API 可以聲明性地通過 parallel() 與 sequential() 在並行流與順序流之間進行切換。
1.Lambda表達式
爲什麼使用 Lambda 表達式
Lambda 是一個匿名函數,我們可以把 Lambda 表達式理解爲是一段可以傳遞的代碼(將代碼像數據一樣進行傳遞)。使用它可以寫出更簡潔、更靈活的代碼。作爲一種更緊湊的代碼風格,使Java的語言表達能力得到了提升。
從匿名類到 Lambda 的轉換舉例1
從匿名類到 Lambda 的轉換舉例2
Lambda 表達式:在Java 8 語言中引入的一種新的語法元素和操作符。這個操作符爲 “->” , 該操作符被稱爲 Lambda 操作符或箭頭操作符。它將 Lambda 分爲兩個部分:
左側:指定了 Lambda 表達式需要的參數列表
右側:指定了 Lambda 體,是抽象方法的實現邏輯,也即Lambda 表達式要執行的功能。
語法
類型推斷
上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然可以編譯,這是因爲 javac 根據程序的上下文,在後臺推斷出了參數的類型。Lambda 表達式的類型依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的“類型推斷”。
2.函數式(Functional)接口
什麼是函數式(Functional)接口
- 只包含一個抽象方法的接口,稱爲函數式接口。
- 你可以通過 Lambda 表達式來創建該接口的對象。(若 Lambda 表達式拋出一個受檢異常(即:非運行時異常),那麼該異常需要在目標接口的抽象方法上進行聲明)。
- 我們可以在一個接口上使用 @FunctionalInterface 註解,這樣做可以檢查它是否是一個函數式接口。同時 javadoc 也會包含一條聲明,說明這個接口是一個函數式接口。
- 在java.util.function包下定義了Java 8 的豐富的函數式接口
如何理解函數式接口
- Java從誕生日起就是一直倡導“一切皆對象”,在Java裏面面向對象**(OOP)編程是一切。但是隨着python、scala等語言的興起和新技術的挑戰,Java不得不做出調整以便支持更加廣泛的技術要求,也即java不但可以支持OOP還可以支持OOF(面向函數編程)**
- 在函數式編程語言當中,函數被當做一等公民對待。在將函數作爲一等公民的編程語言中,Lambda表達式的類型是函數。但是在Java8中,有所不同。在Java8中,Lambda表達式是對象,而不是函數,它們必須依附於一類特別的對象類型——函數式接口。
- 簡單的說,在Java8中,Lambda表達式就是一個函數式接口的實例。這就是Lambda表達式和函數式接口的關係。也就是說,只要一個對象是函數式接口的實例,那麼該對象就可以用Lambda表達式來表示。
- 所以以前用匿名實現類表示的現在都可以用Lambda表達式來寫。
函數式接口舉例
自定義函數式接口
作爲參數傳遞 Lambda 表達式
作爲參數傳遞 Lambda 表達式:爲了將 Lambda 表達式作爲參數傳遞,接收Lambda 表達式的參數類型必須是與該 Lambda 表達式兼容的函數式接口的類型。
Java 內置四大核心函數式接口
函數式接口 | 參數類型 | 返回類型 | 用途 |
---|---|---|---|
Consumer消費型接口 | T | void | 對類型爲T的對象應用操作,包含方法:void accept(T t) |
Supplier供給型接口 | 無 | T | 返回類型爲T的對象,包含方法:T get() |
Function<T, R>函數型接口 | T | R | 對類型爲T的對象應用操作,並返回結果。結果是R類型的對象。包含方法:R apply(T t) |
Predicate斷定型接口 | T | boolean | 確定類型爲T的對象是否滿足某約束,並返回boolean 值。包含方法:boolean test(T t) |
其他接口
函數式接口 | 參數類型 | 返回類型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 對類型爲 T, U 參數應用操作,返回 R 類型的結果。包含方法爲: R apply(T t, U u); |
UnaryOperator(Function子接口) | T | T | 對類型爲T的對象進行一元運算,並返回T類型的結果。包含方法爲:T apply(T t); |
BinaryOperator(BiFunction 子接口) | T, T | T | 對類型爲T的對象進行二元運算,並返回T類型的結果。包含方法爲: T apply(T t1, T t2); |
BiConsumer<T, U> | T, U | void | 對類型爲T, U 參數應用操作。包含方法爲: void accept(T t, U u) |
BiPredicate<T,U> | T,U | boolean | 包含方法爲: boolean test(T t,U u) |
ToIntFunction ToLongFunction ToDoubleFunction | T | int long double | 分別計算int、long、double值的函數 |
IntFunction LongFunction DoubleFunction | int long double | R | 參數分別爲int、long、double 類型的函數 |
3.方法引用與構造器引用
方法引用(Method References)
- 當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用!
- 方法引用可以看做是Lambda表達式深層次的表達。換句話說,方法引用就是Lambda表達式,也就是函數式接口的一個實例,通過方法的名字來指向一個方法,可以認爲是Lambda表達式的一個語法糖。
- 要求:實現接口的抽象方法的參數列表和返回值類型,必須與方法引用的方法的參數列表和返回值類型保持一致!
- 格式:使用操作符 “::” 將類(或對象) 與 方法名分隔開來。
- 如下三種主要使用情況:
對象::實例方法名
類::靜態方法名
類::實例方法名
方法引用
注意:當函數式接口方法的第一個參數是需要引用方法的調用者,並且第二個參數是需要引用方法的參數(或無參數)時:ClassName::methodName
構造器引用
格式: ClassName::new
與函數式接口相結合,自動與函數式接口中方法兼容。
可以把構造器引用賦值給定義的方法,要求構造器參數列表要與接口中抽象方法的參數列表一致!且方法的返回值即爲構造器對應類的對象。
數組引用
格式: type[] :: new
4.強大的Stream API
Stream API說明
- Java8中有兩大最爲重要的改變。第一個是 Lambda 表達式;另外一個則 是 Stream API。
- Stream API ( java.util.stream) 把真正的函數式編程風格引入到Java中。這 是目前爲止對Java類庫最好的補充,因爲Stream API可以極大提供Java程 序員的生產力,讓程序員寫出高效率、乾淨、簡潔的代碼。
- Stream 是 Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進 行的操作,可以執行非常複雜的查找、過濾和映射數據等操作。 使用 Stream API 對集合數據進行操作,就類似於使用 SQL 執行的數據庫查詢。 也可以使用 Stream API 來並行執行操作。簡言之,Stream API 提供了一種 高效且易於使用的處理數據的方式。
爲什麼要使用Stream API
- 實際開發中,項目中多數數據源都來自於Mysql,Oracle等。但現在數據源可以更多了,有MongDB,Radis等,而這些NoSQL的數據就需要Java層面去處理。
- Stream 和 Collection 集合的區別:Collection 是一種靜態的內存數據結構,而 Stream 是有關計算的。前者是主要面向內存,存儲在內存中,後者主要是面向 CPU,通過 CPU 實現計算。
什麼是 Stream
Stream到底是什麼呢?
是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。
“集合講的是數據,Stream講的是計算!”
注意:
①Stream 自己不會存儲元素。
②Stream 不會改變源對象。相反,他們會返回一個持有結果的新Stream。
③Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行。
Stream 的操作三個步驟
1- 創建 Stream
一個數據源(如:集合、數組),獲取一個流
2- 中間操作
一箇中間操作鏈,對數據源的數據進行處理
3- 終止操作(終端操作)
旦執行終止操作,就執行中間操作鏈,併產生結果。之後,不會再被使用
創建 Stream方式一:通過集合
Java8 中的 Collection 接口被擴展,提供了兩個獲取流的方法:
- default Stream stream() : 返回一個順序流
- default Stream parallelStream() : 返回一個並行流
創建 Stream方式二:通過數組
Java8 中的 Arrays 的靜態方法 stream() 可以獲取數組流:
- static Stream stream(T[] array): 返回一個流
重載形式,能夠處理對應基本類型的數組:
- public static IntStream stream(int[] array)
- public static LongStream stream(long[] array)
- public static DoubleStream stream(double[] array)
創建 Stream方式三:通過Stream的of()
可以調用Stream類靜態方法 of(), 通過顯示值創建一個流。它可以接收任意數量的參數。
- public static Stream of(T… values) : 返回一個流
創建 Stream方式四:創建無限流
可以使用靜態方法 Stream.iterate() 和 Stream.generate(), 創建無限流。
- 迭代
public static Stream iterate(final T seed, final UnaryOperator f) - 生成
public static Stream generate(Supplier s)
Stream 的中間操作
多個中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱爲“惰性求值”。
1-篩選與切片
方 法 | 描 述 |
---|---|
filter(Predicate p) | 接收 Lambda , 從流中排除某些元素 |
distinct() | 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素 |
limit(long maxSize) | 截斷流,使其元素不超過給定數量 |
skip(long n) | 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補 |
2-映 射
方法 | 描述 |
---|---|
map(Function f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的 LongStream。 |
flatMap(Function f) | 接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有流連接成一個流 |
3-排序
方法 | 描述 |
---|---|
sorted() | 產生一個新流,其中按自然順序排序 |
sorted(Comparator com) | 產生一個新流,其中按比較器順序排序 |
Stream 的終止操作
終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
流進行了終止操作後,不能再次使用。
1-匹配與查找
方法 | 描述 |
---|---|
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count() | 返回流中元素總數 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 內部迭代(使用 Collection 接口需要用戶去做迭代,稱爲外部迭代。相反,Stream API 使用內部迭代——它幫你把迭代做了) |
2-歸約
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以將流中元素反覆結合起來,得到一個值。返回 T |
reduce(BinaryOperator b) | 可以將流中元素反覆結合起來,得到一個值。返回 Optional |
備註:map 和 reduce 的連接通常稱爲 map-reduce 模式,因 Google 用它來進行網絡搜索而出名。
3-收集
方 法 | 描 述 |
---|---|
collect(Collector c) | 將流轉換爲其他形式。接收一個 Collector接口的實現,用於給Stream中元素做彙總的方法 |
Collector 接口中方法的實現決定了如何對流執行收集的操作(如收集到 List、Set、Map)。
另外, Collectors 實用類提供了很多靜態方法,可以方便地創建常見收集器實例,具體方法與實例如下表:
5.Optional類
- 到目前爲止,臭名昭著的空指針異常是導致Java應用程序失敗的最常見原因。以前,爲了解決空指針異常,Google公司著名的Guava項目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染,它鼓勵程序員寫更乾淨的代碼。受到Google Guava的啓發,Optional類已經成爲Java 8類庫的一部分。
- Optional 類(java.util.Optional) 是一個容器類,它可以保存類型T的值,代表這個值存在。或者僅僅保存null,表示這個值不存在。原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。並且可以避免空指針異常。
- Optional類的Javadoc描述如下:這是一個可以爲null的容器對象。如果值存在則isPresent()方法會返回true,調用get()方法會返回該對象。
- Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。
- 創建Optional類對象的方法:
Optional.of(T t) : 創建一個 Optional 實例,t必須非空;
Optional.empty() : 創建一個空的 Optional 實例
Optional.ofNullable(T t):t可以爲null - 判斷Optional容器中是否包含對象:
boolean isPresent() : 判斷是否包含對象
void ifPresent(Consumer<? super T> consumer) :如果有值,就執行Consumer接口的實現代碼,並且該值會作爲參數傳給它。 - 獲取Optional容器的對象:
T get(): 如果調用對象包含值,返回該值,否則拋異常
T orElse(T other) :如果有值則將其返回,否則返回指定的other對象。
T orElseGet(Supplier<? extends T> other) :如果有值則將其返回,否則返回由Supplier接口實現提供的對象。
T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值則將其返回,否則拋出由Supplier接口實現提供的異常。
@Test
public void test1() {
Boy b = new Boy("張三");
Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend());
// 如果女朋友存在就打印女朋友的信息
opt.ifPresent(System.out::println);
}
@Test
public void test2() {
Boy b = new Boy("張三");
Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend());
// 如果有女朋友就返回他的女朋友,否則只能欣賞“嫦娥”了
Girl girl = opt.orElse(new Girl("嫦娥"));
System.out.println("他的女朋友是:" + girl.getName());
}
@Test
public void test3(){
Optional<Employee> opt = Optional.of(new Employee("張三", 8888));
//判斷opt中員工對象是否滿足條件,如果滿足就保留,否則返回空
Optional<Employee> emp = opt.filter(e -> e.getSalary()>10000);
System.out.println(emp);
}
@Test
public void test4(){
Optional<Employee> opt = Optional.of(new Employee("張三", 8888));
//如果opt中員工對象不爲空,就漲薪10%
Optional<Employee> emp = opt.map(e ->
{e.setSalary(e.getSalary()%1.1);return e;});
System.out.println(emp);
}