首页 > 编程语言 >Java --- Lambda

Java --- Lambda

时间:2022-08-27 19:45:51浏览次数:69  
标签:return String void --- static test Java public Lambda

学习Lambda的理由

  1. 绝大多数公司代码的主流风格。
  2. 大数据量下处理集合效率高,优秀的高并发解决。
  3. 代码的可读性增强。
  4. 消灭嵌套地狱。>形状的if或者for再也不用写了。

为了了解Lambda表达式,我们必须了解什么是函数式接口,这是Lambda表达式得以实现的依据。

在java中,函数式接口指注解了@FunctionalInterface(非必须)的接口。
函数式接口具有一下特性:

  • 接口中有且仅有一个抽象方法(必须)。

除却以上性质与普通的接口没有区别。

由以上定义,那么问题来了,为什么要使用函数式接口?

让我们先看一个接口的实现案例。

// 传统的接口实现方式
interface MyInterface {
    void test();
}
class MyInterfaceImpl implements MyInterface{
    public void test() {
        // 各种处理
    }
}
class Client {
    public void process() {
        MyInterface mif = new MyInterfaceImpl();
        mif.test();
    }
}

目前看上去我们的实现是没有什么问题的,无外乎定义一个接口,然后定义接口的实现类,在客户端调用的过程中父类引用子类对象。。。
但是,如果这么定义的话,意味着实现类是可以复用的。实际上应该反过来思考,就因为需要复用实现类,我们才这么定义接口与实现。

有基础的小伙伴肯定反应过来我想说的问题:如果一个接口的实现根据业务需求在项目中指调用两三次,并且每次的实现方式还不同,我需要为了这三个不一样的实现分别写三个不同的实现类吗?

其实没有必要,由此引出接下来的内容:匿名内部类实现接口。

// 匿名内部类的接口实现方式
interface MyInterface {
    void test();
}
class Client {
    public void process() {
        MyInterface mif = new MyInterface() {
            @Override
            public void test() {
                // 各种处理
            }
        };
        mif.test();
    }
    /*
    	其实不难看出,对于只有一个抽象方法的接口,匿名内部类的实现方式比较冗余
    	首先new MyInterface(){}并不是非写不可,
    	@Override,public,void,test也同样。
    	可能有同学看到这里已经蒙了:这人在说啥?我接下来解释。
    	接口只有一个抽象方法也就是说没有重载,所以我们通过明确写出方法的参数列(类型也不需要,可推定)
    	已经方法体就可以确定重写的是哪个方法,也就省去了@Override public void test。
    	又因为变量声明了一个接口类型所以就知道需要实例化的对象接口也就省去了new MyInterface(){}
    	至此代码变为了以下这种形式:
    	MyInterface mif = () -> { /* 各种处理 */ };
     */
}

虽然上述的实现方式可以让我们比较方便的实现需求频繁变动且复用需求低的接口,但人类是非常懒的,为了让我们实现上述功能的同时写更少的代码,技术人员发明了lambda[1]表达式。

接下来,我们来看看上述实现使用lambda的效果。

// lambda的接口实现方式
interface MyInterface {
    void test();
}
class Client {
    public void process() {
        MyInterface mif = () -> { /* 各种处理 */ };
        mif.test();
    }
}

代码量肉眼可见的减少,接下来解释为什么可以这么写。

MyInterface mif = () -> { /* 各种处理 */ };
  • ():接口抽象方法的形式参数。
  • ->:箭头标记,固定写法。
  • {}:实现的方法体。

lambda的省略规则

// 方法参数类型可以省略
(a, b) -> { /* 各种处理 */ };
// 方法体只有一行代码时,{}、;、return可省略(必须同时省略)
(a, b) -> /* 各种处理 */;
// 方法参数只有一个时,()可以省略
a -> /* 各种处理 */;
// 方法无参时,()不可省略
() -> /* 各种处理 */;

仔细观察上述的所有写法可以了解到lambda表达式与传统匿名内部类的实现方式有一个本质的区别:有且仅有一个实现的抽象方法。这也是为什么函数式接口只能定义一个抽象方法的原因。

