簡化業務代碼開發:看Lambda表達式如何將代碼封裝爲數據

摘要:在雲服務業務開發中,善於使用代碼新特性,往往能讓開發效率大大提升,這裏簡單介紹下lambad表達式及函數式接口特性。

1.Lambda 表達式

Lambda表達式也被稱爲箭頭函數、匿名函數、閉包。他允許把函數作爲一個方法的參數(函數作爲參數傳遞到方法中),體現出輕量級函數式編程思想。

爲什麼引入lambda?

Model Code as Data,編碼及數據,儘可能輕量級的將代碼封裝爲數據。

解決方案:接口&實現類(匿名內部類)

存在問題:語法冗餘,this關鍵字、變量捕獲、數據控制等

public static void main (String[] args){
    // 1. 傳統模式下,新線程的創建
    new Thread (new Runnable() {
        @Override 
        public void run() {
            System.out.println("threading..." + Thread.currentThread().getId())
        }
    }).start();
     // 2. lambda表達式優化線程模式
    new Thread(()->{
        System.out.println("lambda threading..." + Thread.currentThread().getId());
    })
 
}
  1. 不是解決未知問題的新技術
  2. 對現有問題的語義化優化
  3. 需要根據實際需求考慮性能問題

2.函數式接口(Functional Interface)

函數式接口就是Java類型系統中的接口,是隻包含一個抽象方法的特殊接口(可以有很多非抽象方法)。

語言化檢測註解:@FunctionalInterface 檢測合法性

java1.8支持接口內包含:抽象方法、默認接口方法、靜態接口方法、來自Object繼承的方法

/**
 * 用戶身份認證標記接口
 */
@FunctionalInterface
public interface IUserCredential {

    /**
     * 通過用戶賬號,驗證用戶身份信息的接口
     * @param username 要驗證的用戶賬號
     * @return 返回身份信息[系統管理員、用戶管理員、普通用戶]
     */
    String verifyUser(String username);
 
    default String getCredential(String username) {
        if ("admin".equals(username)) {
            return "admin + 系統管理員用戶";
        } else if("manager".equals(username)){
            return "manager + 用戶管理員用戶";
        } else {
            return "commons + 普通會員用戶";
        }
    }
    String toString();

    /**
     * 消息合法性驗證方法
     * @param msg 要驗證的消息
     * @return 返回驗證結果
     */
    static boolean verifyMessage(String msg) {
        if (msg != null) {
            return true;
        }
        return false;
    }
}

 

 // 匿名內部類,實現接口的抽象方法
        IUserCredential ic = new IUserCredential() {
            @Override
            public String verifyUser(String username) {
                return "admin".equals(username)?"管理員":"會員";
            }
        };
    // lambda表達式是函數式接口的一種簡單實現
        IUserCredential ic2 = (username) -> {
            return "admin".equals(username)?"lbd管理員": "lbd會員";
        };

JDK 1.8 之前已有的函數式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • more

JDK 1.8 新增加的函數接口:

  • java.util.function
  /*
        java.util.function提供了大量的函數式接口
        Predicate 接收參數T對象,返回一個boolean類型結果
        Consumer 接收參數T對象,沒有返回值
        Function 接收參數T對象,返回R對象
        Supplier 不接受任何參數,直接通過get()獲取指定類型的對象
        UnaryOperator 接口參數T對象,執行業務處理後,返回更新後的T對象
        BinaryOperator 接口接收兩個T對象,執行業務處理後,返回一個T對象
         */
        Predicate<String> pre = (String username) -> {
            return "admin".equals(username);
        };
        System.out.println(pre.test("manager"));

        Consumer<String> con = (String message) -> {
            System.out.println("要發送的消息:" + message);
        };
        con.accept("lambda expression.");

        Function<String, Integer> fun = (String gender) -> {
            return "male".equals(gender)?1:0;
        };
        System.out.println(fun.apply("male"));

        Supplier<String> sup = () -> {
            return UUID.randomUUID().toString();
        };
        System.out.println(sup.get());

        UnaryOperator<String> uo = (String img)-> {
            img += "[100x200]";
            return img;
        };
        System.out.println(uo.apply("原圖--"));

        BinaryOperator<Integer> bo = (Integer i1, Integer i2) -> {
            return i1 > i2? i1: i2;
        };
        System.out.println(bo.apply(12, 13));

3.lambda表達式的基本語法

基本語法

  • 聲明:就是和lambda表達式綁定的接口類型
  • 參數:包含在一對圓括號中,和綁定的接口中的抽象方法中的參數個數及順序一致。
  • 操作符:->
  • 執行代碼塊:包含在一對大括號中,出現在操作符號的右側

