写点什么

简化业务代码开发:看 Lambda 表达式如何将代码封装为数据

发布于: 2021 年 01 月 18 日

摘要:在云服务业务开发中,善于使用代码新特性,往往能让开发效率大大提升,这里简单介绍下 lambad 表达式及函数式接口特性。


在云服务业务开发中,善于使用代码新特性,往往能让开发效率大大提升,这里简单介绍下 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 继承的方法

/** * 用户身份认证标记接口 */@FunctionalInterfacepublic 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

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 表达式变量捕获

总结:Lambda 表达式优化了匿名内部类类型中的 this 关键字,不再单独建立对象作用域,表达式本身就是所属类型对象的一部分,在语法语义上使用更加简洁。

类型检查

对于语法相同的表达式,Jvm 在运行的过程中,在底层通过解释及重构,进行类型的自动推导。

  • 表达式类型检查

  • 参数类型检查


方法重载

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


1. 声明一个私有静态方法,对 Lambda 表达式做一个具体的方法实现

2. 声明一个 final 内部类型并实现接口

3. 在实现接口后的重写方法中利用外部类调用该私有静态方法

 4.方法引用

方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象(实例)的方法或构造器。与 lambda 联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 静态方法引用

  • 实例方法引用

  • 构造方法引用



5.Stream

  • 新添加的 Stream 流—是一个来自数据源的元素队列并支持聚合操作。把真正的函数式编程风格引入到 Java 中。

  • 不存储数据,也不修改原始源。

  • Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

  • Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。

  • 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

  • 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

// 1. for 循环实现 List list = new ArrayList(); 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

来说,我需要将其形变为 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

,我需要将其按照 partitionFlag 分类,Collector 提供两种方法 partitioningBy()、groupingBy()。前者分成满足条件与不满足条件两类,后者可按条件分成若干类别的 Map。


  Map<Boolean, List<Table>> tablePartition = tableList          .stream().collect(Collectors.partitioningBy(item -> item.getPartitionFlag() == true));复制代码
复制代码
  • 有的时候,我们关注的不光是元素还有元素的个数,流处理可以再进行后期处理。

> 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

中 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 循环、 迭代器差不多,在处理对象(复杂数据类型)的情况下,并行性能最佳

本文分享自华为云社区《如何善用函数式接口简化云服务业务代码开发》,原文作者:luanzhen 。


点击关注,第一时间了解华为云新鲜技术~


发布于: 2021 年 01 月 18 日阅读数: 20
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
简化业务代码开发:看Lambda表达式如何将代码封装为数据