补充

  1. lambda基本可以认为是匿名内部类的语法糖[2](不太准确)
    但lambda与匿名内部类在原理上有一个区别:

    • 匿名内部类会生成class文件

    • lambda不会生成class文件

  2. lambda有着延迟执行的特点

    public Main {
        public static void main(String args[]) {
      	  String str1 = "你";
      	  String str2 = "是";
      	  String str3 = "谁?";
      	  // lambda的实现方式
      	  Test.doTest(true, () -> {
      		  System.out.println("满足条件时执行");
      		  return "lambda实现:"+str1+str2+str3;
      	  });
      	  // 匿名内部类的实现方式(同样有延迟执行的特点)
      	  Test.doTest(false, new Test() {
      		 @Override
      		 public void test() {
      			 System.out.println("满足条件时执行");
      			 return "匿名内部类实现:"+str1+str2+str3;
      		 }
      	  });
      	  // 常规写法
      	  doTest(false, str1+str2+str3);
              /*
                  满足条件时执行
                  lambda实现:你是谁?
              */
              /*
                  通常情况下会使用常规写法,因为理解简单,但存在一个问题,
                  可以看出flag=false时,确实没有输出str,但str却提前拼接好,
                  导致性能的浪费。
                  为此使用lambda(推荐)或者匿名内部类的形式,将字符串的拼接
                  延迟到判定条件之后。
               */
        }
        public static void doTest(boolean flag, String str) {
      	  if (flag) {
      		  System.out.println(str);
      	  }
        }
    }
    interface Test {
        String test();
        static void doTest(boolean flag, Test t) {
      	  if (flag) {
      		System.out.println(t.test());
      	  }
        }
    }
    

核心函数式接口

Supplier

方法签名:T get()
作用:供应商接口。生成T类型的数据。

public class Main {
     public static void main(String[] args) {
        int nums[] = {1, 23, 135, 534, 6245, 16254, 3547345};
        Integer res = test(() -> {
            int max = -1;
            for (int num : nums) {
                max = Math.max(max, num);
            }
            return max;
        });
        System.out.println("最大值:" + res); // 最大值:3547345
    }
    public static <T> T test(Supplier<T> s) {
        return s.get();
    }
}

Consumer

方法签名:void accept(T t)
作用:消费者接口。使用T类型的数据。

// 格式化打印
public class Main {
    public static void main(String[] args) {
        String strs[] = {"张三 男", "李四 男", "小红 女"};
        test(strs, (arr) -> {
            for (String str : arr) {
                String s[] = str.split(" ");
                System.out.println(s[0] + ":" + s[1]);
            }
        });
        /*
            张三:男
            李四:男
            小红:女
         */
    }
    public static <T> void test(T t, Consumer<T> c) {
        c.accept(t);
    }
}

当想将上面的数据正向与逆向输出两遍怎么办?

// 正向与逆向格式化打印, 使用andThen
public class Main {
    public static void main(String[] args) {
        String strs[] = {"张三 男", "李四 男", "小红 女"};
        Consumer<String[]> c1 = (arr) -> {
            System.out.println("------正序输出------");
            for (String str : arr) {
                String s[] = str.split(" ");
                System.out.println(s[0] + ":" + s[1]);
            }
        };
        Consumer<String[]> c2 = (arr) -> {
            System.out.println("------逆序输出------");
            for (int i = arr.length - 1; i >= 0; i --) {
                String s[] = arr[i].split(" ");
                System.out.println(s[0] + ":" + s[1]);
            }
        };
        test(strs, c1.andThen(c2));
        /*
            ------正序输出------
            张三:男
            李四:男
            小红:女
            ------逆序输出------
            小红:女
            李四:男
            张三:男
         */
    }
    public static <T> void test(T t, Consumer<T> c) {
        c.accept(t);
    }
}

由此看出两种写法等效,并且可以看出andThen可以链接两个Consumer的处理变为一个处理,或者说一起处理。当需要链接的Consumer数量不定时,有非常大的作用。传入的参数只需如下即可。

// c1,c2,c3,c4,c5均为Consumer实例
// 如此一来就可以连续执行c1,c2,c3,c4,c5的处理了
test(strs, c1.andThen(c2).andThen(c3).andThen(c4).andThen(c5));

Predicate

方法签名:boolean test(T t)
作用:断言接口。封装判断语句。

