== lambda表達式 ==


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型,否則編譯不過去;

 



 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM