Java 8 中使用 Lambda 表达式简化代码

Java 8 中使用 Lambda 表达式简化代码

文章目录

  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。


系统环境:

  • Java JDK 版本:1.8

参考地址:

一、Lambda 表达式简介

1、什么是 Lambda 表达式

       Lambda 表达式是在 JDK 8 中引入的一个新特性,可用于取代大部分的匿名内部类。使用 Lambda 表达式可以完成用少量的代码实现复杂的功能,极大的简化代码代码量和代码结构。同时,JDK 中也增加了大量的内置函数式接口供我们使用,使得在使用 Lambda 表达式时更加简单、高效。

2、为什么需要 Lambda 表达式

       谈起为什么需要 Lambda 表达式,那得从函数式编程开始说起。函数式编程可以简单说是一种编程规范,也就是如何编写程序的方法论。它属于结构化编程的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。函数式编程有很多优点,其中包括:

  • 易于并发编程;
  • 代码的热升级;
  • 更方便的代码管理;
  • 代码简洁,开发快速;
  • 接近自然语言,易于理解;

函数式编程在 C#PythonJavaScript中都得到充分体现,在 Java 8 版本中也得到了支持。最明显的就是对 Lambda 表达式的支持。很多种迹象表明未来编程语言将是逐渐融合各自的特性,而不是单纯的声明式语言函数编程语言。将来声明式编程语言借鉴函数编程思想,函数编程语言融合声明式编程特性,这几乎是一种必然趋势。

       在 Java 中主要引入 Lambda 表达式的作用是对现有编码语义的优化,减少语法冗余。轻量级的将代码封装为数据,使代码简洁,易于理解。

二、函数式接口和定义

1、什么是函数式接口

       函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。Java 中函数式接口被隐式转换为 Lambda 表达式,只有保证接口类中有且只有一个抽象方法,Java 中的 Lambda 表达式才能对该方法进行推导。

2、函数式接口格式

在 Java 函数式接口类中,需要满足接口类中只能有一个抽象方法。总结如下:

  • 接口有且仅有一个抽象方法;
  • 允许定义静态方法;
  • 允许定义默认方法;
  • 允许 java.lang.Object 中的 public 方法;

在创建函数式接口时,可以在接口类上面加上@FunctionalInterface注解,这时编译器就可以对接口结构进行强制检查是否符合函数式接口规则,如果不符合规则就显示错误。当然,这个注解只是用于检查,即使不加上该注解,只要符合函数式接口规则一样也是函数式接口。

下面创建个演示的函数式接口,如下:

 1// @FunctionalInterface 注解说明: 
 2// 使用该注解来定义接口,编译器会强制检查接口是否符合函数式接口规则(有且仅有一个抽象方法),如果不符合则会报错。
 3@FunctionalInterface
 4public interface MyInterface{
 5    /**
 6     * 抽象方法(Jdk 8 以后接口类中方法可以省去 public abstract)
 7     */
 8    public abstract [返回值类型] [方法名称](参数列表);
 9    
10    /**
11     * 其它方法(Jdk 8 以后允许接口类中添加"默认方法"与"静态方法" )
12     */
13    ...()
14}

按照上面函数式接口,定义一个示例的函数式接口类,代码如下:

1@FunctionalInterface
2public interface MyCollection {
3    
4    void push(List list);
5    
6}

3、函数式接口和 Lambda 表达式的关系

函数式接口和 Lambda 表达式的关系可以总结为:

  • 函数式接口只包含一个操作方法;
  • Lambda 表达式只能操作一个方法;
  • Java 中的 Lambda 表达式核心就是一个函数式编程接口的实现。

4、当前 JDK 8 中存在的函数式接口类

在 JDK 1.8 之前,已经存在部分函数式接口,如下:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

在 JDK 8 中新增了函数接口 java.util.function 包,里面包含了很多用来支持 Java 的函数式编程的接口类,如下:

