lambda表達式的使用方式
************************************************************************************************************************
java8新特性之一,流式處理數據
流式操作不僅可以操作集合,也可以操作數據和文件,一般可以轉化為流的基本可以使用流來處理數據;流式操作基本上可以分為三個部分:流轉化,中間操作,終端操作。
************************************************************************************************************************
lambda操作List,Map和Array>>>
lambda操作list前提是將List轉化為Stream接口,除非通過最后步驟的收集器之類的結尾操作指定具體的類型,否則都是會返回Stream接口。
// 創建MdProIndexExt集合
List<MdProIndexExt> bdVos = Lists.newArrayList();
// 將List轉化為Stream,返回Stream,這是lambda操作List的前提條件
Stream<MdProIndexExt> stream = bdVos.stream();
另一種將集合轉為流,使用praallelStream(),支持並行處理數據,使用時考慮多線程安全使用
Stream<ReTrafficDetailExt> parallelStream = trafficDetailExtList.parallelStream();
返回String,Stream.distinct()去重,Collectors.joining(",")以","分割拼接字符串
List<MdProIndexExt> bdVos = Lists.newArrayList();
String ids = bdVos.stream().map(MdProIndexExt::getProBatchId).distinct().collect(Collectors.joining(","));
System.out.println(ids); // 輸出:"張三,李四,王五"
lambda的map操作,也是返回Stream;其中原本List指定的類型是MdProIndexExt類,經過map轉化以后為String類型,這是因為getProBatchId方法返回的是String類型,其中雙冒號“::”是lambda的操作簡寫,前面是類名,后面是方法名
Stream<String> streamToString = stream.map(MdProIndexExt::getProBatchId); 等價於 Stream<String> streamToString = stream.map(p -> p.getProBatchId());
返回Set
Set<String> setString = streamToString.collect(Collectors.toSet());
返回map,其中t.getRouteId()對應map的key,p.getRouteName()對應map的value,如果key有重復則會報錯,使用(k1, k2) -> k1處理,最終返回Map具體的類型也可以指定,這里是HashMap,key和value不能為空,后面兩個參數可以為空
Map<String, String> mapStream = stream.collect(Collectors.toMap(t -> t.getRouteId(), p -> p.getRouteName()getRouteId(), (k1, k2) -> k1, HashMap::new));
forEach處理List集合
List<MdProIndexDetailExt> detList = Lists.newArrayList();
detList.forEach(p -> p.setRowState(RowStateEnum.DELETED.getValue()));
forEach處理Map集合
Map<String, String> mapStream = stream.collect(Collectors.toMap(t -> t.getRouteId(), p -> p.getRouteName()getRouteId(), (k1, k2) -> k1, HashMap::new));
mapStream.forEach((key, value) -> System.out.println(key + "=" + value));
List分組,key為getRouteName()的返回值,意思是將多個對象的routeName屬性值相同則分為一組
List<MdProIndexExt> bdVos = Lists.newArrayList();
// 第二個參數不寫則默認表示對象的List集合
Map<String, List<MdProIndexExt>> listToMap = bdVos.stream().collect(Collectors.groupingBy(MdProIndexExt::getRouteName));
Collectors.counting()意思是將多個routeId屬性值相同對象按照routeId分組后計數每個組的個數
Map<String, Long> listToMap = bdVos.stream().collect(Collectors.groupingBy(MdProIndexExt::getRouteId,Collectors.counting()));
Collectors.summingInt()求和,返回結果是每組所有對象的index屬性相加,每組的key對應求和的結果
Map<String, Integer> listToMap = bdVos.stream().collect(Collectors.groupingBy(MdProIndexExt::getRouteId, Collectors.summingInt(MdProIndexExt::getIndex)));
多重分組
Map<String, Map<String, List<ReTrafficDetailExt>>> mapInMpaInList = trafficDetailExtList.stream().collect(
Collectors.groupingBy(ReTrafficDetailExt::getObservationDate,
Collectors.groupingBy(ReTrafficDetailExt::getDrivingDirectionCode)));
分區,將需要的數據與不需要的數據進行分區
// 將交通量中路線名稱不為空的數據與其他數據分組兩組,key為true這組則是想要的結果
Map<Boolean, List<ReTrafficDetailExt>> listInMap = trafficDetailExtList.stream().collect(Collectors.partitioningBy(p -> StringUtil.isNotEmpty(p.getRouteName())));
返回最大值的對象,有可能不存在,最終結果是key->MdProIndexExt,其中MdProIndexExt是每一組中行號(index)最大的對應的對象
Map<String, Optional<MdProIndexExt>> listToMap = bdVos.stream().collect(Collectors.groupingBy(MdProIndexExt::getRouteId, Collectors.maxBy(Comparator.comparing(MdProIndexExt::getIndex))));
Stream.filter(),過濾數據,參數為true(符合實際要求)時則收集否則丟棄,這里是返回List集合
// 過濾掉路線名稱為空的對象,保留路線不為空的數據
List<MdProIndexExt> ListInFliter = bdVos.stream().filter(p -> StringUtil.isNotEmpty(p.getRouteName())).collect(Collectors.toList());
Stream.findAny()隨機返回一個對象;Stream.findFirst()返回第一個對象,使用Optional來接
// 隨機返回一個對象
Optional<MdProIndexExt> findAny = bdVos.stream().filter(p -> StringUtil.isNotEmpty(p.getRouteName())).findAny();
// 返回第一個對象
Optional<MdProIndexExt> findFirst = bdVos.stream().filter(p -> StringUtil.isNotEmpty(p.getRouteName())).findFirst();
Optional.isPresent()返回boolean,判斷對象是否存在
// 隨機返回一個對象 Optional<MdProIndexExt> findAny = bdVos.stream().filter(p -> StringUtil.isNotEmpty(p.getRouteName())).findAny();
// 判斷隨機返回的對象存不存在,若存在findFirst.get()獲取當前對象
if(findAny.isPresent()){
System.out.println(findFirst.get().getRouteName());
}
Optional.ifPresent();無返回值,判斷對象是否存在
// 返回第一個對象 Optional<MdProIndexExt> findFirst = bdVos.stream().filter(p -> StringUtil.isNotEmpty(p.getRouteName())).findFirst();
// 如果路線存在,則輸出路線名稱
findFirst.ifPresent(p -> System.out.println(p.getRouteName()));
Stream.allMatch(),判斷條件是否滿足所有對象
// 判斷是否滿足所有路線的路線名稱都不為空,若是則返回true,否則返回false
boolean isAllMatch = bdVos.stream().allMatch(p -> StringUtil.isNotEmpty(p.getRouteName()));
Stream.anyMatch()判斷條件否滿足一個對象;Stream.noneMatch()判斷條件是否都不滿足所有對象
// 判斷是否存在一條路線的路線名稱不為空,若存在則返回true,若所有路線的路線名稱都為空着返回false
boolean isAllMatch = bdVos.stream().anyMatch(p -> StringUtil.isNotEmpty(p.getRouteName()));
// 如果所有路線的路線名稱都為空則返回true,如果有一條路線的路線名稱不為空則返回false,noneMatch()與anyMatch()相對
boolean isAllMatch = bdVos.stream().noneMatch(p -> StringUtil.isEmpty(p.getRouteName()));
lambda的計算,可以通過map轉為特定的類型(int,long)之后再計算,也可以通過收集器統一計算(Collectors.summingInt(),Collectors.summinLong())
int min = trafficDetailExtList.stream().mapToInt(p -> DateUtil.getYear(stringToDate(p.getObservationDate()))).sum(); // 求和
OptionalDouble avg = trafficDetailExtList.stream().mapToInt(p -> DateUtil.getYear(stringToDate(p.getObservationDate()))).average(); // 求平均數
double sum = trafficDetailExtList.stream().collect(Collectors.summingDouble(p -> p.getEndStake().doubleValue())); // 求和
double avg = trafficDetailExtList.stream().collect(Collectors.averagingDouble(p -> p.getEndStake().doubleValue())); // 求平均數
lambda排序:lambda提供了另外一種sort排序方法:List.sort(),參數是Comparator 接口
// 傳入兩個參數p1,p2,命名隨意看個人愛好,然后對這兩個參數進行操作,可以多步操作但是返回值必須是int型
bdVos.sort((p1, p2) -> p1.getRowState().compareTo(p2.getRowState()));
lambda排序:另一個比較簡潔的方式,Comparator實現方式簡寫,是上面傳入兩個參數(p1,p2)方式的簡寫,但是要求返回值是int型
bdVos.sort(Comparator.comparing(ReTrafficDetailExt::getRowState));
多個排序條件
List<VcUser> listUser = new ArrayList<VcUser>();
// 先根據真實姓名排序,再根據用戶名排序,也提供了排序重載方式:(p1,p2) -> p1.compareTo(P2) listUser.sort(Comparator.comparing(VcUser::getRealName,(p1, p2) -> p1.compareTo(p2)).thenComparing(VcUser::getUserName));
lambda處理最值方式:目前我使用到有3種情況:通過Map映射轉化類型;通過收集器Collector集合;直接使用最值方法(max,min)
1.使用映射方式Map轉為int類型,返回值OptionalInt
// 還有mapToLong();mapToDuble(),取決於方法返回值類型,最后面min()獲取最小值,max()最大值,average()平均值,summing()求和
OptionalInt min = trafficDetailExtList.stream().mapToInt(p -> DateUtil.getYear(stringToDate(p.getObservationDate()))).min();
// min.getAsInt()獲取到具體的值
min.ifPresent(p -> {System.out.println(min.getAsInt());});
2.通過Collector處理處理最值,返回值為Optional類型
// Collectors.maxBy()獲取最大值
Optional<ReTrafficDetailExt> min = trafficDetailExtList.stream().collect(Collectors.minBy(Comparator.comparing(ReTrafficDetailExt::getYearAxleloadNum)));
3.通過直接使用max()方法
Optional<ReTrafficDetailExt> max = trafficDetailExtList.stream().max(Comparator.comparing(p -> DateUtil.getYear(stringToDate(p.getObservationDate()))));
跳過前面元素:Strream.skip()
@Test
public void skip(){
List<Integer> listNum = Stream.of(1,2,3,4,5,6).skip(2).collect(Collectors.toList());
listNum.forEach(System.out::println);
}
// 輸出:3
4
5
6
截取前幾個元素:Stream.limit()
@Test
public void limit(){
List<Integer> listNum = Stream.of(1,2,3,4,5,6).limit(2).collect(Collectors.toList());
listNum.forEach(System.out::println);
}
// 輸出:1
2
歸約操作:Stream.reduce()
@Test
public void reduce(){
// reduce()=>param1:初始化參數的初始值,其中p1為初始化參數,p2為集合元素,計算過程是p1=p1*p2;最終結果賦值給p1並返回p1,p1的初始值為1
Integer listNum = Stream.of(1, 2, 3, 4, 5, 6).reduce(1, (p1, p2) -> p1 * p2);
System.out.println(listNum);
}
// 輸出:720
了解Optional類,上面的一些操作也會返回Optional類型的值
Optional允許對象為空的容器,可使用ifPresent()或者ifPresent()判斷對象是否為null,如下使用of()方法創建Optional
Optional<String> optionalString = Optional.of("hello world");
if (optionalString.isPresent()) {
System.out.println(optionalString.get());
}
optionalString.ifPresent(p-> System.out.println(optionalString.get()));
使用ofNullable()創建
Optional<String> optionalString = Optional.ofNullable("hello world");
ofNullable()與of()區別在於ofNullable()允許對象為null,of()不允許對象為null,get()是獲取optional中的對象
Optional<String> optionalString = Optional.of(null);
if (optionalString.isPresent()) {
System.out.println(optionalString.get());
}
// 這里會報空指針錯誤
Optional<String> string = Optional.ofNullable(null); if (string .isPresent()) { System.out.println(optionalString.get()); } else { System.out.println("Optional對象為null"); } // 輸出:Optional對象為null
Optional的map()方法,將對象轉化為其它類型,如果結果為null則返回空對象的optional,否則返回對應的內容
Optional<String> optionalString = Optional.of("hello world");
System.out.println(optionalString.map(String::toUpperCase).orElse("other")); 等價於 System.out.println(optionalString.map(p -> p.toUpperCase()).orElse("other"));
// 輸出:HELLO WORLD
Optional<String> optionalString = Optional.empty();
System.out.println(optionalString.map(String::toUpperCase).orElse("other"));
// 輸出:other
optional的orElse()方法,是判斷optional的對象如果為null則返回orElse()方法的返回值,其返回值的類型必須與optional的泛型類型一致
Optional<String> optionalString = Optional.empty(); System.out.println(optionalString.orElse("other"))
// 輸出:other
optional的orElseGet()方法與orElse()用法差不多,只不過orElseGet()的參數是Supplier接口,可以傳入一個方法,看例子
private String doSomething(){
return "hello new world";
}
Optional<String> optionalString = Optional.empty();
System.out.println(optionalString.orElseGet(() -> doSomething()));
// 輸出:hello new world
lambda還可以支持接口匿名簡寫,但是只能支持函數式接口(只能有一個抽象方法),先定義一個接口
package test;
public interface MyLambda {
void doSomething();
}
函數式接口中可以包含靜態方法和默認方法,可以增加注解@FunctionalInterface
package test;
@FunctionalInterface
public interface MyLambda {
// 只能包含一個抽象方法
void doSomething();
// 可以包含靜態方法
static void staticFunction() {
System.out.println("----->static function");
}
// 可以包含默認方法
default void defaultFunction(){
System.out.println("--------->default function");
}
}
函數式接口測試效果
package test; import org.junit.Test; public class ControllerTest{ @Test public void testId(){ testLambda(() -> System.out.println("--------->lambda function")); // 實現MyLambda接口,並重寫doSomething()方法 } private void testLambda(MyLambda lambda) { lambda.doSomething(); System.out.println("----------->testLambda function"); } }
// 輸出:--------->lambda function
----------->testLambda function
流合並Stream.flatMap()
@org.junit.Test
public void controllerTest() throws Exception {
VcUser user1 = new VcUser();
user1.setUserName("demo1");
user1.setRealName("zhangsan");
VcUser user2 = new VcUser();
user2.setUserName("demo2");
user2.setRealName("lisi");
List<VcUser> list = new ArrayList<>();
list.add(user1);
list.add(user2);
List<String> dd = list.stream().flatMap(p -> Stream.of(p.getUserName(), p.getRealName())).collect(Collectors.toList());
dd.forEach(System.out::println);
}
java8四大函數接口
——Function(函數):接受一個參數,返回一個值
private int addOne(int i) {
return i + 1;
}
private int opera(int i, Function<Integer, Integer> function) {
return function.apply(i);
}
@org.junit.Test
public void controllerTest(){
// Function參數可以是表達式,但是必須返回一個值
int y = opera(1, p -> addOne(p));
System.out.println("y = " + y);
int x = opera(4, p -> p + 1); // int x = opera(4, p -> {int x = 6; retrun x + p;} => 輸出:10
System.out.println("x = " + x);
}
// 輸出:y = 2
x = 5
——Consumer(消費者):接收一個參數,沒有返回值
private void consumer(VcUser vcUser, Consumer<VcUser> consumer) {
consumer.accept(vcUser);
}
@org.junit.Test public void controllerTest() { VcUser vcUser = new VcUser(); vcUser.setRealName("demo");
// Consumer參數可以是表達式,但是不能有返回值 consumer(vcUser, p -> System.out.println("user = " + p.getRealName())); }
// 輸出:user = demo
——Supplier(提供者):不接收參數,返回一個值
private VcUser supplier(Supplier<VcUser> supplier) {
return supplier.get();
}
@org.junit.Test public void controllerTest() {
// Supplier參數不接收參數,其中參數可以是表達式,但是必須有返回值 VcUser user = supplier(() -> { VcUser vcUser2 = new VcUser(); vcUser2.setRealName("demo"); return vcUser2; }); System.out.println("realName = " + user.getRealName()); }
// 輸出:realName = demo
——Predicate(謂語接口):接收一個參數,返回一個Boolean類型值,是一個特殊的Function(相當於Function<T,Boolean>)
private Boolean predicate(Integer i, Predicate<Integer> predicate) {
return predicate.test(i);
}
@org.junit.Test
public void controllerTest() {
// Predicate接口接收一個參數,並返回boolean類型的返回值,predicate參數可以是表達式
boolean is = predicate(24, p -> {
int i = 1;
return p % 2 == i;
});
System.out.println("is = " + is);
}
// 輸出:is = false
其它的接口都是上面四中接口的變種,一般是在限制參數類型,數量,具體如下信息:
——參數類型限制的接口
IntPredicate,LongPredicate, DoublePredicate,這幾個接口,都是在基於Predicate接口的,不同的就是
他們的泛型類型分別變成了Integer,Long,Double,IntConsumer,LongConsumer, DoubleConsumer比如這幾
個,對應的就是Consumer,Consumer,Consumer,其余的是一樣的道理,就不再舉例子了。
——返回值類型
和上面類似,只是命名的規則上多了一個To,例如IntToDoubleFunction,IntToLongFunction, 很明顯就是對應的
Funtion;參數限制與返回值限制的命名唯一不同就是To,簡單來說,前面不帶To的都是參數類型限制,帶To的是返回值類
型限制,對於沒有參數的函數接口,那顯而易見只可能是對返回值作限制。例如LongFunction就相當於Function<Long,R>
而多了一個To的ToLongFunction就相當於Function<T,Long>,也就是對返回值類型作了限制。
——數量限制接口
有些接口需要接受兩名參數,此類接口的所有名字前面都是附加上Bi,是Binary的縮寫,開頭也介紹過這個單詞了,是二元的
意思,例如BiPredicate,BiFcuntion等等,而由於java沒有多返回值的設定,所以Bi指的都是參數為兩個。
——Operator接口
此類接口只有2個分別是UnaryOperator 一元操作符接口,與BinaryOperator二元操作符接口,這類接口屬於Function接口的簡寫,
他們只有一個泛型參數,意思是Funtion的參數與返回值類型相同,一般多用於操作計算,計算 a + b的BiFcuntion如果限制條件為
Integer的話 往往要這么寫BiFunction<integer,integer,integer> 前2個泛型代表參數,最后一個代表返回值,看起來似乎是
有點繁重了,這個時候就可以用BinaryOperator來代替了。
接口函數如下
java尾遞歸,在java編程中,如果使用遞歸操作數據,容易出現棧溢出的情況,這是因為每次調用下一輪遞歸的時候在棧都需要保存之前的變量,在沒有到底之前,那些中間變量會一直保存着,因此每一次遞歸都需要開辟一個新的棧空間。使用java的lambda表達式可優化此問題。原理利用java8的惰性求值,使用一個變量,每次遞歸時都保存上一輪的結果,到了最后輪時,直接取出結果即可。
——設計一個函數式接口,用來控制遞歸的狀態
public interface Tail<T> {
// 用來連接每次一次遞歸的棧幀,返回下一個棧幀
Tail<T> apply();
// 判斷遞歸是否結束,默認遞歸還沒有結束,具體判斷可在工具類中重寫
default boolean isFinished() {
return false;
}
// 遞歸結束時獲得最終值,這里默認是還沒有結束,拋出異常,具體返回值在工具類中重寫
default T getResult() throws BusinessException {
throw new BusinessException("遞歸還沒有結束...");
}
// java8的及早求值,執行完一系列的遞歸后,因為棧幀只有一個,所以使用findFirst()獲取最終的棧幀
default T invoke() throws BusinessException {
return Stream.iterate(this, Tail::apply)
.filter(Tail::isFinished)
.findFirst()
.get()
.getResult();
}
}
——尾遞歸的工具類
public class TailInvoke {
public static <T> Tail<T> callBack(Tail<T> next) {
return next;
}
private static <T> Tail<T> done(T value) {
return new Tail<T>() {
@Override
public Tail<T> apply() {
try {
throw new BusinessException("遞歸已經結束...");
} catch (BusinessException e) {
e.printStackTrace();
}
return null;
}
@Override
public boolean isFinished() {
return true;
}
@Override
public T getResult() {
return value;
}
};
}
}
——例子:1 + 2 + 3 + ... + 100
public static Tail<Long> calculate(final long factory, final long next) {
if (1 == next) {
return TailInvoke.done(factory + next);
} else {
return TailInvoke.callBack(() -> calculate(factory + next, next - 1)); // 調用invoke()時都重寫了apply()方法,並將上一次的結果值保存在factory中,每次都將factory傳到下一遞歸中
}
}
@org.junit.Test
public void controllerTest() throws Exception {
long y = TailInvoke.calculate(0, 100).invoke();
System.out.println("y = " + y);
}
// 輸出:5050
使用經驗:
1、lambda中的雙冒號“::”操作都可以用箭頭“->”代替,如User::getName 等價於 user->user.getName();
2、使用箭頭操作相對靈活一點,可以改變返回值類型,如user.getName().getBytes()返回的是byte[];
3、"->"后面可以跟着“{}”代碼塊,如user->{return user.getName();};
4、從外面傳到lambda方法體時,必須是final型,否則編譯不過去;