[接口聲明] = (參數) -> {執行代碼塊};

// 沒有參數,沒有返回值的lambda表達式綁定的接口
    interface ILambda1{
        void test();
    }

    // 帶有參數,沒有返回值的lambda表達式
    interface ILambda2{
        void test(String name, int age);
    }

    // 帶有參數,帶有返回值的lambda表達式
    interface ILambda3 {
        int test(int x, int y);
    }

 

ILambda1 i1 = () -> System.out.println("hello boys!");
        i1.test();

        ILambda2 i21 = ( n,  a) -> {
            System.out.println(n + "say: my year's old is " + a);
        };
        i21.test("jerry", 18);

        ILambda2 i22 = (n, a) -> 
            System.out.println(n + " 說:我今年" + a + "歲了.");
 
        i22.test("tom", 22);

        ILambda3 i3 = (x, y) -> {
            int z = x + y;
            return z;
        };
        System.out.println(i3.test(11, 22));

        ILambda3 i31 = (x, y) -> x + y;
        System.out.println(i31.test(100, 200));

總結:

  • lambda表達式,必須和接口進行綁定。
  • lambda表達式的參數,可以附帶0個到n個參數,括號中的參數類型可以不用指定,jvm在運行時,會自動根據綁定的抽象方法中的參數進行推導。
  • lambda表達式的返回值,如果代碼塊只有一行,並且沒有大括號,不用寫return關鍵字,單行代碼的執行結果,會自動返回。 如果添加了大括號,或者有多行代碼,必須通過return關鍵字返回執行結果。

變量捕獲

  • 匿名內部類型變量捕獲
  • lambda表達式變量捕獲
// 1. 匿名內部類型中對於變量的訪問
    String s1 = "全局變量";
    public void testInnerClass() {
        String s2 = "局部變量";

        new Thread(new Runnable() {
            String s3 = "內部變量";
            @Override
            public void run() {
                // 訪問全局變量
//              System.out.println(this.s1);// this關鍵字~表示是當前內部類型的對象(報錯)
                System.out.println(s1);

                System.out.println(s2);// 局部變量的訪問,不能對局部變量進行數據的修改final
//              s2 = "hello";

                System.out.println(s3);
                System.out.println(this.s3);
            }
        }).start();
    }

    // 2. lambda表達式變量捕獲
    public void testLambda() {
        String s2 = "局部變量lambda";

        new Thread(() -> {
            String s3 = "內部變量lambda";

            // 訪問全局變量
            // 不再建立對象域
            System.out.println(this.s1);// this關鍵字,表示的就是所屬方法所在類型的對象
            // 訪問局部變量
            System.out.println(s2);
//          s2 = "hello";// 不能進行數據修改,默認推導變量的修飾符:final
            System.out.println(s3);
            s3 = "labmda 內部變量直接修改";
            System.out.println(s3);
        }).start();
    }

總結:Lambda表達式優化了匿名內部類類型中的this關鍵字,不再單獨建立對象作用域,表達式本身就是所屬類型對象的一部分,在語法語義上使用更加簡潔。

類型檢查

對於語法相同的表達式,Jvm在運行的過程中,在底層通過解釋及重構,進行類型的自動推導。

  • 表達式類型檢查
  • 參數類型檢查
@FunctionalInterface
interface MyInterface<T, R> {
    R strategy (T t, R r);

}

 

public static void test(MyInterface<String, List> inter) {
        List<String> list = inter.strategy("hello", new ArrayList());
        System.out.println(list);
    }

   public static void main(String[] args) {
        test(new MyInterface<String, List>() {
            @Override
            public List strategy(String s, List list) {
                list.add(s);
                return list;
            }
        });

        test((x, y) -> {
            y.add(x);
            return y;
//            x.add(y);
//            return x;
        });
 
/*
(x,y)->{..} --> test(param) --> param==MyInterface --> lambda表達式-> MyInterface類型
這個就是對於lambda表達式的類型檢查,MyInterface接口就是lambda表達式的目標類型(target typing)

(x,y)->{..} --> MyInterface.strategy(T r, R r)--> MyInterface<String, List> inter
--> T==String R==List --> lambda--> (x, y) == strategy(T t , R r)--> x==T==String  y==R==List
*/

方法重載

interface Param1 {
        void outInfo(String info);
    }

    interface Param2 {
        void outInfo(String info);
    }
// 定義重載的方法
    public void lambdaMethod(Param1 param) {
        param.outInfo("hello param1 imooc!");
    }
    public void lambdaMethod(Param2 param) {
        param.outInfo("hello param2 imooc");
    }

 

test.lambdaMethod(new Param1() {
            @Override
            public void outInfo(String info) {
                System.out.println(info);
            }
        });

        test.lambdaMethod(new Param2() {
            @Override
            public void outInfo(String info) {
                System.out.println("------");
                System.out.println(info);
            }
        });

        /*
        lambda表達式存在類型檢查-> 自動推導lambda表達式的目標類型
        lambdaMethod() -> 方法 -> 重載方法
                -> Param1  函數式接口
                -> Param2  函數式接口
                調用方法-> 傳遞Lambda表達式-> 自動推導->
                    -> Param1 | Param2
         */
//           報錯 Ambigus Method call
//        test.lambdaMethod( (String info) -> {
//            System.out.println(info);
//        });

總結:出現方法重載的類型中參數都是函數式接口的情況,需使用匿名內部類實現替代lambda表達式。

底層構建原理

public class Test{
    public static void main(String args[]){
        ITest it = (message) -> System.out.println(message);
        it.markUp("lambda!");    
        // new Test$$Lambda$1().markUp("lambda");
    } 
}
interface ITest{
    void markUp(String msg);
}

javac Test.java

  • javap -p Test.class (javap反解析工具 -p顯示所有類與成員)
java -Djdk.internal.lambda.dumpProxyClasses Test

Compiled from "Test.java"
public class Test {
  public Test();
  public static void main(java.lang.String[]);
    private static void lambda$main$0(java.lang.String){
        System.out.println(message);
    };

}

 

finnal class Test$$Lambda$1 implements ITest{
    private Test$$Lambda$1(){
 
    }
        public void markUp(java.lang.String msg){
        Test.lambda$main$0(msg);
    }
}
      1. 聲明一個私有靜態方法,對Lambda表達式做一個具體的方法實現
      2. 聲明一個final內部類型並實現接口
      3. 在實現接口後的重寫方法中利用外部類調用該私有靜態方法

4.方法引用

方法引用提供了非常有用的語法,可以直接引用已有Java類或對象(實例)的方法或構造器。與lambda聯合使用,方法引用可以使語言的構造更緊湊簡潔,減少冗餘代碼。

      1. 靜態方法引用
      2. 實例方法引用
      3. 構造方法引用
class Person {
    private String name;
    private String gender;
    private int age;

    // 靜態方法引用
    public static int compareByAge(Person p1, Person p2) {
        return p1.getAge() - p2.getAge();
    }
}
class PersonUtil {
    // 增加一個實例方法
    public int comprareByName(Person p1, Person p2) {
        return p1.getName().hashCode() - p2.getName().hashCode();
    }
 
interface IPerson {
    // 抽象方法:通過指定類型的構造方法初始化對象數據
    Person initPerson(String name, String gender, int age);    


}

 

public static void main(String[] args) {
        List<Person> list = new ArrayList<Person>();
        list.add(new Person("shuke", "", 29));
        list.add(new Person("tom", "", 16));
        list.add(new Person("jerry", "", 20));
        list.add(new Person("beita", "", 30));

//      1.匿名內部類實現
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });
//      2.lambda表達式實現
        Collections.sort(list, (p1, p2) -> p1.getAge() - p2.getAge());
//      3.靜態方法引用實現
        Collections.sort(list, Person::compareByAge);

//      4.實例方法引用
        PersonUtil pu = new PersonUtil();
        Collections.sort(list, pu::comprareByName);
        list.forEach(System.out::println);
 
//        5.構造方法引用:綁定函數式接口
        IPerson ip = Person::new;
        Person person = p1.initPerson("tom", "", 18);
        System.out.println(person);
    }

5.Stream

    • 新添加的Stream流—是一個來自數據源的元素隊列並支持聚合操作。把真正的函數式編程風格引入到Java中。
    • 不存儲數據,也不修改原始源。
    • Stream 使用一種類似用 SQL 語句從數據庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。
    • Stream API可以極大提高Java程序員的生產力,讓程序員寫出高效率、乾淨、簡潔的代碼。
    • 這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。
    • 元素流在管道中經過中間操作(intermediate operation)的處理,最後由最終操作(terminal operation)得到前面處理的結果。
// 1. for循環實現 List<String> list = new ArrayList<String>(); for (String s : list) { if (s.length() > 3) { lista.add(s); } } System.out.println(lista);
// 2. 迭代器實現
List<String> listb = new ArrayList<>();
Iterator<String> it = list.iterator();
while(it.hasNext()) {
    String s = it.next();
    if(s.length() > 3) {
        listb.add(s);
    }
}
System.out.println(listb);