类名称 描述信息
BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果。
BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果。
BinaryOperator<T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果。
BiPredicate<T,U> 代表了一个两个参数的 boolean 值方法。
BooleanSupplier 代表了 boolean 值结果的提供方。
Consumer<T> 代表了接受一个输入参数并且无返回的操作。
DoubleBinaryOperator 代表了作用于两个 double 值操作符的操作,并且返回了一个 double 值的结果。
DoubleConsumer 代表一个接受 double 值参数的操作,并且不返回结果。
DoubleFunction<R> 代表接受一个 double 值参数的方法,并且返回结果。
DoublePredicate 代表一个拥有 double 值参数的 boolean 值方法。
DoubleSupplier 代表一个 double 值结构的提供方。
DoubleToIntFunction 接受一个 double 类型输入,返回一个 int 类型结果。
DoubleToLongFunction 接受一个 double 类型输入,返回一个 long 类型结果。
DoubleUnaryOperator 接受一个参数同为类型 double,返回值类型也为 double。
Function<T,R> 接受一个输入参数,返回一个结果。
IntBinaryOperator 接受两个参数同为类型 int,返回值类型也为 int。
IntConsumer 接受一个 int 类型的输入参数,无返回值。
IntFunction<R> 接受一个 int 类型输入参数,返回一个结果。
IntPredicate 接受一个 int 输入参数,返回一个布尔值的结果。
IntSupplier 无参数,返回一个 int 类型结果。
IntToDoubleFunction 接受一个 int 类型输入,返回一个double类型结果。
IntToLongFunction 接受一个 int 类型输入,返回一个 long 类型结果。
IntUnaryOperator 接受一个参数同为类型 int,返回值类型也为 int。
LongBinaryOperator 接受两个参数同为类型 long,返回值类型也为 long。
LongConsumer 接受一个 long 类型的输入参数,无返回值。
LongFunction<R> 接受一个 long 类型输入参数,返回一个结果。
LongPredicate R接受一个 long 输入参数,返回一个布尔值类型结果。
LongSupplier 无参数,返回一个结果 long 类型的值。
LongToDoubleFunction 接受一个 long 类型输入,返回一个 double 类型结果。
LongToIntFunction 接受一个 long 类型输入,返回一个 int 类型结果。
LongUnaryOperator 接受一个参数同为类型 long,返回值类型也为 long。
ObjDoubleConsumer<T> 接受一个 object 类型和一个 double 类型的输入参数,无返回值。
ObjIntConsumer<T> 接受一个 object 类型和一个 int 类型的输入参数,无返回值。
ObjLongConsumer<T> 接受一个 object 类型和一个 long 类型的输入参数,无返回值。
Predicate<T> 接受一个输入参数,返回一个布尔值结果。
Supplier<T> 无参数,返回一个结果。
ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个 double 类型结果
ToDoubleFunction<T> 接受一个输入参数,返回一个 double 类型结果。
ToIntBiFunction<T,U> 接受两个输入参数,返回一个 int 类型结果。
ToIntFunction<T> 接受一个输入参数,返回一个 int 类型结果。
ToLongBiFunction<T,U> 接受两个输入参数,返回一个 long 类型结果。
ToLongFunction<T> 接受一个输入参数,返回一个 long 类型结果。
UnaryOperator<T> 接受一个参数为类型 T,返回值类型也为 T。

5、JDK 中常见的函数式接口类

上面 java.util.function 包提供了众多的函数式接口,其中常用的有:

  • java.util.function.Predicate<T>:接收参数对象 T,返回一个 boolean 类型结果。
  • java.util.function.Comsumer<T>:接收参数对象 T,不返回结果。
  • java.util.function.Function<T,R>:接收参数对象 T,返回结果对象 R。
  • java.util.function.Supplier<T>:不接收参数,提供 T 对象的创建工厂。
  • java.util.function.UnaryOperator<T>:接收参数对象 T,返回结果对象 T。
  • java.util.function.BinaryOperator<T>:接收两个 T 对象,返回一个 T 对象结果。

注:为了使易懂,下面示例中 Lambda 表达式没有使用的最简易写法,而是使用比较繁琐的写法。

(1)、java.util.function.Predicate<T>

  • 接口类作用: 接收参数对象T,返回一个 boolean 类型结果。
  • 接口类源码:
 1@FunctionalInterface
 2public interface Predicate<T> {
 3    /** abstract 方法,接收一个参数, 判断这个参数是否匹配某种规则, 然后返回布尔值结果 */
 4    boolean test(T t);
 5    
 6    /** default 方法,接收另外一个 Predicate<T> 类型参数进行"逻辑与"操作,返回一个新的 Predicate */
 7    default Predicate<T> and(Predicate<? super T> other) {
 8        Objects.requireNonNull(other);
 9        return (t) -> test(t) && other.test(t);
10    }
11    
12    /** default 方法,接收另外一个 Predicate<T> 类型参数进行"逻辑或"操作,返回一个新的 Predicate */
13    default Predicate<T> negate() {
14        return (t) -> !test(t);
15    }
16    
17    /** default 方法,返回当前 Predicate 取反操作之后的 Predicate */
18    default Predicate<T> or(Predicate<? super T> other) {
19        Objects.requireNonNull(other);
20        return (t) -> test(t) || other.test(t);
21    }
22
23    static <T> Predicate<T> isEqual(Object targetRef) {
24        return (null == targetRef)
25                ? Objects::isNull
26                : object -> targetRef.equals(object);
27    }
28}
  • 使用示例:
 1public class PredicateExample {
 2
 3    /** 这里创建一个 Prodicate,设置验证秘钥的一个逻辑,然后返回并输出验证结果 */
 4    public static void main(String[] args) {
 5        // 创建 Predicate 及 Lambda 表达式与待实现的逻辑
 6        Predicate<String> validation = (String secret) -> {
 7            return "123456".equals(secret);
 8        };
 9        // 调用 Predicate 提供的 test 方法并输出结果
10        System.out.println(validation.test("123"));
11        System.out.println(validation.test("123456"));
12    }   
13    
14}

日常开发中,需要对某个值进行判断操作,并且返回判断结果,这时可以考虑使用 Predicate 接口,以及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。

(2)、java.util.function.Comsumer<T>

  • 接口类作用: 接收参数对象 T,不返回结果。
  • 接口类源码:
 1@FunctionalInterface
 2public interface Consumer<T> {
 3
 4    /** abstract 方法,接收一个参数,执行消费逻辑 */
 5    void accept(T t);
 6     
 7     /** default 方法,将两个 Consumer 连接到一起,再进行消费 */
 8    default Consumer<T> andThen(Consumer<? super T> after) {
 9        Objects.requireNonNull(after);
10        return (T t) -> { accept(t); after.accept(t); };
11    }
12    
13}
  • 使用示例:
 1/** 这里创建一个 Consumer,模拟发送消息并打印内容 */
 2public class ConsumerExample {
 3
 4    public static void main(String[] args) {
 5        // 创建 Consumer 及 Lambda 表达式与待实现的逻辑
 6        Consumer<String> consumer = (String message) -> {
 7            System.out.println("发送消息内容:" + message);
 8        };
 9        // 调用 Consumer 提供的 accept 方法
10        consumer.accept("测试消息");
11    } 
12    
13}

日常开发中,需要对某个类型进行公共处理,并且不需要任何返回值,这时可以考虑使用 Consumer 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。

(3)、java.util.function.Function<T,R>

  • 接口类作用: 接收参数对象 T,返回结果对象 R。
  • 接口类源码:
 1@FunctionalInterface
 2public interface Function<T, R> {
 3    /**abstract 方法,接收一个参数进行处理,然后返回处理结果 R */
 4    R apply(T t);
 5
 6    /** default 方法,先执行参数(Function)的,再执行调用者(Function) */
 7    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
 8        Objects.requireNonNull(before);
 9        return (V v) -> apply(before.apply(v));
10    }
11    
12    /** default 方法,先执行调用者,再执行参数,和compose相反 */
13    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
14        Objects.requireNonNull(after);
15        return (T t) -> after.apply(apply(t));
16    }
17    
18    /** 返回当前正在执行的方法 */
19    static <T> Function<T, T> identity() {
20        return t -> t;
21    }
22}
  • 使用示例:
 1/** 这里创建一个 Function,对传入的参数进行验证,如果包含 a 字符就返回1,否则返回0 */
 2public class FunctionExample {
 3
 4    public static void main(String[] args) {
 5        // 创建 Function 及 Lambda 表达式与待实现的逻辑
 6        Function<String, Integer> function = (String str) -> {
 7            return str.contains("a") ? 1 : 0;
 8        };
 9        // 调用 Function 提供的 apply 方法
10        System.out.println(function.apply("abcd"));
11        System.out.println(function.apply("efgh"));
12    }
13    
14}

日常开发中,需要对某个类型数据进行操作,经过一系列逻辑后转换为一个新的类型进行返回,这时可以考虑使用 Function 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。

(4)、java.util.function.Supplier<T>

  • 接口类作用: 不接收参数,提供 T 对象的创建工厂。
  • 接口类源码:
1@FunctionalInterface
2public interface Supplier<T> {
3    /** abstract 方法,设置业务逻辑,获取逻辑中创建的对象 */
4    T get();
5}
  • 使用示例:
 1/** 这里创建一个 Supplier,用于生成随机ID,通过 get 方法获取生成的随机ID值 */
 2public class SupplierExample {
 3
 4    public static void main(String[] args) {
 5        // 创建 Supplier 及 Lambda 表达式与待实现的逻辑
 6        Supplier<String> supplier = () -> {
 7            return UUID.randomUUID().toString();
 8        };
 9        // 调用 Supplier 提供的 get 方法
10        System.out.println(supplier.get());
11    }
12
13}

日常开发中,需要创建一个统一的工厂用于生成特定的产物完成特定的目标,这时就可以考虑使用 Supplier 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。

(5)、java.util.function.UnaryOperator<T>

  • 接口类作用: 接收参数对象 T,返回结果对象 T。
  • 接口类源码:
1// 可以看到 UnaryOperator 继承了 Function 接口
2@FunctionalInterface
3public interface UnaryOperator<T> extends Function<T, T> {
4    /** static 方法,接收一个参数,然后对其处理后再返回 */
5    static <T> UnaryOperator<T> identity() {
6        return t -> t;
7    }
8}
  • 使用示例:
 1/** 这里创建一个 UnaryOperator,接收一个字符串进行加工处理后返回新字符串 */
 2public class UnaryOperatorExample {
 3    
 4    public static void main(String[] args) {
 5        // 创建 UnaryOperator 及 Lambda 表达式与待实现的逻辑
 6        UnaryOperator<String> unaryOperator = (String str) -> {
 7            return "[" + str + "]";
 8        };
 9        // 调用 UnaryOperator 继承的 Function 提供的 apply 方法
10        System.out.println(unaryOperator.apply("hello"));
11    }
12
13}

日常开发中,我们经常要对一个已有的对象进行操作修改,然后返回修改后的对象,这时就可以考虑使用 UnaryOperator 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。

(6)、java.util.function.BinaryOperator<T>

  • 接口类作用: 接收两个 T 对象,返回一个 T 对象结果。
  • 接口类源码:
 1@FunctionalInterface
 2public interface BinaryOperator<T> extends BiFunction<T,T,T> {
 3    /** abstract 方法,通过比较器Comparator来比较两个元素中较小的一个作为返回值返回 */
 4    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
 5        Objects.requireNonNull(comparator);
 6        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
 7    }
 8    
 9    /** 通过比较器Comparator来比较两个元素中较大的一个作为返回值返回 */
10    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
11        Objects.requireNonNull(comparator);
12        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
13    }
14}
  • 使用示例:
 1/** 这里创建一个 BinaryOperator,比较传入的两个参数哪个值最大,返回最大值 */
 2public class BinaryOperatorExample {
 3
 4    public static void main(String[] args) {
 5        // 创建 BinaryOperator 及 Lambda 表达式与待实现的逻辑
 6        BinaryOperator<Integer> binaryOperator = (Integer t1, Integer t2) -> {
 7            return t1 > t2 ? t1 : t2;
 8        };
 9        // 调用 BinaryOperator 继承的 BiFunction 提供的 apply 方法
10        System.out.println(binaryOperator.apply(1, 2));
11    }
12
13}

在使用这几种基本函数接口时传入参数 T 不能是基本类型,如 BinaryOperator 中 T 不能设置为 int,只能使用 Integer 包装类,这也限制了 Lambda 表达式中设置参数时候,使用包装类替换基本类型。

日常开发中,我们有时候会对两个对象进行操作,执行一些操作逻辑后返回结果,这时就可以考虑使用 BinaryOperator 接口及它的 Lambda 表达式的实现,能方便的实现我们业务上的一些功能。

三、Lambda 表达式基本语法

1、Lambda 表达式的组成

Lambda 表达式的组成可以拆分为:

  • 声明: 与 Lambda 表达式绑定的接口类型。
  • 参数: 参数包含在一对 () 中,和绑定的接口中的抽象方法中的参数个数及顺序一致。
  • 操作符: ->
  • 执行代码块: 执行代码块包含在一对 {} 中,出现在操作符的右侧。            

    [接口声明] = (参数) -> {执行代码块}

2、Lambda 表达式的格式

Lambda 表达式可以分为下面几种格式:

  • 无参数,无返回值;
  • 有一个参数,无返回值;
  • 左侧只有一个参数,小括号可以省略不写;
  • 有两个以上参数,有返回值,并且Lambda 体中有多条语句;
  • 若右侧Lambda体中,只有一条语句,return 和大括号都可以省略不写;
  • Lambda 表达式的参数列表的数据类型可以省略不写,jvm编译器会进行上下文推断出,数据类型“类型推断”;

(1)、无参数,无返回值

1() -> System.out.println("测试");

(2)、有一个参数,无返回值

1(x) -> System.out.println(x);

(3)、左侧只有一个参数,小括号可以省略不写

1x -> System.out.println(x);

(4)、有两个以上参数,有返回值,并且Lambda 体中有多条语句

1Comparator<Integer> comparator = (x, y) -> {
2    System.out.println("测试");
3    return Integer.compare(x,y);
4};

(5)、若右侧Lambda体中,只有一条语句,return 和大括号都可以省略不写

1Comparator<Integer> Comparator = (x, y) -> Integer.compare(x, y);

(6)、Lambda 表达式的参数列表的数据类型可以省略不写,JVM 在运行时,会自动根据绑定的抽象方法中的参数,进行数据类型推导

1(Integer x, Integer y) -> Integer.compare();

四、Lambda 表达式中变量作用域

Java 中的变量捕获与变量隐藏:

  • 变量捕获: 局部类和匿名内部类可以访问被 final 修饰的封闭块内的局部变量。
  • 变量隐藏: 在一个类中,子类中的成员变量如果和父类中的成员变量同名,那么即使他们类型不一样,只要名字一样,父类中的成员变量都会被隐藏。

在局部类和匿名内部类都存在 变量捕获变量隐藏,而在 Lambda 表达式中则只支持 变量捕获

下面是对这作用域得演示示例:

(1)、匿名内部类:

 1public class VariableExample {
 2    /** 成员变量 */
 3    String str1 = "成员变量";
 4
 5    public void innerClass() {
 6        // 方法内部变量
 7        String str2 = "方法内部变量";
 8        // 使用匿名内部类创建线程
 9        new Thread(new Runnable() {
10            // 匿名内部类内部变量
11            String str3 = "匿名内部类内部变量";
12            @Override
13            public void run() {
14                /* 访问变量 */
15                System.out.println("匿名内部类输出:" + str1);
16                System.out.println("匿名内部类输出:" + str2);
17                System.out.println("匿名内部类输出:" + str3);
18                /* 修改变量 */
19                str1 = "修改访问成员变量";
20                // str2 = "修改访问方法内部变量"; // 不能进行修改,默认推导变量的修饰符 final
21                str3 = "修改访问匿名内部类内部变量";
22                /* 在匿名内部类中定义和类外部变量一样名称的变量 */
23                String str1 = "重新命名成员变量";
24                String str2 = "重新命名方法内部变量";
25            }
26        }).start();
27    }
28    
29    /** Main 方法 */
30    public static void main(String[] args) {
31        VariableExample variableExample = new VariableExample();
32        // 匿名内部类
33        variableExample.innerClass();
34    }
35
36}

(2)、Lambda 表达式:

 1public class VariableExample {
 2    /** 成员变量 */
 3    String str1 = "成员变量";
 4    
 5    public void lambdaExpression() {
 6        // 方法内部变量
 7        String str2 = "方法内部变量";
 8        new Thread(()->{
 9            // Lambda 内部变量
10            String str3 = "Lambda 内部变量";
11            /* 访问变量 */
12            // 访问成员变量
13            System.out.println("Lambda 表达式输出:" + str1);
14            // 访问方法内部变量
15            System.out.println("Lambda 表达式输出:" + str2);
16            // 访问匿名内部类内部变量
17            System.out.println("Lambda 表达式输出:" + str3);
18            /* 修改变量 */
19            str1 = "修改访问成员变量";
20            // str2 = "修改访问方法内部变量"; // 不能进行修改,默认推导变量的修饰符 final
21            str3 = "修改访问匿名内部类内部变量";
22            /* 在 Lambda 中定义和类外部变量一样名称的变量 */
23            String str1 = "重新命名成员变量";
24            // String str2 = "重新命名方法内部变量"; // 不能命名,lambda 不支持变量隐藏
25        }).start();
26    }
27
28    /** Main 方法 */
29    public static void main(String[] args) {
30        VariableExample variableExample = new VariableExample();
31        // 匿名内部类
32        variableExample.innerClass();
33        // Lambda 表达式
34        variableExample.lambdaExpression();
35    }
36
37}

五、Lambda 表达式方法重载问题

       当使用 Lambda 表达式,调用一个类中的重载方法,且方法中的参数为都为函数接口,函数接口中定义的方法接收的参数类型相同,这时候 Lambda 是无法推断出要调用哪个方法。

函数接口A:

1@FunctionalInterface
2interface MyInterfaceA {
3    void push(String param);
4}

函数接口B:

1@FunctionalInterface
2interface MyInterfaceB {
3    void pull(String param);
4}

示例,实现方法重载与测试的 Main 方法:

 1public class LambdaOverloadExample {
 2    
 3    // 重载方法A
 4    public static void method(MyInterfaceA myInterfaceA) {
 5        myInterfaceA.push("hello 1");
 6    }
 7    // 重载方法B
 8    public static void method(MyInterfaceB myInterfaceB) {
 9        myInterfaceB.pull("Hello 2");
10    }
11
12    /** Main 方法*/
13    public static void main(String[] args) {
14        // 使用匿名内部类
15        method(new MyInterfaceA() {
16            @Override
17            public void push(String param) {
18                System.out.println(param);
19            }
20        });
21        method(new MyInterfaceB() {
22            @Override
23            public void pull(String param) {
24                System.out.println(param);
25            }
26        });
27        // 使用 Lambda 表达式
28        //method(param -> System.out.println(param)); // 编译器提示错误,表示无法推断使用哪个参数
29    }
30
31}

上面注掉的那部分代码,在编辑器中直接提示错误,很显然 Lambda 表达式无法直接推断出使用哪个类中的重载方法。其实,只要明确告诉 Lambda 表达式使用哪个参数,就可以很简单的解决问题,比如以上面的例子,在 Lambda 表达式使用 method 方法时,将参数类型转换为对应的要使用的类型就可以解决这个问题,代码如下:

1// 转换参数为 MyInterfaceA
2method((MyInterfaceA)param -> System.out.println(param));
3// 转换参数为 MyInterfaceB
4method((MyInterfaceB)param -> System.out.println(param));

按上面进行修改后就可以正常使用 Lambda 表达式了,如果不习惯也可以使用匿名内部类进行方法调用,内名内部类是没有相关问题的。

六、Lambda 表达式方法引用

       方法引用本质上就是对方法调用的简化,方法引用和函数式接口绑定,在使用过程中会创建函数式接口的实例,是结合 Lambda 表达式的一种特性。在应用过程中,方法引用常分为:

  • 静态方法引用
  • 实例方法引用
  • 构造方法引用
  • 特定类型的任意对象实例方法引用

注意:在使用 Lmabda 方法引用时虽然能够简化代码,但是在实际开发中不可因需要简化代码而过度使用方法引用,因为他会在很大程度上降低代码可读性。

1、创建示例的实体类

