1

Java lambda表达式基本使用 - Xianuii

 1 year ago
source link: https://www.cnblogs.com/Xianhuii/p/16927003.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

代码示例:java.lambda.LambdaExpression

1 本质#

  • lambda表达式本质上是对匿名内部类实例的一种简化写法。

1.1 案例#

有以下List<Integer>对象:

List<Integer> list = Arrays.asList(1, 3, 5, 7, 9, 2, 4, 6, 8, 10);

在对List进行从小大大排序时,会用到List#sort(Comparator)方法,需要传递实现Comparator接口的对象作为参数:

default void sort(Comparator<? super E> c) {
    // 省略方法体
}

可以想到有如下四种不同的代码编写的方式。

1、 创建Comparator的实现类#

根据需求,手动实现Comparator接口:

public class AscComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
}

然后,创建AscComparator实例,传给List#sort(Comparator)方法:

Comparator<Integer> ascComparator = new AscComparator();
list.sort(ascComparator);

2、创建Comparator的匿名对象#

可以直接创建Comparator的匿名对象,然后传给List#sort(Comparator)方法:

Comparator<Integer> anonymousComparator = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
};
list.sort(anonymousComparator);
list.sort(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});

3、lambda表达式#

直接使用lambda表达式:

list.sort((o1, o2) -> o1.compareTo(o2));

4、方法引用#

使用方法引用(方法引用具体概念和使用可以查看相关文章):

list.sort(Integer::compare);

可以明显看出,使用lambda表达式和方法引用极大提高了开发的速度,提升了代码的简洁性。

1.2 本质#

实际上,lambda表达式只是JVM提供的语法糖。在JVM执行过程中,会根据lambda表达式的规则,动态创建出匿名的接口实现类对象。

  • lambda表达式本质上是Java对象。

可以通过查看lambda表达式的Class对象和实例对象来证明这一点:

public class LambdaExpression {
    public void printConsumer(Consumer consumer) {
        System.out.println(consumer.getClass());
        System.out.println(consumer.getClass().getInterfaces()[0]);
        System.out.println(consumer);
    }
}

1、案例1#

运行以下代码:

LambdaExpression lambdaObjPrinter = new LambdaExpression();
lambdaObjPrinter.printConsumer(o -> o.getClass());
lambdaObjPrinter.printConsumer(o -> o.getClass());

会有如下输出:

class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
  • 这证明了执行过程中会根据lambda表达式动态生成函数式接口的实现类,并创建该实现类的实例。
  • 同时,先后执行的2个lambda表达式,尽管格式相同,仍然动态生成了2个实现类。

查看编译后的.class文件如下:

LambdaExpression lambdaObjPrinter = new LambdaExpression();
lambdaObjPrinter.printConsumer((o) -> {
    o.getClass();
});
lambdaObjPrinter.printConsumer((o) -> {
    o.getClass();
});

2、案例2#

运行如下代码:

LambdaExpression lambdaObjPrinter = new LambdaExpression();
for (int i = 0; i < 2; i++) {
    lambdaObjPrinter.printConsumer(o -> o.getClass());
}
System.out.println("=============");
for (int i = 0; i < 2; i++) {
    lambdaObjPrinter.printConsumer(o -> o.getClass());
}

会发现有如下输出:

class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
=============
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
  • 说明在不同for循环中(while等循环结果相同),只会动态生成1个实现类。

查看编译后的.class文件如下:

LambdaExpression lambdaObjPrinter = new LambdaExpression();

int i;
for(i = 0; i < 2; ++i) {
    lambdaObjPrinter.printConsumer((o) -> {
        o.getClass();
    });
}

System.out.println("=============");

for(i = 0; i < 2; ++i) {
    lambdaObjPrinter.printConsumer((o) -> {
        o.getClass();
    });
}
  • 说明这不是编译器编译的结果,应该是JVM执行时对循环语句中lambda表达式的优化。

2 基本语法#

lambda表达式本质上是对函数式接口的匿名实现类实例的一种简化写法:方法格式和lambda表达式格式一一对应。

对于执行逻辑而言,方法主要由两部分组成(没有返回值):形参和方法体。

lambda表达式与之对应:
1、形参:(t1, t2[, ……]),对应方法的形参(T1 t1, T2 t2[, ……])
2、箭头:->,固定
3、方法体:{},对应方法的方法体

2.1 分类#

根据方法形参和返回值的不同组合,lambda表达式可以分成以下几类:

  1. 没有形参:
() -> {
	// 方法体
}
  1. 一个形参:
(t) -> {
	// 方法体
}
  1. 多个形参:

    (t1, t2[, ……]) -> {
    // 方法体
    }

  2. 没有返回值:

() -> {
	// 方法体
}
  1. 有返回值:
() -> {
	// 方法体
	return something;
}

根据形参个数的不同,形参部分可以有不同的写法:
1、没有形参或多个形参(超过1个),需要带()

() -> {
	// 方法体
}
(t1, t2[, ……]) {
	// 方法体
}

2、一个形参,可以省略()

(t) -> {
	// 方法体
}
t -> {
	// 方法体
}

根据方法体中代码行数的不同,方法体部分也有不同的写法:
1、一行代码,可以省略{}(此时该行代码的return;也必须省略):