// 3. stream實現
List listc = list.stream().filter(s->s.length()>3)
    .collect(Collectors.toList());
System.out.println(listc);

幾者關係

    • lambda表達式是傳統方法的語法糖,簡化並且改造傳統內部類實現設計方案的另一種實現模式。
    • 方法引用又是lambda基礎上的語法糖,和Stream沒有關係,簡化方法調用的。
    • Stream是針對數據和集合的強化優化操作,可以和lambda結合起來簡化編碼過程。

常見API介紹

1.聚合操作

2.Stream的處理流程

    • 數據源
    • 數據轉換[可一到多次轉換]
    • 獲取結果

3.獲取Stream對象

    • 從集合或者數組中獲取

Collection.stream(), 如list.stream()

Collection.parallelstream(), 獲得支持併發處理的流

Arrays.stream(T t)

    • BufferReader

BufferReader.lines()-> stream()

    • 靜態工廠

java.util.stream.IntStream.range()..

java.nio.file.Files.walk()..

    • 自定構建

java.util.Spliterator

    • 更多的方式

Random.ints()

Pattern.spiltAsStream()..

4.中間操作API{intermediate}:

    • 操作結果是一個Stream對象,所以中間操作可有一個或多個連續的中間操作,需要注意的是中間操作只記錄操作方式,不做具體執行,直到結束操作發生時,才做數據的最終執行。
    • 中間操作就是業務邏輯處理
    • 操作過程分爲有狀態和無狀態

無狀態:即處理數據時,不受前置中間操作的影響

    • map/filter/peek/parallel/sequential/unordered
    • 有狀態:即處理數據時,受前置中間操作的影響
    • distant/sorted/limit/skip

5.終結操作|結束操作{Terminal}

一個steam對象只能有一個Terminal操作。這個操作不可逆,一旦發生,就會真實處理數據生成對應結果

    • 非短路操作:當前的Stream對象必須處理完集合中所有的數據,才能得到處理結果

forEach/forEachOrdered/toArray/reduce/collect/min/max/count/iterator

短路操作:當前的Stream對象在處理過程中,一旦滿足某個條件,就可以得到結果

anyMatch/AllMatch/noneMatch/findfirst/findAny等

short-circuiting : 在無限大的stream 中返回有限大的stream 需要包含短路操作是有必要的

Stream轉換

 // 1. 批量數據 -> Stream對象
        // 多個數據
        Stream stream = Stream.of("admin", "tom", "jerry");

        // 數組
        String [] strArrays = new String[] {"xueqi", "biyao"};
        Stream stream2 = Arrays.stream(strArrays);

        // 列表
        List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        Stream stream3 = list.stream();

        // 集合
        Set<String> set = new HashSet<>();
        set.add("aaa");
        set.add("bbb");
        set.add("ccc");
        Stream stream4 = set.stream();

        // Map
        Map<String, Integer> map = new HashMap<>();
        map.put("tom", 1000);
        map.put("jerry", 1200);
        map.put("shuke", 1000);
        Stream stream5 = map.entrySet().stream();

    //2. Stream對象對於基本數據類型的功能封裝
        //int / long / double
        IntStream.of(new int[] {10, 20, 30}).forEach(System.out::println); //只做一次拆箱裝箱
        IntStream.range(1, 5).forEach(System.out::println);
        IntStream.rangeClosed(1, 5).forEach(System.out::println);

   // 3. Stream對象 --> 轉換得到指定的數據類型
        // 數組
        Object [] objx = stream.toArray(String[]::new);

        // 字符串
        String str = stream.collect(Collectors.joining()).toString();
        System.out.println(str);

        // 列表
        //List<String> listx = (List<String>) stream.collect(Collectors.toList());
        System.out.println(listx);

        // 集合
        //Set<String> setx = (Set<String>) stream.collect(Collectors.toSet());
        System.out.println(setx);

        // Map
        //Map<String, String> mapx = (Map<String, String>)                             stream.collect(Collectors.toMap(x->x, y->"value:"+y));



        System.out.println(mapx);

Stream常見操作