其实Predicate就是将条件语句打包成一个类,减少编程时传参的麻烦,同时使得条件语句也可以延迟执行

public class Main {
    public static void main(String[] args) {
        int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
        test(arr, (t) -> {
            if (t > 12523) return true;
            return false;
        });
        /*
            16254
            3547345
         */
    }
    public static void test(int arr[], Predicate<Integer> pre) {
        for (int s : arr) {
            // 用pre.test(s)来代替条件语句
            if (pre.test(s)) {
                System.out.println(s);
            }
        }
    }
}

default方法

方法签名:Predicate<T> and(Predicate<? super T> other)
作用:返回当前断言 && 入参断言。

满足当前断言和入参断言时,返回true。

public static void main(String[] args) {
    int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
    Predicate<Integer> pre1 = (t) -> {
        if (t > 12523) return true;
        return false;
    };
    Predicate<Integer> pre2 = (t) -> {
        if (t < 300000) return true;
        return false;
    };
    // 等价于 t > 12523 && t < 300000 的条件
    test(arr, pre1.and(pre2));
    /*
        16254
     */
}
public static void test(int arr[], Predicate<Integer> pre) {
    for (int s : arr) {
        if (pre.test(s)) {
            System.out.println(s);
        }
    }
}

方法签名:Predicate<T> or(Predicate<? super T> other)
作用:返回当前断言 || 入参断言。

满足当前断言或者入参断言时,返回true。

public static void main(String[] args) {
    int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
    Predicate<Integer> pre1 = (t) -> {
        if (t > 12523) return true;
        return false;
    };
    Predicate<Integer> pre2 = (t) -> {
        if (t < 300000) return true;
        return false;
    };
    // 等价于 t > 12523 && t < 300000 的条件
    test(arr, pre1.or(pre2));
    /*
        1
        23
        135
        534
        6245
        16254
        3547345
     */
}
public static void test(int arr[], Predicate<Integer> pre) {
    for (int s : arr) {
        if (pre.test(s)) {
            System.out.println(s);
        }
    }
}

方法签名:Predicate<T> negate()
作用:返回当前断言的取反。

public static void main(String[] args) {
    int arr[] = {1, 23, 135, 534, 6245, 16254, 3547345};
    Predicate<Integer> pre1 = (t) -> {
        if (t < 12523) return true;
        return false;
    };
    Predicate<Integer> pre2 = (t) -> {
        if (t > 300000) return true;
        return false;
    };
    // 等价于 t > 12523 && t < 300000 的条件
    test(arr, pre1.or(pre2).negate());
    /*
    	16254
        3547345
     */
}
public static void test(int arr[], Predicate<Integer> pre) {
    for (int s : arr) {
        if (pre.test(s)) {
            System.out.println(s);
        }
    }
}

其他方法

static <T> Predicate<T> isEqual(Object targetRef) {
    return null == targetRef ? Objects::isNull : (object) -> {
        return targetRef.equals(object);
    };
}

static <T> Predicate<T> not(Predicate<? super T> target) {
    Objects.requireNonNull(target);
    return target.negate();
}
  • isEqual:传入一个对象,当对象引用null时,返回isNull方法引用的Predicate,否则返回equals的Predicate
  • not:返回目标Predicate的negate

Function

方法签名:R apply(T var1)
作用:将T类型转为R类型,返回。

public class Main {
    public static void main(String[] args) {
        String str = "13523";
        Integer i = test(str, (t) -> {
            return Integer.parseInt(str);
        });
        System.out.println("i的value: " + i); // i的value: 13523
    }
    public static <T, R> R test(T t, Function<T, R> f) {
        return f.apply(t);
    }
}

default方法

方法签名:<V> Function<T, V> andThen(Function<? super R, ? extends V> after)
作用:将两个Function结合,返回。

先根据当前Function执行类型转换,然后再根据入参Function执行类型转换。

public class Main {
    public static void main(String[] args) {
        Function<String, Integer> f1 = (t) -> {
            return Integer.parseInt(t);
        };
        Function<Integer, String> f2 = (t) -> {
            return String.valueOf(t);
        };
        String str = "13523";
        String str1 = test(str, f1.andThen(f2));
        System.out.println("str1的value: " + str1); // str1的value: 13523
        /*
        	String -> Integer -> String
         */
    }
    public static <T, R> R test(T t, Function<T, R> f) {
        return f.apply(t);
    }
}

