• JAVA高级——lambda


    JAVA高级——lambda

        • 1、是什么是函数是接口?
        • 3、在哪里以及如何使用Lambda
      • lambda实现:环绕执行模式
        • 2、使用函数时接口传递行为
        • 3、执行一个行为并转为lambda
        • 2、重构使用lambda的问题
          • 2.1 重构lambda找不到情况
          • 2.2 从lambda表达式到方法引用的转换
        • 3、使用lambda重构设计模式

    lambda基础

    1、是什么是函数是接口?

    函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

    :arrow_double_up: 上图就是一个函数是接口样式;

    2、lambda的特点

    • 匿名————没有明确的名称:写的少想的多(就是不直观);
    • 函数————它不像普通的方法有特定的类。但是方法一样,lambda有方法一切的特性;
    • 传递————lambda表达式可以作为参数传递给方法或者存储在变量中;
    • 简洁————无需使用匿名类,可以省掉很多模板;

    下面是一个使用了lambda简化的代码模板:

    public void test03(){
     
        // 原来
        Consumer consumer = new Consumer() {
     
            @Override
            public void accept(Person person) {
     
                System.out.println(person);
            }
        };
        // now 1
        Consumer consumer1 = person -> System.out.println(person);
        // now 2
        Consumer consumer2 = System.out::println;
        
    }

    3、在哪里以及如何使用Lambda

    当接口是函数式接口时,可以使用;

    lambda实现:环绕执行模式

    这里我将述说如何自定义一个template去实现我们自己想要的函数格式;

    代码样例:

    // 这一行代码只能阅读固定的文件
        public String BufferReadProcessor1() throws IOException {
     
            try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
     
                return br.readLine();
            }
        }

    1、行为参数化

    如果我一次性想读取多个行文件呢?

    String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());

    2、使用函数时接口传递行为

    package com.xiao.java_base.lambdas.demo01;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    
    @FunctionalInterface
    public interface BufferReaderProcessor {
     
        String process(BufferedReader bufferedReader) throws IOException;
    }

    现在就可以把这个接口作为新的 ProcessFile 方法参数了

    3、执行一个行为并转为lambda

    @Test
    public void test01() throws IOException {
     
       
        BufferReaderProcessor bufferReaderProcessor = new BufferReaderProcessor() {
     
            @Override
            public String process(BufferedReader bufferedReader) throws IOException {
     
                return bufferedReader.readLine() + bufferedReader.readLine();
            }
        };
       
        // 使用lambda进行优化
        BufferReaderProcessor bufferReaderProcessor = bufferedReader ->
                bufferedReader.readLine() + bufferedReader.readLine();
        // 执行process进行调用
        bufferReaderProcessor.process(new BufferedReader(new FileReader("data.txt")));
    }

    这里就说明了如果创建函数是接口、使用、合并的过程;

    使用函数式接口

    1、常用的函数型接口

    Predicate:里面有一个 test() 方法,设置 Predicate函数接口的实现方法,通过传递参数,让其进行判断是否符合函数方法中的实现方法,如果符合就获取,否者就不进行操作;

    public static  List filter(List list, Predicate p) {
     
        List results = new ArrayList<>();
        for (T t : list) {
     
            if (p.test(t)) {
     
                results.add(t);
            }
        }
        return results;
    }
    
    public static void main(String[] args) {
     
        Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty();
        List eeqwer = filter(Arrays.asList("args","123"), nonEmptyStringPredicate);
        System.out.println(eeqwer);
    }

    Function:创造型函数式接口

    public class FunctionS {
     
    
        public static  List map(List list, Function f) {
     
            ArrayList result = new ArrayList<>();
            for (T t : list) {
     
                result.add(f.apply(t));
            }
            return result;
        }
    
        public static void main(String[] args) {
     
            List map = map(Arrays.asList("lambda", "in", "action"), String::length);
            // 传统写法
            //List map = map(Arrays.asList("lambda", "in", "action"), new Function() {
     
            //    @Override
            //    public Integer apply(String s) {
     
            //        return s.length();
            //    }
            //});
            System.out.println(map);
        }
    }

    Consume:消费型函数式接口,传入的数据源在接口中被消费,没有返回值;

    public class ConsumeS {
     
    
        public static  void consumes(List list, Consumer consumer){
     
            for (T t : list) {
     
                consumer.accept(t);
            }
        }
    
        public static void main(String[] args) {
     
            consumes(Arrays.asList("args","123","eeeeer"),System.out::println);
        }
        /*
         运行结果:
                args
                123
                eeeeer
        */
    
    }

    注意:我们还需要考虑一个问题,就是基本数据类型的 装箱 和 拆箱 功能,这些会极大的影响 java的运行效率

    所以java8 就自带了一个专门不用拆箱和装箱的类:

    比如: IntPredicate 、 DoublePredicate 。。。

    ​ 一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的数据类型,那么就是可以免于上述性能问题;

    这里是 int 数据类型 :arrow_double_down:

    这里是 double 基本数据类型 :arrow_double_down:

    2、重构使用lambda的问题

    2.1 重构lambda找不到情况

    接下来这个代码使用lambda的情况下都是可以成立,但是这个到底需要找那个呢?

    public static void doSomething(Runnable runnable){
     
            runnable.run();
        }
        public static void doSomething(Task t){
     
            t.execute();
        }
    
        public static void main(String[] args) {
     
            // 这个没问题
            doSomething(new Task() {
     
                @Override
                public void execute() {
     
                    System.out.println("SUCCESS!");
                }
            });
    
            // 查看错误
            doSomething(() -> System.out.println("AAA"));
        }

    此时IDEA给出了编译报错的问题,所以我们不必担心;但是如何解决这个问题呢?

    可以对Task尝试使用显式的 类型转换 来解决这种模棱两可的情况:

    2.2 从lambda表达式到方法引用的转换

    Lambda表达式非常适用于需要传递代码片段的场景。不过,为了改善代码的可读性,也请尽量使用方法引用。因为方法名往往能更直观地表达代码的意图。

    Map> dishesByCaloricLevel =
        menu.stream()
            .collect(
                groupingBy(dish -> {
     
                  if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                  else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                  else return CaloricLevel.FAT;
                }));

    那么我们可以将 lambda 表达式中的信息进行抽离,直接使用下面这种方式:

    // src/com/xiao/java_base/lambdas/demo03
    List dishes = Arrays.asList(
                    new Dish("土豆片", null, 200),
                    new Dish("油炸土豆", null, 500),
                    new Dish("油油炸土豆片", null, 800),
                    new Dish("油油油炸土豆片", null, 1000)
            );
            Map> collect =
                    dishes.stream().collect(groupingBy(Dish::getCaloricLevel));
            collect.forEach((caloricLevel,list) ->{
     
                System.out.println("-------------------"+caloricLevel.name()+"-------------------");
                list.forEach(System.out::print);
                System.out.println("---------------------------------------------------------");
            });

    但是同时需要修改 Dish 中的 getCaloricLevel 方法

    public CaloricLevel getCaloricLevel() {
     
        if (this.getCalories() <= 400) return CaloricLevel.DIET;
        else if (this.getCalories() <= 700) return CaloricLevel.NORMAL;
        else  return CaloricLevel.FAT;
    }

    所以现在思路就很明确了:

    ​ lambda表达式 直接通过方法引用 的方式获取到对应的 能量等级枚举值 。

    看一下获取的分组值:

    3、使用lambda重构设计模式

    3.1 重构策略模式

    什么是策略模式呢?

    一个代表某个算法的接口(Strategy接口)。

    一个或多个该接口的具体实现,它们代表了算法的多种实现(比如,实体类ConcreteStrategyA或者ConcreteStrategyB)。

    一个或多个使用策略对象的客户。

    假设你希望验证输入的内容是否根据标准进行了恰当的格式化(比如只包含小写字母或数字)。你可以从定义一个验证文本(以String的形式表示)的接口入手:

    public interface ValidationStrategy {
     
        boolean execute(String s);
    }

    其次,你定义了该接口的一个或多个具体实现:

    public class IsAllLowerCase implements ValidationStrategy {
     
        public boolean execute(String s){
     
            return s.matches("[a-z]+");
        }
    }
    public class IsNumeric implements ValidationStrategy {
     
        public boolean execute(String s){
     
            return s.matches("\\d+");
        }
    }

    之后,你就可以在你的程序中使用这些略有差异的验证策略了:

    public class Validator{
     
        private final ValidationStrategy strategy;
        public Validator(ValidationStrategy v){
     
            this.strategy = v;
        }
        public boolean validate(String s){
     
            return strategy.execute(s);
        }
    }
    Validator numericValidator = new Validator(new IsNumeric());
    boolean b1 = numericValidator.validate("aaaa");       ←---- 返回false
    Validator lowerCaseValidator = new Validator(new IsAllLowerCase ());
    boolean b2 = lowerCaseValidator.validate("bbbb");       ←---- 返回true

    使用lambda优化的方式:

    1. 创建函数式接口:
    public interface Validator {
     
        public boolean validate(String t);
    }
    1. 创建函数方法模板
    public static boolean validate(String s,Validator validator){
        return validator.validate(s);
    }
    1. 调用方法
    boolean bo1 = validate("aaa", s-> s.matches("[a-z]+"));
    System.out.println(bo1);
    boolean bo2 = validate("bbb", s-> s.matches("\\\\d+"));
    System.out.println(bo2);

    3.2 模板方法

    就如同下面代码,虽然说是策略模式但是,也是属于lambda的模板方法

    如下业务场景:

    需要编写一个简单的在线银行应用。通常,用户需要输入 一个用户账户 ,之后应用才能从银行的数据库中 得到用户的详细信息 ,最终 完成一些让用户满意的操作 。不同分行的在线银行应用让客户满意的 方式可能略有不同 ,比如给客户的账户发放红利,或者仅仅是少发送一些推广文件。

    abstract class OnlineBanking {
     
        public void processCustomer(int id){
     
            Customer c = Database.getCustomerWithId(id);
            makeCustomerHappy(c);
        }
        abstract void makeCustomerHappy(Customer c);
    }

    这里只是一个代码样式,其思想是和银行代码一样的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0szli7P0-1637059492930)(E:/typora%E7%AC%94%E8%AE%B0/typoraImg/image-20211115220437927-16369855413413.png)]

    public static void main(String[] args) {
     
     boolean bo1 = validate("aaa", s-> s.matches("[a-z]+"));
     System.out.println(bo1);
     boolean bo2 = validate("bbb", s-> s.matches("\\\\d+"));
     System.out.println(bo2);
    }

    此时如果有其他银行要使用这个模板,就需要继承这个类,实现 OnlineBanking 这个类,但是改用lambda表达式就可以使用下面这个模板。

    public void processCustomer(int id, Consumer makeCustomerHappy){
     
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy.accept(c);
    }

    现在,你可以很方便地通过传递Lambda表达式,直接插入不同的行为,

    new OnlineBankingLambda().processCustomer(1337, (Customer c) ->
        System.out.println("Hello " + c.getName());
  • 相关阅读:
    Java修仙之高级功法篇->MybatisPlus
    SpringBoot日志相关进阶配置
    解决mysql报错this is incompatible with DISTINCT
    01、Windows 常用的 DOS命令:
    [云原生] [kubernetes] Kubesphere下的DevOps / (CI/CD)
    什么是分布式软件系统
    JavaScript系列:JS实现复制粘贴文字以及图片
    关于 CTF 中 php 考点与绕过那些事的总结
    网站数据加密之Hook通用方案
    前缀和【一维前缀和与二维前缀和】
  • 原文地址:https://blog.csdn.net/lt_xiaodou/article/details/126119429