() -> {
	System.out.println("Hello World!");
}
() -> System.out.println("Hello World!")
() -> {
	return "Hello World!"
}
() -> "Hello World!"

2、多行代码,不可以省略{}

() -> {
	System.out.println("Hello");
	System.out.println("World!");
}
() -> {
	System.out.println("Hello");
	return "Hello World!"
}

2.2 案例#

  • 定义函数式接口,模拟不同类型的lambda表达式:
public class FunctionInterface {
    interface AcceptEmpty {
        void accept();
    }

    interface AcceptOne<T> {
        void accept(T t);
    }

    interface AcceptMore<T, E> {
        void accept(T t, E e);
    }

    interface ReturnVoid {
        void returnVoid();
    }

    interface ReturnR<R> {
        R returnR();
    }
}
  • 定义调用类,接收不同的lambda表达式:
/**
* 调用函数式接口的服务类
* @param <T> 第一个形参类型
* @param <E> 第二个形参类型
* @param <R> 返回值类型
*/
public class Service<T, E, R> {
    private T t;
    private E e;

    public Service(T t, E e) {
        this.t = t;
        this.e = e;
    }

    void acceptEmpty(FunctionInterface.AcceptEmpty acceptEmpty) {
        acceptEmpty.accept();
    }

    void acceptOne(FunctionInterface.AcceptOne<T> acceptOne) {
        acceptOne.accept(this.t);
    }

    void acceptMore(FunctionInterface.AcceptMore<T, E> acceptMore) {
        acceptMore.accept(this.t, this.e);
    }

    void returnVoid(FunctionInterface.ReturnVoid returnVoid) {
        returnVoid.returnVoid();
    }

    R returnR(FunctionInterface.ReturnR<R> returnR) {
        return returnR.returnR();
    }
}
  • 创建服务类实例:
Service<Integer, Integer, String> service = new Service<>(1, 2);

1、没有形参#

service.acceptEmpty(new FunctionInterface.AcceptEmpty() {
    @Override
    public void accept() {
        System.out.println("没有形参");
    }
});
service.acceptEmpty(() -> {
    System.out.println("没有形参");
});
service.acceptEmpty(() -> System.out.println("没有形参"));

2、一个形参#

service.acceptOne(new FunctionInterface.AcceptOne<Integer>() {
    @Override
    public void accept(Integer t) {
        System.out.println(t);
    }
});
service.acceptOne((t) -> System.out.println(t));
service.acceptOne(t -> System.out.println(t));

3、多个形参#

service.acceptMore(new FunctionInterface.AcceptMore<Integer, Integer>() {
    @Override
    public void accept(Integer t, Integer e) {
        System.out.println(t);
        System.out.println(e);
    }
});
service.acceptMore((t, e) -> {
    System.out.println(t);
    System.out.println(e);
});

4、没有返回值#

service.returnVoid(new FunctionInterface.ReturnVoid() {
    @Override
    public void returnVoid() {
        System.out.println("没有返回值");
    }
});
service.returnVoid(() -> System.out.println("没有返回值"));

5、有返回值#

service.returnR(new FunctionInterface.ReturnR<String>() {
    @Override
    public String returnR() {
        return "3";
    }
});
service.returnR(() -> "3");

3 执行逻辑#

  • lambda表达式本质上是传递了一个动态生成的匿名对象,是一种假的函数式编程。

lambda表达式形式上看起来很像是函数式编程:将一个函数当作形参传给方法。

实际上,lambda表达式只是Java的一个语法糖,它本质上仍然是一个普通的Java对象。

在执行的过程中,lambda表达式最终还是会被解析成匿名的接口实现类对象。

由于多态特性,在执行过程中,调用是外部传进来的实现类实例的代码。

在这个过程中,我们甚至可以将该匿名对象保存起来,便于后续多次调用。

  • 定义一个函数式接口:
public interface Lambda<T, R> {
    R method(T t);
}
  • 定义调用类:
public class FakeFunctionalProgramming<T, R> {
    private T t;
    private Lambda<T, R> lambda;

    public void setT(T t) {
        this.t = t;
    }

    public void setLambda(Lambda<T, R> lambda) {
        this.lambda = lambda;
    }

    public void doSomeThing() {
        T t = before();
        R r = lambda.method(t);
        after(r);
    }

    public T before() {
        return t;
    }
    public void after(R r) {
        System.out.println(r);
    }
}
  • 执行以下代码:
FakeFunctionalProgramming<String, String> ffp = new FakeFunctionalProgramming<>();
ffp.setT("Xianhuii");
ffp.setLambda((t) -> "Hello " + t + "!");
ffp.doSomeThing();  // Hello Xianhuii!

从上述结果可以看出,lambda表达式的编程方式本质上是利用了多态的特性,同时又使用了模板方法模式:

  • 调用处接收一个接口实例Lambda<T, R>作为形参。
  • 执行before()方法,处理相对固定的前处理逻辑。
  • 将执行过程中相关值作为形参传给Lambda<T, R>实例,进行特定处理。
  • 接收Lambda<T, R>特定处理后的返回值。
  • 执行after()方法,处理相对固定的后处理逻辑。

此时,我们应该能够透彻理解lambda表达式中形参的来源返回值的去向了。

借助Java多态特性,以及JVM动态生成匿名实现类实例的功能,lambda表达式才表现得那么像是函数式编程。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK