這是我參與8月更文挑戰的第9天,活動詳情查看:8月更文挑戰java
匿名類內部類雖然沒有類名,但仍是要給出方法定義git
Lambda 表達式的初衷是進一步簡化匿名類的語法數組
在實現上,Lambda 表達式並非匿名類的語法糖安全
Lambda 和匿名類的示例markdown
public class ThreadCreator {
public static Thread createThreadByAnonymous() {
return new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous thread print");
}
});
}
public static Thread createThreadByLambda() {
return new Thread(() -> {
System.out.println("Lambda thread print");
});
}
}
複製代碼
Lambda 表達式如何匹配類型接口?=> 函數式接口數據結構
函數式接口是一種只有單一抽象方法的接口,使用 @FunctionalInterface 來描述,能夠隱式地轉換成 Lambda 表達式app
使用 Lambda 表達式建立函數式接口的示例, 可讓函數成爲程序的一等公民,從而像普通數據同樣看成參數傳遞dom
JDK 的 java.util.function 中提供了許多原生的函數式接口,如 Supplieride
@FunctionalInterface
public interface Supplier<T> {
/** * Gets a result. * * @return a result */
T get();
}
複製代碼
使用 Lamda 或方法引用來獲得函數式接口的實例函數
// 使用 Lambda 表達式提供 Supplier 接口實現,返回 OK 字符串
Supplier<String> stringSupplier = () ->"OK";
// 使用方法引用提供 Supplier 接口實現,返回空字符串
Supplier<String> supplier = String::new;
複製代碼
方法引用是 Lambda 表達式的另外一種表現形式
Lambda 表達式可用方法引用代替的場景:Lambda 表達式的主體僅包含一個表達式,且該表達式僅調用了一個已經存在的方法
方法引用能夠是:
- 類 : : 靜態方法
- 類 : : new
- 類 : : 實例方法((A, B) -> A.實例方法(B) <=> A 的類 : : 實例方法)
- 任意對象 : : 實例方法
利用 stream 方法將 list 或數組轉換爲流
Arrays.asList("a1", "a2", "a3").stream().forEach(System.out::println);
Arrays.stream(new int[]{1, 2, 3}).forEach(System.out::println);
複製代碼
非基本數據類型的數值也可使用 Arrays.asList 方法轉 list 再調 stream
經過 Stream.of 方法直接傳入多個元素構成一個流
String[] arr = {"a", "b", "c"};
Stream.of(arr).forEach(System.out::println);
Stream.of("a", "b", "c").forEach(System.out::println);
Stream.of(1, 2, "a").map(item -> item.getClass().getName())
.forEach(System.out::println);
複製代碼
經過 Stream.iterate 方法使用迭代的方式構造一個無限流,而後使用 limit 限制流元素個數
Stream.iterate(2, item -> item * 2).limit(10).forEach(System.out::println);
Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.TEN))
.limit(10).forEach(System.out::println);
複製代碼
經過 Stream.generate 方法從外部傳入一個提供元素的 Supplier 來構造無限流,再使用 limit 限制流元素個數
Stream.generate(() -> "test").limit(3).forEach(System.out::println);
Stream.generate(Math::random).limit(10).forEach(System.out::println);
複製代碼
經過 IntStream 或 DoubleStream 構造基本類型的流
// IntStream 和 DoubleStream
IntStream.range(1, 3).forEach(System.out::println);
IntStream.range(0, 3).mapToObj(i -> "x").forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);
DoubleStream.of(1.1, 2.2, 3.3).forEach(System.out::println);
// 使用 Random 類建立隨機流
new Random()
.ints(1, 100) // IntStream
.limit(10)
.forEach(System.out::println);
// 注意基本類型流和裝箱後的流的區別
Arrays.asList("a", "b", "c").stream() // Stream<String>
.mapToInt(String::length) // IntStream
.asLongStream() // LongStream
.mapToDouble(x -> x / 10.0) // DoubleStream
.boxed() // Stream<Double>
.mapToLong(x -> 1L) // LongStream
.mapToObj(x -> "") // Stream<String>
.collect(Collectors.toList());
複製代碼
Stream 經常使用 API:
如下爲測試用實體類,代碼省略了 getter、settter 和構造方法
訂單項
public class OrderItem {
private Long productId;// 商品ID
private String productName;// 商品名稱
private Double productPrice;// 商品價格
private Integer productQuantity;// 商品數量
}
複製代碼
訂單
public class Order {
private Long id;
private Long customerId;// 顧客ID
private String customerName;// 顧客姓名
private List<OrderItem> orderItemList;// 訂單商品明細
private Double totalPrice;// 總價格
private LocalDateTime placedAt;// 下單時間
}
複製代碼
消費者
public class Customer {
private Long id;
private String name;// 顧客姓名
}
複製代碼
filter 操做用做過濾,相似於 SQL 中的 where 條件,接收一個 Predicate 謂詞對象做爲參數,返回過濾後的流
能夠連續使用 filter 進行多層過濾
// 查找最近半年的金額大於40的訂單
orders.stream()
.filter(Objects::nonNull) // 過濾null值
.filter(order -> order.getPlacedAt()
.isAfter(LocalDateTime.now().minusMonths(6))) // 最近半年的訂單
.filter(order -> order.getTotalPrice() > 40) // 金額大於40的訂單
.forEach(System.out::println);
複製代碼
map 操做用作轉換,也叫投影,相似於 SQL 中的 select
// 計算全部訂單商品數量
// 1. 經過兩次遍歷實現
LongAdder longAdder = new LongAdder();
orders.stream().forEach(order ->
order.getOrderItemList().forEach(orderItem -> longAdder.add(orderItem.getProductQuantity())));
System.out.println("longAdder = " + longAdder);
// 2. 使用兩次 mapToLong 和 sum 方法實現
long sum = orders.stream().mapToLong(
order -> order.getOrderItemList().stream()
.mapToLong(OrderItem::getProductQuantity)
.sum()
).sum();
複製代碼
flatMap 是扁平化操做,即先用 map 把每一個元素替換爲一個流,再展開這個流
// 統計全部訂單的總價格
// 1. 直接展開訂單商品進行價格統計
double sum1 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.mapToDouble(item -> item.getProductQuantity() * item.getProductPrice())
.sum();
// 2. 另外一種方式 flatMapToDouble,即 flatMap + mapToDouble,返回 DoubleStream
double sum2 = orders.stream()
.flatMapToDouble(order ->
order.getOrderItemList()
.stream().mapToDouble(item ->
item.getProductQuantity() * item.getProductPrice())
)
.sum();
複製代碼
sorted 是排序操做,相似 SQL 中的 order by 子句,接收一個 Comparator 做爲參數,可以使用 Comparator.comparing 來由大到小排列,加上 reversed 表示倒敘
// 大於 50 的訂單,按照訂單價格倒序前 5
orders.stream()
.filter(order -> order.getTotalPrice() > 50)
.sorted(Comparator.comparing(Order::getTotalPrice).reversed())
.limit(5)
.forEach(System.out::println);
複製代碼
skip 用於跳過流中的項,limit 用於限制項的個數
// 按照下單時間排序,查詢前 2 個訂單的顧客姓名
orders.stream()
.sorted(Comparator.comparing(Order::getPlacedAt))
.map(order -> order.getCustomerName())
.limit(2)
.forEach(System.out::println);
// 按照下單時間排序,查詢第 3 和第 4 個訂單的顧客姓名
orders.stream()
.sorted(Comparator.comparing(Order::getPlacedAt))
.map(order -> order.getCustomerName())
.skip(2).limit(2)
.forEach(System.out::println);
複製代碼
distinct 操做的做用是去重,相似 SQL 中的 distinct
// 去重的下單用戶
orders.stream()
.map(Order::getCustomerName)
.distinct()
.forEach(System.out::println);
// 全部購買過的商品
orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.map(OrderItem::getProductName)
.distinct()
.forEach(System.out::println);
複製代碼
上文中已經屢次使用,內部循環流中的全部元素,對每個元素進行消費
forEachOrder 和 forEach 相似,但能保證消費順序
返回流中項的個數
轉換流爲數組
短路操做,有一項匹配就返回 true
// 查詢是否存在總價在 100 元以上的訂單
boolean b = orders.stream()
.filter(order -> order.getTotalPrice() > 50)
.anyMatch(order -> order.getTotalPrice() > 100);
複製代碼
其餘短路操做:
allMatch:所有匹配才返回 true
noneMatch:都不匹配才返回 true
findFirst:返回第一項的 Optional 包裝
findAny:返回任意一項的 Optional 包裝,串行流通常返回第一個
概括,一邊遍歷,一邊將處理結果保持起來,代入下一次循環
重載方法有三個,截圖來自 IDEA 參數提示:
// 一個參數
// 求訂單金額總價
Optional<Double> reduce = orders.stream()
.map(Order::getTotalPrice)
.reduce((p, n) -> {
return p + n;
});
// 兩個參數
// 可指定一個初始值,初始值類型須要和 p、n 一致
Double reduce2 = orders.stream()
.map(Order::getTotalPrice)
.reduce(0.0, (p, n) -> {
return p + n;
});
複製代碼
三個參數的 reduce 方法:
能夠接收一個目標結果類型的初始值,一個串行的處理函數,一個並行的合併函數
// 將全部訂單的顧客名字進行拼接
// 第一個參數爲目標結果類型,這裏設置爲空的 StringBuilder
// 第二個參數 BiFunction,參數爲上一次的 StringBuilder 和流中的下一項,返回新的 StringBuilder
// 第三個參數 BinaryOperator,參數都是 StringBuilder,返回合併後的 StringBuilder
StringBuilder reduce = orders.stream()
.reduce(new StringBuilder(),
(sb, next) -> {
return sb.append(next.getCustomerName() + ",");
},
(sb1, sb2) -> {
return sb1.append(sb2);
});
複製代碼
其餘概括方法:
max/min 求最大/最小值,接收一個比較器做爲參數
對於 LongStream 等基礎類型 Stream,則不須要傳入比較器參數
collect 是收集操做,對流進行終結操做,把流導出爲咱們須要的數據結構
collect 須要接收一個收集器 Collector 對象做爲參數,JDK 內置的 Collector 的實現類 Collectors 包含了許多經常使用的導出方式
collect 經常使用 API:
導出流爲集合:
// 轉爲 List(ArrayList)
orders.stream().collect(Collectors.toList());
// 轉爲不可修改的 List
orders.collect(Collectors.toUnmodifiableList());
複製代碼
// 轉爲 Set
orders.stream().collect(Collectors.toSet());
// 轉爲不可修改的 Set
orders.stream().collect(Collectors.toUnmodifiableSet());
複製代碼
orders.stream().collect(Collectors.toCollection(LinkedList::new));
複製代碼
導出流爲 Map:
// 使用 toMap 獲取訂單 ID + 下單用戶名的 Map
orders.stream()
.collect(Collectors.toMap(Order::getId, Order::getCustomerName))
.entrySet().forEach(System.out::println);
//使用 toMap 獲取下單用戶名 + 最近一次下單時間的 Map
orders.stream()
.collect(Collectors.toMap(Order::getCustomerName, Order::getPlacedAt,
(x, y) -> x.isAfter(y) ? x : y))
.entrySet().forEach(System.out::println);
複製代碼
toUnmodifiableMap:返回一個不可修改的 Map
toConcurrentMap:返回一個線程安全的 Map
分組導出:
在 toMap 中遇到重複的鍵名,經過指定一個處理函數來選擇一個鍵值保留
大多數狀況下,咱們須要根據鍵名分組獲得 Map,Collectors.groupingBy 是更好的選擇
重載方法有三個,截圖來自 IDEA 參數提示:
一個參數,等同於第二個參數爲 Collectors.toList,即鍵值爲 List 類型
// 按照下單用戶名分組,鍵值是該顧客對應的訂單 List
Map<String, List<Order>> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName));
複製代碼
兩個參數,第二個參數用於指定鍵值類型
// 按照下單用戶名分組,鍵值是該顧客對應的訂單數量
Map<String, Long> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName,
Collectors.counting()));
複製代碼
三個參數,第二個參數用於指定分組結果的 Map 類型,第三個參數用於指定鍵值類型
// 按照下單用戶名分組,鍵值是該顧客對應的全部商品的總價
Map<String, Double> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName,
Collectors.summingDouble(Order::getTotalPrice)));
// 指定分組結果的 Map 爲 TreeMap 類型
Map<String, Double> collect = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomerName,
TreeMap::new,
Collectors.summingDouble(Order::getTotalPrice)));
複製代碼
分區導出:
分區使用 Collectors.partitioningBy,就是將數據按照 TRUE 或者 FALSE 進行分組
// 按照是否有下單記錄進行分區
customers.stream()
.collect(Collectors.partitioningBy(customer -> orders
.stream()
.mapToLong(Order::getCustomerId)
.anyMatch(id -> id == customer.getId())
));
// 等價於
customers.stream()
.collect(Collectors.partitioningBy(customer -> orders
.stream()
.filter(order -> order.getCustomerId() == customer.getId())
.findAny()
.isPresent()
));
複製代碼
類中間操做:
Collectors 還提供了相似於中間操做的 API,方便在收集時使用,如 counting、summingDouble、maxBy 等
Collectors.maxBy
// 獲取下單量最多的商品,三種方式
// 使用收集器 maxBy
Map.Entry<String, Integer> e1 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.collect(Collectors.groupingBy(OrderItem::getProductName,
Collectors.summingInt(OrderItem::getProductQuantity)))
.entrySet()
.stream()
.collect(Collectors.maxBy(Map.Entry.<String, Integer>comparingByValue()))
.get();
// 使用中間操做 max
Map.Entry<String, Integer> e2 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.collect(Collectors.groupingBy(OrderItem::getProductName,
Collectors.summingInt(OrderItem::getProductQuantity)))
.entrySet()
.stream()
.max(Map.Entry.<String, Integer>comparingByValue())
.get();
// 由大到小排序,再 findFirst
Map.Entry<String, Integer> e3 = orders.stream()
.flatMap(order -> order.getOrderItemList().stream())
.collect(Collectors.groupingBy(OrderItem::getProductName,
Collectors.summingInt(OrderItem::getProductQuantity)))
.entrySet()
.stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.findFirst()
.get();
// 注:
// Map.Entry.<String, Integer>comparingByValue().reversed()
// 能夠與下面語句等價
// Comparator.comparing(Map.Entry<String, Integer>::getValue).reversed()
複製代碼
Collectors.joining
// 下單用戶名去重後拼接
String collect = orders.stream()
.map(Order::getCustomerName)
.distinct()
.collect(Collectors.joining(","));
// .collect(Collectors.joining(",", "[", "]")); // 可指定前綴和後綴
複製代碼