如何善用函数式接口简化云服务业务代码开发

举报
我还没用力 发表于 2020/12/31 16:32:49 2020/12/31
【摘要】 在云服务业务开发中,善于使用代码新特性,往往能让开发效率大大提升,这里简单介绍下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继承的方法

/**
 * 用户身份认证标记接口
 */
@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联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

      • 静态方法引用

      • 实例方法引用

      • 构造方法引用

        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());
    【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
    • 点赞
    • 收藏
    • 关注作者

    评论(0

    0/1000
    抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。