为了下面示例方便,我们首先创建一个 Person 实体类,如下:

 1public class Person {
 2    /** 姓名 */
 3    private String name;
 4    /** 岁数 */
 5    private int age;
 6
 7    public Person() {
 8    }
 9    public Person(String name, int age) {
10        this.name = name;
11        this.age = age;
12    }
13
14    public String getName() {
15        return name;
16    }
17    public void setName(String name) {
18        this.name = name;
19    }
20    public int getAge() {
21        return age;
22    }
23    public void setAge(int age) {
24        this.age = age;
25    }
26    
27    @Override
28    public String toString() {
29        return "Person{name=" + name + ",age=" + age + "}";
30    }
31}

2、静态方法引用示例

静态方法的引用的使用: 静态方法所在类.方法名称() --> 静态方法所在类 :: 方法名称

创建一个使用静态方法引用的示例类:

 1public class StaticMethodExample {
 2
 3    /** 测试的静态方法 */
 4    public static int compareAge(Person p1, Person p2) {
 5        return p1.getAge() - p2.getAge();
 6    }
 7
 8    public static void main(String[] args) {
 9        // 创建 Person 集合
10        List<Person> personList = new ArrayList<>();
11        personList.add(new Person("Wangqin",26));
12        personList.add(new Person("Liming",22));
13        personList.add(new Person("Alisi",18));
14        personList.add(new Person("Jerry",31));
15        
16        // 按岁数进行排序,使用静态方法引用
17        personList.sort(StaticMethodExample::compareAge);
18    }
19
20}

3、实例方法引用示例

实例方法的引用的使用:创建类型对应一个对象 --> 对应应用 :: 实例方法名称

创建一个封装实例方法的类:

1public class PersonUtil{
2    /** 测试的实例方法 */
3    public int compareAge(Person p1, Person p2) {
4        return p1.getAge() - p2.getAge();
5    }
6}

创建一个使用实例方法引用的示例类:

 1public class InstanceMethodExample {
 2
 3    public static void main(String[] args) {
 4        // 创建 Person 集合
 5        List<Person> personList = new ArrayList<>();
 6        personList.add(new Person("Wangqin",26));
 7        personList.add(new Person("Liming",22));
 8        personList.add(new Person("Alisi",18));
 9        personList.add(new Person("Jerry",31));
10        
11        // 按岁数进行排序,
12        PersonUtil personUtil = new PersonUtil();
13        // 引用实例方法
14        personList.sort(personUtil::compareAge);
15    }
16
17}

4、构造方法引用示例

构造方法的引用的使用:绑定函数式接口

创建一个函数式接口,且设置接收参数和 Person 的构造方法相同,返回 Person 对象的方法定义:

1@FunctionalInterface
2public interface PersonConstructor{
3    Person initInstance(String name, int age);
4}

创建一个使用构造方法引用的示例类

 1public class LambdaConstructorMethodExample {
 2
 3    public static void main(String[] args) {
 4        // 创建 Person 集合
 5        List<Person> personList = new ArrayList<>();
 6        personList.add(new Person("Wangqin", 26));
 7        personList.add(new Person("Liming", 22));
 8        personList.add(new Person("Alisi", 18));
 9        personList.add(new Person("Jerry", 31));
10
11        // 构造方法引用
12        PersonConstructor personConstructor = Person::new;
13        Person person = personConstructor.initInstance("linda", 18);
14        System.out.println(person);
15    }
16    
17}

5、特定类型的任意对象实例方法引用示例

特定类型的任意对象实例方法引用示例:特定类型 :: 特定类型的方法

以下是对特定类型的任意对象的实例方法的引用的示例:

 1public class LambdaExample {
 2
 3    public static void main(String[] args) {
 4        // 创建字符串集合
 5        List<String> strList = new ArrayList<>();
 6        strList.add("Jerry");
 7        strList.add("Mini");
 8        strList.add("Kary");
 9        strList.add("walls");
10        
11        // 使用集合的sort方法,按照String的compareToIgnoreCase进行排序(比较字符串hash值)
12        strList.sort(String::compareToIgnoreCase);
13        System.out.println(strList);
14    }
15
16}

这里根据定义的集合 strList 去推导目标类型参数值,如果不符合后面传入的方法引用所对应的类型,将报错。该方法参考等效 Lambda 表达式 String::compareToIgnoreCase 的参数列表 (String a, String b),其中 a 和 b 是用于更好地描述这个例子中的任意名称。方法引用将调用该方法 a.compareToIgnoreCase(b)

---END---


  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。