1 Lambda 表達式(Lambda Expressions)
Lambda 是一個匿名函數,我們可以把 Lambda 表達式了解為是一段可以傳遞的代碼(将代碼像資料一樣進行傳遞)。使用它可以寫出更簡潔、更靈活的代碼。作為一種更緊湊的代碼風格,使Java的語言表達能力得到了提升。
// 匿名内部類
Runnable r = new Runnable(){
@Override
public void run(){
System.out.println("Hello World!");
}
}
// Lambda表達式
Runnable r = () -> System.out.println("Hello World!");
看着是不是有點牛皮~?再來一個:
//原來使用匿名内部類作為參數傳遞
TreeSet<String> ts = new TreeSet<>(new Comparator<String>(){
@Override
public int compare(String o1,String o2){
return Integer.compare(o1.length(),o2.length());
}
});
// Lambda 表達式
TreeSet<String> ts = new TreeSet<>(
(o1,o2) -> Integer.compare(o1.length(),o2.length());
);
1.1 Lambda表達式文法
Lambda 表達式:在Java 8 語言中引入的一種新的文法元素和操作符。這個操作符為 “->” , 該操作符被稱為 Lambda 操作符或箭頭操作符。它将 Lambda 分為兩個部分:
左側:指定了 Lambda 表達式需要的參數清單
右側:指定了 Lambda 體,是抽象方法的實作邏輯,也即 Lambda 表達式要執行的功能。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TP35UNrpXTy0kaNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4EDO2MDNxEjMwEzMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程式依然可以編譯,這是因為 javac 根據程式的上下文,在背景推斷出了參數的類型。Lambda 表達式的類型依賴于上下文環境,是由編譯器推斷出來的。這就是所謂的“類型推斷”。
2 函數式(Functional)接口
隻包含一個抽象方法的接口,稱為函數式接口。
你可以通過 Lambda 表達式來建立該接口的對象。(若 Lambda 表達式抛出一個受檢異常(即:非運作時異常),那麼該異常需要在目标接口的抽象方法上進行聲明)。
我們可以在一個接口上使用 @FunctionalInterface 注解,這樣做可以檢查它是否是一個函數式接口。同時 javadoc 也會包含一條聲明,說明這個接口是一個函數式接口。
在java.util.function包下定義了java 8 的豐富的函數式接口。如下代碼便是Runnable接口的代碼,有@FunctionalInterface注解
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Java從誕生日起就是一直倡導“一切皆對象”,在java裡面面向對象(OOP)程式設計是一切。但是随着python、scala等語言的興起和新技術的挑戰,java不得不做出調整以便支援更加廣泛的技術要求,也即java不但可以支援OOP還可以支援OOF(面向函數程式設計)
在函數式程式設計語言當中,函數被當做一等公民對待。在将函數作為一等公民的程式設計語言中,Lambda表達式的類型是函數。但是在Java8中,有所不同。在Java8中,Lambda表達式是對象,而不是函數,它們必須依附于一類特别的對象類型——函數式接口。
簡單的說,在Java8中,Lambda表達式就是一個函數式接口的執行個體。這就是Lambda表達式和函數式接口的關系。也就是說,隻要一個對象是函數式接口的執行個體,那麼該對象就可以用Lambda表達式來表示。
是以以前用匿名内部類表示的現在都可以用Lambda表達式來寫。
@FunctionalInterface
public interface MyInterface1{
public int get();
}
@FunctionalInterface
public interface MyInterface2<T>{
public T get(T t);
}
Java 内置四大核心函數式接口
函數式接口 | 參數類型 | 傳回類型 | 用途 |
Consumer<T> 消費型接口 | T | void | 對類型為T的對象應用操作,包含方法: void accept(T t) |
Supplier<T> 供給型接口 | 無 | T | 傳回類型為T的對象,包含方法:T get() |
Function<T, R> 函數型接口 | T | R | 對類型為T的對象應用操作,并傳回結果。結果是R類型的對象。包含方法:R apply(T t) |
Predicate<T> 斷定型接口 | 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<T> (Function子接口) | T | T | 對類型為T的對象進行一進制運算,并傳回T類型的結果。包含方法為 T apply(T t); |
BinaryOperator<T> (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<T> ToLongFunction<T> ToDoubleFunction<T> | T | int long double | 分别計算int、long、double、值的函數 |
IntFunction<R> LongFunction<R> DoubleFunction<R> | int long double | R | 參數分别為int、long、double 類型的函數 |
3 方法引用與構造器引用
3.1 方法引用(Method References)
當要傳遞給Lambda體的操作,已經有實作的方法了,可以使用方法引用!
方法引用就是Lambda表達式,就是函數式接口的一個執行個體,通過方法的名字來指向一個方法,可以認為是Lambda表達式的一個文法糖。
要求:實作抽象方法的參數清單和傳回值類型,必須與方法引用的方法的參數清單和傳回值類型保持一緻!
方法引用:使用操作符 “::” 将類(或對象) 與 方法名分隔開來。
如下三種主要使用情況:
對象::執行個體方法名
類::靜态方法名
類::執行個體方法名
注意:當函數式接口方法的第一個參數是需要引用方法的調用者,并且第二個參數是需要引用方法的參數(或無參數)時:ClassName::methodName。
3.2 構造器引用
格式: ClassName::new
與函數式接口相結合,自動與函數式接口中方法相容。
可以把構造器引用指派給定義的方法,要求構造器參數清單要與接口中抽象方法的參數清單一緻!且方法的傳回值即為構造器對應類的對象。
3.3 數組引用
格式: type[] :: new
4 Stream API
Stream API ( java.util.stream) 把真正的函數式程式設計風格引入到Java中。這是目前為止對Java類庫最好的補充,因為Stream API可以極大提供Java程式員的生産力,讓程式員寫出高效率、幹淨、簡潔的代碼。
Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查找、過濾和映射資料等操作。 使用Stream API 對集合資料進行操作,就類似于使用 SQL 執行的資料庫查詢。也可以使用 Stream API 來并行執行操作。簡言之,Stream API 提供了一種高效且易于使用的處理資料的方式。
Stream是資料管道,用于操作資料源(集合、數組等)所生成的元素序列。“集合講的是資料,Stream講的是計算!”。其特點如下:
1)Stream 不是集合,自己不會存儲元素。
2)Stream 不會改變源對象。相反,他們會傳回一個持有結果的新Stream。
3)Stream 操作是延遲執行的。必須搞清楚有哪些資料才能往下執行,這意味着他們會等到需要結果的時候才執行。
4)Stream隻能“消費”一次,如果想繼續做其他操作,需要重新擷取stream對象
5)更像一個進階的iterator,單向,不可往複,資料隻能周遊一次,周遊過一次後即用盡了,但是可以并行化資料!
4.1 Stream 的操作三個步驟
1- 建立 Stream
一個資料源(如:集合、數組),擷取一個流
2- 中間操作
一個中間操作鍊,對資料源的資料進行處理
3- 終止操作(終端操作)
一旦執行終止操作,就執行中間操作鍊,并産生結果。之後,不會再被使用
4.2 建立Stream 的方式
1) 通過集合
Java8 中的 Collection 接口被擴充,提供了兩個擷取流的方法:
default Stream<E> stream() : 傳回一個順序流
default Stream<E> parallelStream() : 傳回一個并行流
2) 通過數組
Java8 中的 Arrays 的靜态方法 stream() 可以擷取數組流:
static <T> Stream<T> stream(T[] array): 傳回一個流
重載形式,能夠處理對應基本類型的數組:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
3)通過Stream的of()
可以調用Stream類靜态方法 of(), 通過顯示值建立一個流。它可以接收任意數量的參數。
public static<T> Stream<T> of(T... values) : 傳回一個流
4)建立無限流
可以使用靜态方法 Stream.iterate() 和 Stream.generate(), 建立無限流。
疊代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
生成
public static<T> Stream<T> generate(Supplier<T> s)
4.3 Stream 的中間操作
多個中間操作可以連接配接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱為“惰性求值”。
1-篩選與切片
方 法 | 描 述 |
filter(Predicate p) | 接收 Lambda , 從流中排除某些元素 |
distinct() | 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素 |
limit(long maxSize) | 截斷流,使其元素不超過給定數量 |
skip(long n) | 跳過元素,傳回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則傳回一個空流。與 limit(n) 互補 |
filter(Predicate p)——接收 Lambda , 從流中排除某些元素。
limit(n)——截斷流,使其元素不超過給定數量。
skip(n) —— 跳過元素,傳回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則傳回一個空流。與 limit(n) 互補
distinct()——篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素
2-映 射
方法 | 描述 |
map(Function f) | 接收一個函數作為參數,該函數會被應用到每個元素上,并将其映射成一個新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一個函數作為參數,該函數會被應用到每個元素上,産生一個新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一個函數作為參數,該函數會被應用到每個元素上,産生一個新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一個函數作為參數,該函數會被應用到每個元素上,産生一個新的 LongStream。 |
flatMap(Function f) | 接收一個函數作為參數,将流中的每個值都換成另一個流,然後把所有流連接配接成一個流 |
map(Function f)——接收一個函數作為參數,将元素轉換成其他形式或提取資訊,該函數會被應用到每個元素上,并将其映射成一個新的元素。
flatMap(Function f)——接收一個函數作為參數,将流中的每個值都換成另一個流,然後把所有流連接配接成一個流。
3-排序
方法 | 描述 |
sorted() | 産生一個新流,其中按自然順序排序 |
sorted(Comparator com) | 産生一個新流,其中按比較器順序排序 |
sorted()——自然排序
sorted(Comparator com)——定制排序
4.4 Stream 的終止操作
•終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
•流進行了終止操作後,不能再次使用。
1-比對與查找
方法 | 描述 |
allMatch(Predicate p) | 檢查是否比對所有元素 |
anyMatch(Predicate p) | 檢查是否至少比對一個元素 |
noneMatch(Predicate p) | 檢查是否沒有比對所有元素 |
findFirst() | 傳回第一個元素 |
findAny() | 傳回目前流中的任意元素 |
allMatch(Predicate p)——檢查是否比對所有元素
anyMatch(Predicate p)——檢查是否至少比對一個元素
noneMatch(Predicate p)——檢查是否沒有比對的元素
findFirst——傳回第一個元素
findAny——傳回目前流中的任意元素
count——傳回流中元素的總個數
max(Comparator c)——傳回流中最大值
min(Comparator c)——傳回流中最小值
forEach(Consumer c)——内部疊代
方法 | 描述 |
count() | 傳回流中元素總數 |
max(Comparator c) | 傳回流中最大值 |
min(Comparator c) | 傳回流中最小值 |
forEach(Consumer c) | 内部疊代(使用 Collection 接口需要使用者去做疊代,稱為外部疊代。相反,Stream API 使用内部疊代——它幫你把疊代做了) |
2-歸約
方法 | 描述 |
reduce(T iden, BinaryOperator b) | 可以将流中元素反複結合起來,得到一個值。傳回 T |
reduce(BinaryOperator b) | 可以将流中元素反複結合起來,得到一個值。傳回 Optional<T> |
reduce(T identity, BinaryOperator)——可以将流中元素反複結合起來,得到一個值。傳回 T
reduce(BinaryOperator) ——可以将流中元素反複結合起來,得到一個值。傳回 Optional<T>
3-收集
方 法 | 描 述 |
collect(Collector c) | 将流轉換為其他形式。接收一個 Collector接口的實作,用于給Stream中元素做彙總的方法 |
collect(Collector c)——将流轉換為其他形式。接收一個 Collector接口的實作,用于給Stream中元素做彙總的方法
方法 | 傳回類型 | 作用 |
toList | List<T> | 把流中元素收集到List |
List<Employee> emps= list.stream().collect(Collectors.toList()); | ||
toSet | Set<T> | 把流中元素收集到Set |
Set<Employee> emps= list.stream().collect(Collectors.toSet()); | ||
toCollection | Collection<T> | 把流中元素收集到建立的集合 |
Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new)); | ||
counting | Long | 計算流中元素的個數 |
long count = list.stream().collect(Collectors.counting()); | ||
summingInt | Integer | 對流中元素的整數屬性求和 |
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary)); | ||
averagingInt | Double | 計算流中元素Integer屬性的平均值 |
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary)); | ||
summarizingInt | IntSummaryStatistics | 收集流中Integer屬性的統計值。如:平均值 |
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); |
joining | String | 連接配接流中每個字元串 |
String str= list.stream().map(Employee::getName).collect(Collectors.joining()); | ||
maxBy | Optional<T> | 根據比較器選擇最大值 |
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); | ||
minBy | Optional<T> | 根據比較器選擇最小值 |
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); | ||
reducing | 歸約産生的類型 | 從一個作為累加器的初始值開始,利用BinaryOperator與流中元素逐個結合,進而歸約成單個值 |
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); | ||
collectingAndThen | 轉換函數傳回的類型 | 包裹另一個收集器,對其結果轉換函數 |
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); | ||
groupingBy | Map<K, List<T>> | 根據某屬性值對流分組,屬性為K,結果為V |
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus)); | ||
partitioningBy | Map<Boolean, List<T>> | 根據true或false進行分區 |
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
并行流與串行流
并行流就是把一個内容分成多個資料塊,并用不同的線程分别處理每個資料塊的流。
Java 8 中将并行進行了優化,我們可以很容易的對資料進行并行操作。Stream API 可以聲明性地通過 parallel() 與 sequential() 在并行流與順序流之間進行切換。
5 Optional 類
到目前為止,臭名昭著的空指針異常是導緻Java應用程式失敗的最常見原因。以前,為了解決空指針異常,Google公司著名的Guava項目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染,它鼓勵程式員寫更幹淨的代碼。受到Google Guava的啟發,Optional類已經成為Java 8類庫的一部分。
Optional實際上是個容器:它可以儲存類型T的值,或者僅僅儲存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。
Optional類的Javadoc描述如下:這是一個可以為null的容器對象。如果值存在則isPresent()方法會傳回true,調用get()方法會傳回該對象。
Optional<T> 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。并且可以避免空指針異常。
常用方法:
Optional.empty() : 建立一個空的 Optional 執行個體
Optional.of(T t) : 建立一個 Optional 執行個體
Optional.ofNullable(T t):若 t 不為 null,建立 Optional 執行個體,否則建立空執行個體
isPresent() : 判斷是否包含值
T get(): 如果調用對象包含值,傳回該值,否則抛異常
orElse(T t) : 如果調用對象包含值,傳回該值,否則傳回t
orElseGet(Supplier s) :如果調用對象包含值,傳回該值,否則傳回 s 擷取的值
map(Function f): 如果有值對其處理,并傳回處理後的Optional,否則傳回 Optional.empty()
flatMap(Function mapper):與 map 類似,要求傳回值必須是Optional
Optional<Employee> op = Optional.of(new Employee(101, "張三", 18, 9999.99));
Optional<String> op2 = op.map(Employee::getName);
System.out.println(op2.get());
Optional<String> op3 = op.flatMap((e) -> Optional.of(e.getName()));
System.out.println(op3.get());