// Stream中常見的API操作
        List<String> accountList = new ArrayList<>();
        accountList.add("tom");
        accountList.add("jerry");
        accountList.add("apha");
        accountList.add("beta");
        accountList.add("shuke");

        // map() 中間操作,map()方法接收一個Functional接口
        accountList = accountList.stream().map(x->"name:" + x).collect(Collectors.toList());

        // filter() 添加過濾條件,過濾符合條件的用戶
        accountList = accountList.stream().filter(x-> x.length() > 3).collect(Collectors.toList());

        // forEach 增強型循環
        accountList.forEach(x-> System.out.println("forEach->" + x));

        // peek() 中間操作,迭代數據完成數據的依次處理過程
        accountList.stream()
                .peek(x -> System.out.println("peek 1: " + x))
                .peek(x -> System.out.println("peek 2:" + x))
                .forEach(System.out::println);// 合併多個過程 迭代只發生一次

        accountList.forEach(System.out::println);

        // Stream中對於數字運算的支持
        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() 中間操作,有狀態,排序
        // max() 獲取最大值
        Optional optional = intList.stream().max((x, y)-> x-y);
        System.out.println(optional.get());
        // min() 獲取最小值

        // reduce() 合併處理數據
        Optional optional2 = intList.stream().reduce((sum, x)-> sum + x);
        System.out.println(optional2.get());

6.案例

問題一:將實例List轉化爲Map

對於List<Table>來說,我需要將其形變爲Map<Table.id,Table>,用如下流處理代碼

//Table類
public class DmTable {
    private Integer id;

    private String tableName;

    private String tableComment;

    private Integer datasourceId;

    private Integer directoryId;

    private Boolean partitionFlag;
 
    private Integer columnNum;
    // ......
}
tableMap=TableList.stream().collect(Collectors.toMap(Table::getId, b -> b);
// 等效於
tableMap=TableList.stream().collect(Collectors.toMap(Table::getId, Function.identity()));// 靜態方法 實現 return t -> t;

問題二:將集合分成若干類別

使用問題一中的Table類,對於List<Table>,我需要將其按照partitionFlag分類,Collector提供兩種方法partitioningBy()、groupingBy()。前者分成滿足條件與不滿足條件兩類,後者可按條件分成若干類別的Map。

Map<Boolean, List<Table>> tablePartition = tableList
        .stream().collect(Collectors.partitioningBy(item -> item.getPartitionFlag() == true));

有的時候,我們關注的不光是元素還有元素的個數,流處理可以再進行後期處理。

Map<Boolean, List<Table>> tablePartition = tableList


        .stream().collect(Collectors.partitioningBy(item -> item.getPartitionFlag() == true,Collectors.counting()));

可輸出符合要求的個數。

groupingBy()可對字符串長度分組。

List<String> strings=Arrays.asList(“this”,”is”,”a”,”test”);
Map<Integer, List<String>> stringsMap = strings
        .stream().collect(Collectors.groupingBy(String::length);

結果輸出多分類的map,key值爲字符串長度。

注意:如果是從數據庫獲取數據,務必將分組操作放在數據庫中執行,java8新增方法只適合處理內存中的數據。

問題三:從list中得到某個特定的對象

獲得List<Table>中columnNum最多的table對象

tableList.stream().sorted(comparingInt(Table::getColumnNum)).collect(Collectors.toList()).get(tableList.size() - 1);

添加中間操作reversed() 可獲取最小columnNum的對象

問題四: 得到Map<Table,Table.columnNum>中最大columnNum的table

 List<Map.Entry<Table, Integer>> list = new ArrayList(tableMap.entrySet());
Collections.sort(list, (o1, o2) -> (o2.getValue() - o1.getValue()));
list.get(0).getKey();

7.性能與安全

  • 串行Stream的性能小於傳統的for循環、 迭代器
  • 並行Stream的性能與傳統的for循環、 迭代器差不多,在處理對象(複雜數據類型)的情況下,並行性能最佳
// 整數列表
        List<Integer> lists = new ArrayList<Integer>();
        // 增加數據
        for (int i = 0; i < 1000; i++){
            lists.add(i);
        }

        // 串行Stream
        List<Integer> list2 = new ArrayList<>();
        lists.stream().forEach(x->list2.add(x));
        System.out.println(lists.size());
        System.out.println(list2.size());
        // 並行Stream  線程不安全 丟失
        List<Integer> list3 = new ArrayList<>();
        lists.parallelStream().forEach(x-> list3.add(x));
        System.out.println(list3.size());
          // collect 當並行執行時可以實例化、填充和合並多箇中間結果,以保持可變數據結構的隔離
        List<Integer> list4 = lists.parallelStream().collect(Collectors.toList());
        System.out.println(list4.size());
本文分享自華爲雲社區《如何善用函數式接口簡化雲服務業務代碼開發》,原文作者:luanzhen 。

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

interface Param1 {
        void outInfo(String info);
    }

    interface Param2 {
        void outInfo(String info);
    }
// 定義重載的方法
    public void lambdaMethod(Param1 param) {
        param.outInfo("hello param1 imooc!");
    }
    public void lambdaMethod(Param2 param) {
        param.outInfo("hello param2 imooc");
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章