• 简化业务代码开发:看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");
        }
  • 相关阅读:
    网页中添加下划线的方法汇总及优缺点
    git备注
    微信小程序封装年月日时分组件
    微信小程序底部弹窗动画
    微信小程序返回上一页的方法并传参
    微信小程序组件封装
    taro中子父传值
    taro初识一
    reactjs中使用高德地图计算两个经纬度之间的距离
    vue中使用scss
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/14291967.html
Copyright © 2020-2023  润新知