lambda表達式
3.lambda表達式高級拓展
方法引用
方法引用是結合lambda表達式的一組語法特性,在開發過程中方法引用配合lambda表達式可以對代碼進行簡化,但是相應的會損失掉一些可讀性。
方法引用具體分爲靜態方法引用、實例方法引用和構造方法引用。首先創建測試用類,
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
private String name, gender;
private int age;
// 靜態方法比較 age
public static int compareAge(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
// 實例方法比較 Name
public static int compareName(Person p1, Person p2) {
return p1.getAge().hashCode() - p2.getAge().hashCode();
}
}
此處使用 lombok語法支持,增加所有屬性的構造方法和空構造方法。
1)靜態方法引用的使用
- 原始形式:
類名.靜態方法名()
- 引用形式:
類名::靜態方法名
public class Test() {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("a", "male", 16));
personList.add(new Person("c", "female", 32));
personList.add(new Person("de", "female", 34));
// 使用匿名內部類的方式對 personList進行排序
Collections.sort(personList, new Comparator() {
@override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
System.out.println(personList);
// lambda表達式實現
Collections.sort(personList, (p1, p2) -> p1.getAge() - p2.getAge());
System.out.println(personList);
// 靜態方法引用
Collections.sort(personList, Person::compareAge);
System.out.println(personList);
}
}
2)實例方法引用的使用
- 原始形式:
類實例對象.實例方法名()
- 引用形式:
類實例對象::實例方法名
public class Test() {
public static void main(String[] args) {
Person p = new Person();
List<Person> personList = new ArrayList<>();
personList.add(new Person("a", "male", 16));
personList.add(new Person("c", "female", 32));
personList.add(new Person("de", "female", 34));
// 實例方法引用
Collections.sort(personList, p::compareName);
System.out.println(personList);
}
}
3)構造方法引用的使用
與實例方法和靜態方法不同,構造方法的引用需要綁定函數式接口。
創建一個函數式接口,用於初始化 Person類對象,
@FunctionalInterface
interface IPerson {
Person initPerson(String name, String gender, int age);
}
public class Test() {
public static void main(String[] args) {
// 構造方法引用,綁定函數式接口 IPerson
IPerson ip = Person::new;
Person a = ip.initPerson("a", "male", 16);
}
}
Stream
1)概述及演示
Stream流的引入是針對存儲多個數據的容器(如數組、集合等)在批量處理數據過程中的繁雜操作提出的API,可以通過結合lambda表達式通過串行和並行兩種方式完成對批量數據的增強操作。
public class Test() {
public static void main(String[] args) {
List<String> accounts = new ArrayList<>();
accounts.add("tom");
accounts.add("jerry");
accounts.add("beta");
// 用戶名大於3纔算有效賬號
// 傳統方式遍歷集合
for (String account: sccounts) {
if (account.length() > 3)
System.out.println(account);
}
// 通過迭代器進行處理(實際上for循環就是使用了迭代器)
Iterator<String> it = accounts.iterator();
while(it.hasNext()) {
String account = it.next();
if (account.length() > 3)
System.out.println(account);
}
// 通過Stream流方式簡化代碼
List validAccounts = accounts.stream().filter(s -> s.length()>3).collect(Collectors.toList());
System.out.println(validAccounts);
}
}
首先通過stream
方法獲取相應數據的流對象,之後通過filter
方法配合 lambda表達式對流中的數據進行過濾。最後以List的形式返回結果。
2)Stream API
Stream的處理流程可總結爲,
獲取Stream對象的方式有多種,舉例如下
- 從集合中獲取,
集合對象.stream()
或集合對象.parallelStream()
方法可獲取集合的普通Stream對象和支持併發處理的Stream對象 - 從數組中獲取,
Arrays.stream(T[] t)
方法可獲取數組的Stream對象 - 通過緩衝流獲取Stream對象,如
BufferReader對象.lines()
方法 - 通過靜態工廠方法獲取Stream對象,如 java.util.stream包中對基本類型都創建了流對象的構造器。java.nio.file中也提供了流對象的構造方法
- 自定義流對象
Stream API大致可分爲兩個主要操作方式和一個輔助操作方式,
- 中間操作API,intermediate操作
可以理解爲邏輯處理,操作結果是一個Steam對象,一個流程中可以有多個連續的中間操作。中間操作只記錄操作方式,不做具體執行,直到結束操作發生時纔對數據最終執行。
**i.**無狀態:數據處理時不受之前的中間操作影響。主要包含map/filter/parallel/sequential/unorder
等操作
**ii.**有狀態:數據處理時,受到前置中間操作影響。主要包含distinct/sorted/limit/skip
等操作 - 結束操作API,terminal操作
一個Stream流對象的處理流程只能有一個結束操作。一旦這個操作被觸發,就會開啓數據處理的整個中間過程,最終生成結果。該過程是不可逆的。
**i.**非短路操作:當前的Stream對象必須處理完集合中所有數據才能得到處理結果。注意包含forEach/forEachOrdweed/toArray/reduce/collect/min/max/count/iterator
等
**ii.**短路操作:當前的Stream對象在處理過程中一旦滿足某個條件,就可以得到結果。主要包含anyMatch/allMatch/noneMatch/findFirst/findAny
等
3)Stream對象對集合處理
由批量數據得到Stream對象
public class Test {
public static void main(String[] args) {
// 多個數據得到stream
Stream stream1 = Stream.of("adad", "dadads", "dewf");
// 由數組得到Stream對象
String[] strArrays = new String[] {"a", "c", "f"};
Stream stream2 = Arrays.stream(strArrays);
// 由列表得到Stream對象
List<String> list = new ArrayList<>();
list.add("dsad");
list.add("dahdi");
Stream stream3 = list.stream();
// 由集合得到Stream對象
Set<String> set = new HashSet<>();
set.add("dsad");
set.add("dahdi");
Stream stream4 = set.stream();
// 由Map得到Stream對象
Map<String> map = new HashMap<>();
map.put("dsad", 1000);
map.put("dahdi", 1200);
Stream stream5 = map.entrySet().stream();
}
}
Stream對象對基本數據類型的底層封裝
jdk8中目前只針對基本類型中最常使用的 int、long和double類型進行了封裝。以 int類型爲例,
public class Test {
public static void main(String[] args) {
Stream stream = IntStream.of(new int[] {20, 30, 40});
stream.foreach(System.out::println);
IntStream.range(1, 5).forEach(System.out::println); // 輸出 1,2,3,4
}
}
Stream對象轉換得到指定數據類型
public class Test {
public static void main(String[] args) {
// 由Stream對象得到數組
String[] strArrays = new String[] {"a", "c", "f"};
Stream stream2 = Arrays.stream(strArrays);
String strArr = stream2.toArray(String::new);
// 由Stream對象得到字符串,拼接對象中的元素
strArrays = new String[] {"a", "c", "f"};
stream2 = Arrays.stream(strArrays);
String str = stream2.collect(Collectors.joining()).toString();
// 由Stream對象得到列表
List<String> list = new ArrayList<>();
list.add("dsad");
list.add("dahdi");
Stream stream3 = list.stream();
List<String> strList = (List<String>) stream3.collect(Collectors.toList());
// 由Stream對象得到集合
Set<String> set = new HashSet<>();
set.add("dsad");
set.add("dahdi");
Stream stream4 = set.stream();
set<String> strSet = (Set<String>) stream3.collect(Collectors.toSet());
// 由Stream對象得到Map
strArrays = new String[] {"a", "c", "f"};
stream2 = Arrays.stream(strArrays);
Map<String, Integer> strMap = Map<String> stream2.collect(Collectors.toMap(x->x, y->"haha:"+y));
System.out.println(strMap); // 得到 ["a"="haha:a", "b"="haha:b", "c"="haha:c"]
}
}
Stream中常見API操作
public class Test {
public static void main(String[] args) {
List<String> accountList = new ArrayList<>();
accountList.add("songjiang");
accountList.add("linchong");
accountList.add("luzhishen");
// map()中間操作,接收一個FunctionalInterface,對數據逐個操作
accountList = accountList.stream().map(x->"梁山好漢:"+x).collect(Collectors.toList());
// filter()過濾
accountList = accountList.stream().filter(x->x.length()>5).collect(Collectors.toList());
// forEach 增強for循環
accountList.forEach(x->System.out.println("forEach->"+x));
// 如果需要多次循環建議不要使用多次的forEach,因爲多次調用forEach實際上是開啓了多次Stream操作
// 建議使用peek()完成多次循環遍歷,在一次遍歷過程中完成所有步驟的操作(將多次循環合併)
accountList.stream().peek(x->"peek1:"+x)
.peek(x->"peek2:"+x).forEach(System.out::println);
}
}
Stream中對數字運算的支持
public class Test {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(20);
intList.add(19);
intList.add(7);
intList.add(8);
intList.add(86);
intList.add(11);
intList.add(3);
intList.add(20);
// skip跳過部分數據
intList.stream().skip(3).forEach(System.out::println); // 跳過前三個數據
// limit顯著輸出數據數目
intList.stream().skip(3).limit(2).forEach(System.out::println); // 跳過前三個數據且只對之後兩個數據進行處理
// distinct剔除重複數據
intList.stream().distinct().forEach(System.out::println);
// sorted排序
intList.stream().sorted().forEach(System.out::println);
// max、min獲取極值,需傳入一個Comparator
Optional optional = intList.stream().max((x, y) -> x - y);
System.out.println(optional.get());
// reduce進行合併操作
optional = intList.stream().reduce((cumSum, x) -> cumSum + x);
System.out.println(optional.get());
}
}
4)Stream性能
- 在基本數據類型的操作上(如 ArrayList<Integer>)建議使用迭代器、增強for循環和普通for循環。如果是多核環境,可以使用
parallelStream()
並行處理能夠有效提升性能 - 當面對複雜數據的處理時(如 ArrayList<className>),並行Stream在多核環境下可以有效提升數據處理的性能
5)並行Stream的線程安全
並行Stream對象底層原理是將大任務拆分爲多個子任務執行。
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i=0; i<1000; i++) {
list.add(i);
}
// 將list中的元素複製到另一個List
// 串行Stream
List list2 = new ArrayList<>();
list.stream().forEach(x -> list2.add(x));
System.out.println(list.size());
System.out.println(list2.size());
// 並行Stream
List list3 = new ArrayList<>();
list.parallelStream().forEach(x -> list3.add(x));
System.out.println(list3.size());
}
}
上述代碼的運行結果爲,
1000
1000
995
出現了數據丟失的情況,原因是 ArrayList類並非是線程安全的類,且 Stream API中明確寫明forEach
方法並不是線程安全的。解決這一問題可以使用 Stream API中提供的,在官方文檔中明確說明的並行情況下保證線程安全的方法。如
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i=0; i<1000; i++) {
list.add(i);
}
// collect方法用於收集最終的Stream處理結果,是線程安全的方法
List<Integer> list4 = list.parallelStream().collect(Collectors.toList());
System.out.println(list4.size());
}
}
此外更簡單的解決方法是使用線程安全的併發集合類。