方法签名:<V> Function<V, R> compose(Function<? super V, ? extends T> before)
作用:将两个Function结合,返回。

与andThen刚好相反,先根据入参Function执行类型转换,然后再根据当前Function执行类型转换。

public class Main {
    public static void main(String[] args) {
        Function<String, Integer> f1 = (t) -> {
            return Integer.parseInt(t);
        };
        Function<Integer, String> f2 = (t) -> {
            return String.valueOf(t);
        };
        Integer i = 13523;
        Integer i1 = test(i, f1.compose(f2));
        System.out.println("i1的value: " + i1);
        /*
        	Integer -> String -> Integer
         */
    }
    public static <T, R> R test(T t, Function<T, R> f) {
        return f.apply(t);
    }
}

其他方法

static <T> Function<T, T> identity() {
    return (t) -> {
        return t;
    };
}
  • identity:返回一个和输入类型相同的输出类型。

总结

Lambda的特点

  • 语法只关注参数与方法体
  • 可推导即可省略

Lambda的作用

  • 优化部分匿名内部类的写法。(接口有且仅有一个抽象方法)

  1. 有着可推导即可省略的特性。 ↩︎

  2. 使用方式更为简单,但原理不变。 ↩︎

标签:return,String,void,---,static,test,Java,public,Lambda
From: https://www.cnblogs.com/buzuweiqi/p/16584590.html

相关文章

  • el-button点击了按钮会出现保留点击的状态
    问题el-button点击了按钮之后,将鼠标移出按钮,会出现保留点击的状态再查看它的css样式后,可以看到官方默认设置有:focus的状态规则解决办法在el-button的css里自定义或者......
  • 222.count-complete-tree-nodes 完全二叉树的节点个数
    遍历法遍历所有节点的方法,时间复杂度为\(O(n)\)classSolution{public:intcountNodes(TreeNode*root){if(root==nullptr)return0......
  • U8 V13.0小白入门开发记录十八-------------------初识UAP开发
    需要先认证二开的许可是否有二开的许可1.打开UAP应用  2.查看许可,如果没有需要联系官方客服获取相应的开发许可方式。 ......
  • U8 V13.0小白入门开发记录七-------------------插件开发(C#)调试
    这里继续延用上一章所写的内容举例。在上一章创建完工程并写完方法后,如何调试我们的业务逻辑呢? 1.首先登录我们的U8V13客户端 2.在VS打开的工程中,点击【调试】->【......
  • 第五章-DI反模式
    许多菜肴都需要用油在锅中烹饪食物。如果您不熟悉手头的食谱,则可以开始加热油,然后转身阅读食谱。但是一旦切完蔬菜,油就会冒出来。您可能会认为吸烟油意味着平底锅很热,可以......
  • Python-——面向对象
    面向对象编程范式面向对象指的是一种编程范式;编程范式:可以理解为一种编程方式,编程规范,编程模式面向过程面向过程编程:是一种以过程为核心的编程,主要是在解决一个问题......
  • js - 修改弹出窗口的标题
    js-修改弹出窗口的标题_xsscacy的博客-CSDN博客 https://blog.csdn.net/shen813/article/details/9087029先利用window.open(url)打开一个窗口,然后在利用设置document.t......
  • 论文笔记 - ToAlign: Task-oriented Alignment for Unsupervised Domain Adaptation
    摘要文章提出了一个任务导向的无监督域自适应。认为不是所有的特征都需要对齐,而是根据任务的先验知识,将源域特征分解为与任务相关的要对齐的,和与任务无关的可忽略的。......
  • MyBatis-特殊SQL的执行
    1. 通过用户名模糊查询用户信息SpecialSQLMapper.javapublicinterfaceSpecialSQLMapper{List<User>getUserByLike(@Param("mohu")Stringmohu);}SpecialSQ......
  • Nginx分布式框架详解-基础37-45nginx静态资源部署01
    nginx静态资源概述上网去搜索访问资源对于我们来说并不陌生,通过浏览器发送一个HTTP请求实现从客户端发送请求到服务器端获取所需要内容后并把内容回显展示在页面的一个......