文章目錄
歡迎進入Lambda世界
一、 Lambda表達式的基礎語法
java8引入了一個新的操作符 “->” ,該操作符稱爲箭頭操作符或Lambda操作符
左側:Lambda表達式的參數列表
右側:Lambda表達式中所需要執行的功能,即Lambda體
語法格式:
格式一:無參數,無返回值
() -> System.out.printLn(“hello world”);
Runnable r1 = () -> System.out.println("hello world");
r1.run;
格式二:一個參數,並且無返回值。
(x) -> System.out.printLn(x);
格式三:如果只有一個參數,小括號可以不寫
x -> System.out.printLn(x);
格式四:有兩個以上參數,並且有返回值
如果Lambda體有多條語句,則必須使用大括號
Comparator<Integer> com = (x,y) -> {
System.out.println("請求參數:x=“ + x +",y= "+y);
return Integer.compare(x,y);
}
格式五:若Lambda體中只有一個條語句,則return和大括號都可以不寫
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
格式六:Lambda表達式的參數列表的數據類型可以省略不寫,因爲JVM編譯器通過上下文推斷出數據類型,即”類型推斷“
總結:左右遇一括號省,左側推斷類型省。
解釋:左邊遇到一個參數括號省,右邊遇到一條語句大括號和return省
左側類型爲Interger,推斷出右側的x和y必爲Integer,所有類型可以省,如下代碼:
x -> System.out.printLn(x); //左右遇一括號省
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);//左側推斷類型省
二、 Lambda表達式需要"函數式接口"的支持
函數式接口:接口中只有一個抽象方法的接口,稱爲函數是接口,可以使用註解**@FunctionalInterface**來修飾,如果多個抽象方法則會報錯。
java8內置的四大核心函數式接口
1. Consumer : 消費型接口
void accept(T t);
public void test1(){
testConsumer(100,(m) -> System.out.println("購物消費" +m +" 元"));
}
public void testConsumer(double money,Consumer<Double> con){
con.accept(money);
}
2. Supplier : 供給型接口
T get();
public void test2(){
getNumList(10, () -> (int) (Math.random() *100));
for(Integer num : numList){
System.out.prinln(num);
}
}
//需求:產生指定個數的參數,並放入集合
public List<Integer> getNumList(int num,Supplier<Integer> sup){
List<Integer> list = new ArrayList();
for(int i = 0 ; i< num; i++){
Integer n = sup.get();
list.add(n);
}
return list;
}
3. Function< T,R > : 函數型接口
R apply(T t);
public void test3(){
strHandler("hello world", (str) -> str.trim());
}
//用於處理字符串
public String strHandler(String str,Functing<String , String> fun){
return fun.apply(str);
}
4. Predicate : 斷言型接口
boolean test(T t);
public void test4(){
List<String> list = Arrays.asList("hello","www","Lambda","ok","world");
List<String> strList = filterStr(list,(s) -> s.length() > 3);//Lambda體只有一條語句return可以不寫
for(String s : strList) {
System.out.println(s);
}
}
//將滿足條件的字符串添加到集合中
public List<String> filterStr(List<String> list , Predicate<String> pre){
List<String> strList = new ArrayList<>();
for(String str : list){
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}
5.其他函數式接口
函數式接口 | 參數類型 | 返回類型 | 用途 | 使用 |
---|---|---|---|---|
BiFunction<T,U,R> | T,U | R | 對類型爲T,U參數參數應用操作,返回R類型的結果,包含方法爲 R apply(R 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); | |
ToIntFunction< T > | int | void | 計算int值的函數 | |
ToLongFunction< T > | long | void | 計算long值的函數 | |
ToDoubleFunction< T > | double | void | 計算double值的函數 | |
IntFunction< R > | int | R | 參數爲int類型的函數 | |
LongFunction< R > | long | R | 參數爲long類型的函數 | |
DoubleFunction< R > | double | R | 參數爲double類型的函數 |
三、方法引用和構造器引用
1、方法引用:
若Lambda體中的內容有方法已經實現了,那麼我們可以使用**方法引用**(可以理解爲方法引用是Lambda表達式的另外一種表現形式)
主要有三種語法格式:
- 對象::實例方法名
- 類::靜態方法名
- 類::實例方法名
使用注意事項:
1:Lamdba體中調用方法的參數列表與返回值類型,要與函數式接口中抽象方法的函數列表和返回值類型一致!
2:如果函數式接口的第一個參數是方法的調用者,第二個參數是調用方法的參數時,則可以使用**類名::實例方法**
使用案例
1、對象::實例方法名
//demo1:
Consumer<String> con = (x)-> System.out.println(x);
//上面的println方法已經實現了我們功能,可以使用方法引用的方式來代替Lambda表達式
//替換如下
PrintStream ps = System.out;
Consumer<String> con1 = ps::println;
Consumer<String> con2 = System.out::println;
//Consumer的accept方法的參數爲一個,返回值類型爲void;
//println的參數爲一個,返回值類型爲void;
//所有可以使用方法引用來代替Lambda表達式
//demo2:
Employee emp = new Employee();
Supplier<String> sup = () -> emp.getName();
//替換如下
Supplier<String> sup = emp::getName;
System.out.println(sup.get());
注:使用方法引用時,方法引用的參數類型和返回值需要和函數式接口方法的參數和返回值類型保持一致。 如上代碼
2、類::靜態方法名
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
//上面的 Integer.compare方法已經實現了我們功能,可以使用方法引用的方式來代替Lambda表達式
//替換如下:
Comparator<Integer> com1 = Integer::compare;
3、類::實例方法
BiPredicate<String,String> bp = (x,y) -> x.equalse(y);
//替換如下:
BiPreicate<String,String> bp1 = String::equals;
注:如果函數式接口的第一個參數是方法的調用者,第二個參數是調用方法的參數時,則可以使用類名::實例方法,如上代碼。
2、構造器引用
1、格式:
ClassName::new
2、使用注意事項
使用構造器引用,使用的哪個構造器呢?
答:根據函數式接口方法的參數自動匹配對應類的構造器。即需要調用的構造器參數列表要與函數式接口中抽象方法的參數列表保持一致。
3、使用案例
Supplier<Employee> sup = () -> new Employee();
//構造器引用替換如下:
Supplier<Employee> sup =Employee::new;
//使用一個參數的函數式接口,則調用Employee的一個參數的構造參數
Function<Integer,Employee> fun = (x) -> new Employee(x);
Function<Integer,Employee> fun2 = Employee::new;
Employee emp = fun2.apply(23);
System.out.println(emp);
//使用Employee的兩個參數的構造器。
BiFunction<String,Integer,Employee> bf = Employee::new;
Employee emp2 = bf.apply("張三",23);
注:Supplier的方法是無參的,所有Employee::new使用的是無參構造器來創建對象
3、數組引用(與構造器引用類似)
1、格式
Type::new
2、注意事項
3、使用案例
Function<Integer,String[]> fun = (x) -> new String[x];
String[] strs = fun.apply(10);
System.out.println(strs.length);
//數組引用替換如下:
Function<Integer,String[]> fun2 = String[]::new
String[] strs2 = fun2.apply(20);
System.out.println(strs2.length);
四、強大的Stream API
1、概念
java8中兩個最爲重要的改變,一個是Lambda表達式,另一個就是Stream API(java.util.stream.*);
Stream 是java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行復雜的查找、過濾和映射數據等操作。使用Stream API對集合數據進行操作,就類似於使用SQL執行的數據庫查詢,也可以使用Stream API來並行執行操作。簡而言之,Stream API提供了一種高效且易於使用的處理數據的方式。
流(Stream)到底是什麼呢?
是數據渠道,用於操作數據源(集合,數組等)所生成的元素序列。“集合講的是數據,流講的是計算”。
2、注意事項
- Stream自己不會存儲元素
- Stream不會改變源對象。相反,他們會返回一個持有結果的新Stream
- Stream操作是延遲執行的,這意味着他們會等到需要結果的時候才執行。
3、Stream的操作三個步驟
- 創建Stream
一個數據源(如:集合、數組),獲取一個流 - 中間操作
一箇中間操作鏈,對數據源的數據進行處理 - 終止操作(終端操作)
一個終止操作,執行中間操作鏈,併產生結果。
3.1創建Stream
//創建Stream
public void test1(){
//1 可以通過Collection系列集合提供的Stream() 或parallelStream()
//parallelStream 並行流
List<String> list = Lists.newArrayList();
Stream<String> stream1 = list.stream();
//2通過Arrays中的靜態方法Stream()獲取數組流
Employee[] emps =new Employee[10];
Stream<Stream> stream2 = Arrays.stream(emps);
//3 通過Stream類中的靜態方法of();
Stream<String> stream3 = Stream.of("aa","bb","cc");
//4創建無限流
//迭代 ,從0開始,一直加2,無限疊加循環。
Stream<Integer> sream4 = Stream.iterate(0,(x) -> x+2);
stream4.forEach()System.out::println);
//中間操作,獲取前10個
stream4.limit(10).forEach()System.out::println);
//5、 生成6個隨機數,也可以生成無限流。
Stream.generate(() -> Math.random()).limit(6).forEach(System.out::println);
}
3.2 Steam的中間操作
多個中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何處理! 而在終止操作時一次性全部處理,稱爲"惰性求值"。
1、篩選與切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,從流中排除某些元素 |
distinct() | 篩選,通過流所生成元素的hashCode()和equals()去除重複元素 |
limit(long maxSize) | 截斷流,使其元素不超過給定數量 |
skip(long n) | 跳過元素,返回一個扔掉了前n個元素的流,若流中元素不足n個,則返回一個空流,與limit(n)互補 |
篩選與切片的使用案例
1、 filter
Stream<Employee> s = employees.stream()
.filter((e) ->e.getAge()>35)//中間操作,不會有任何結果
.forEach(System.out::println);//終止操作,一次性執行全部內容,即”惰性求值“
//內部迭代由Stream API完成
//外部迭代:我們自己寫
Iterator<Employees> it = employees.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
2、limit
Stream<Employee> s = employees.stream()
.filter((e) ->e.getAge()>35)//中間操作,不會有任何結果
.limit(2) //中間操作,截斷流,只執行2次迭代。後續的迭代操作不再執行。
.forEach(System.out::println);//終止操作,一次性執行全部
3、skip
Stream<Employee> s = employees.stream()
.filter((e) ->e.getAge()>35)//中間操作,不會有任何結果
.skip(2) //中間操作,跳過前兩個元素,取之後兩個
.forEach(System.out::println);//終止操作,一次性執行全部
4、distinct 去重
Stream<Employee> s = employees.stream()
.filter((e) ->e.getAge()>35)//中間操作,不會有任何結果
.skip(2) //中間操作,跳過前兩個元素,取之後兩個
.distinct() //中間操作,去重(需要重寫hashCode和equals方法)
.forEach(System.out::println);//終止操作,一次性執行全部
2、映射
map它接收Lambda,將元素轉換成其他形式或提取信息,接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。
flatMap它接收一個函數作爲參數,將流中的每個值都轉換成另一個流,然後把所有的流連成一個流。
方法 | 描述 |
---|---|
map(Function f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素 |
mapToDouble(ToDoubleFunction f ) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的DoubleStream |
mapToInt(ToLongFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的IntStream |
mapToLong(ToLongFunction f) | 接收一個函數作爲參數,該函數會被應用到每個元素上,產生一個新的LongStream |
flatMap(Function f) | 接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有流連成一個流 |
映射的使用案例
1、map
public void test(){
List<String> list = Arrays.aslist("aa","bb","cc","dd","ee");
list.stream().stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println)
System.out.println("------------分割線----------")
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
System.out.println("------------分割線----------")
Stream<Stream<Character>> streams =
list.stream().map(ClassName::filterCharacter);
streams.forEach((sm) -> {
sm.forEach(System.out::println);
})
}
//操作字符串的方法
publib Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for(Character ch : str.toCharArray){
list.add(ch);
}
return list.stream();
}
2、flatMap
//上面代碼的替換,使用flatMap
Stream<Character> sm = list.stream()
.flatMap(ClassName::filterCharacter)
.forEach(System.out::println);
map和flatMap區別
//上面兩個方法map和flatMap對比
map內部是接收多少個流,則有多少個流,要分開操作--->{ {流1(元素1,元素2,元素3)},{流2(元素4,元素5...)}, ... {流n(元素n)}}
flatMap內部是將所有的流元素拿出來,放在一個流中--->{ 一個流 元素1,元素2,元素3,元素4,元素5 …… 元素n }
類似於add和addAll;
add添加的是整個對象,如果是集合,就把集合添加到集合中
List list1 = new ArrayList();
List list2 = new ArrayList();
List list3 = new ArrayList();
List listStr = new ArrayList();
listStr.add("aa");
listStr.add("bb");
list2.add(11);
list3.add(11);
list2.add(22);
list3.add(22);
list2.add(listStr);
list3.add(listStr);
//輸出: list2 = [11, 22, [aa,bb] ]
//輸出: list3 = [11, 22, aa, bb]
addAll添加的是元素,如果添加集合,則將集合的元素添加到原理的集合中。
3、排序
sorted(): 自然排序(Comparable)
sorted(Comparator com): 定製排序(Comparator)
方法 | 描述 |
---|---|
sorted() | 產生一個新流,其中按自然順序排序 |
sorted(Comparator comp) | 產生一個新流,其中按比較器順序排序 |
排序的使用案例
List<String> list = Arrays.aslist("aa","bb","cc","dd","ee");
list.stream().sorted()
.forEach(System.out::println);
System.out.println("------------分割線----------")
employees.stream()
.sorted((e1,e2)->{
if(e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName));
}else{
return e1.getAge().compareTo(e2.getAge());
}
}).forEach(System.out::println);
3.3 終止操作
終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,比如:list,Integer,甚至是void。
1、查找與匹配
方法 | 描述 |
---|---|
allMatch(Predicate p ) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicate p ) | 檢查是否沒有匹配到所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count | 返回流中元素的個數 |
max | 返回流中最大值 |
min | 返回流中最小值 |
查找與匹配的使用案例
1、allMatch
boolean b1 = employees.stream()
.allMatch((e) -> e.getStatus().equals(Status.BUSY));
System.out.println("是否全部匹配所有元素" + b1);
2、anyMatch
boolean b2 = employees.stream()
.anyMatch((e) -> e.getStatus.equals(Status.BUSY));
System.out.println(”是否有一個匹配的元素“ +b2);
3、 noneMatch
boolean b3 = employees.stream()
.noneMatch((e) -> e.getStatus.equals(Status.BUSY));
System.out.println("是否沒有匹配到所有元素" +b3);
4、 findFirst
Optional<Employee> op = employees.stream()
.sort( (e1,e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst((e) -> e.getStatus.equals(Status.BUSY));
op.orElse(new Employee);//如果Opentional裏面的值爲空,則使用替代的對象,避免空指針異常
System.out.println("獲取第一個元素" +op.get());
5、findAny
Opentional<Employee> op = employees.stream()
.filter((e) -> e.getStatus().equals(Satus.FREE))
.findAny();
//.stream() 獲取一個並行流,從上往下找,找到第一個。
//.parallelStream() 獲取一個並行流,多個線程同時找,誰找到就用誰。
6、count
Long count = employees.stream().count();
System.out.println(count);
7、max
Opentional<Employee> op1 = employees.stream().max((e1,e2) -> Double.compare(e1.getSarlary(),e2.getSarlary));
System.out.println(op1.get());
8、min
Optional<Double> op2 = employees.stream().map(Employee::getSarlary).min(Double::compare);
System.out.println(op2.get());
2、 歸約
方法 | 描述 |
---|---|
reduce(T identity, BinaryOperator accumulator) | 可以將流中的元素反覆結合起來,得到一個值,返回T |
reduce(BinaryOperator b) | 可以將流中元素反覆結合起來,得到一個值,返回Optional< T > |
備註:map和reduce的連接通常稱爲map-reduce模式,因Google用它來進行網絡搜索而出名
歸約的使用案例
List<Integer> list = Arrays.aslist(1,2,3,4,5,6,7,8,9);
Integer sum =list.stream().reduce(0,(x,y) -> x+y);
//第一步 : 0+1=1
//第二步: 1+2 =3
//第三步: 3+3 =6
//第四步: 3+4 =10 ……
System.out.println(sum); //45
Optional<Double> op = employees.stream()
.map(Employee::getSarlary)
.reduce(Double::sum);
System.out.println(op.get());
3、收集
方法 | 描述 |
---|---|
collect(Collector c) | 將流轉換成其他形式,接受一個Collector接口的實現,用於給Stream中元素做彙總的方法 |
注: Collector接口中方法的實現決定了如何對流執行收集操作(如收集到List,Set,Map),但是Collectors實用類提供了很多靜態方法,可以方便地創建常見收集器實例,具體方法與實例如下表:
收集的使用案例(分組,最大值,最小值等)
//收集到list中
List<String> list = employees.stream()
.map(Employee::getName)
.collection(Collectors.toList());
list.forEach(System.out::println);
//收集到set中
List<String> set = employees.stream()
.map(Employee::getName)
.collection(Collectors.toSet());
set.forEach(System.out::println);
//收集到hash或者linkedHashSet中
HashSet<String> hs = employees.stream()
.map(Employee::getName)
.collection(Collectors.toCollection(HashSet::new));
HashSet<String> lhs = employees.stream()
.map(Employee::getName)
.collection(Collectors.toCollection(LinkedHashSet::new));
set.forEach(System.out::println);
lhs.forEach(System.out::println);
//總數
Long count = employees.stream().collection(Collectors.counting());
//平均值
Long count = employees.stream()
.collection(Collectors.averagingDouble(Employee::getSarlary));
//工資總和
Double sum = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary))
//最大值
Optional<Employee> max = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSarlary, e2.getSarlary())));
//最小值
Optional<Double> min = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
//分組
Map<Status, List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
//多級分組
Map<Status, Map<String, Employee>>>map = employees.stream()
.collect(Collectors.groupBy(Employee::getStatus), Collectors.groupBy((e) -> {
if (((Employee) e).getAge() <= 35) {
return ”青年“
} else if (((Employee) e).getAge() <= 50) {
return ”中年“;
} else {
return "老年";
}
}));
//分區
Map<Boolean, List<Employee>> map = employees.stream().collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));
System.out.println(map);
//各種獲取方式
DoubleSummaryStatistics dss = employees.stream()
.collect(Collector.summarizingDouble(Employee::getSalary));
System.out.println(dss.getSum());
System.out.println(dss.getAverage());
System.out.println(dss.getMax());
//字符串拼接
String str1 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining());
System.out.println(str);// "張三李四王五趙六";
//字符串拼接,分割
String str2 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(”,“));
System.out.println(str2);// "張三,李四,王五,趙六"
//字符串拼接,分割&字符串前後加符號
String str3 = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",", "===", "==="));
System.out.println(str3);// "===張三,李四,王五,趙六===";
todo 持續更新
預告片:Opentional 與 新時間LocalDateTime