Java8新特性学习之二:lambda表达式深入学习

2022-07-04 14:44:37 浏览数 (1)

前言:

前面我们已经学习过lambda的入门,也感性的了解了lambda可以帮助我们解决什么问题,今天跟大家一起深入的学习如何使用、以及在哪里使用lambda表达式。如果你希望在看源码中遇到了lambda表达式不会抓狂;你希望你的代码更加优雅、简洁、或者装逼专用,咱们一起来学习。

  • Lambda表达式的语法
  • 在哪里使用、如何使用lambda
  • Lambda扩展

一、lambda表达式语法

如果你见过lambda表达式、或者你看过我的上一篇文章,你应该见过类似下面的代码

代码语言:javascript复制
Thread thread = new Thread(()-> System.out.println("i am a thead ,i am running my name is " Thread.currentThread().getName()));
thread.start();

redApples.sort((Apple a1,Apple a2)->a1.getWeight()-a2.getWeight());
Thread t = new Thread(()->{
    int a = 1;
    System.out.println(a);
});
t.start();

上面就是lambda表达式,那么lambda表达式的语法到底是怎么样的呢?使用它需要注意什么?我们从上面两个两个例子中应该能抓住一些lambda共有的特性,比如()->{}或者()->xxx。我们发现lambda表达式主要包括三个部分

1、参数列表:()里面的内容,如果为空,那么就没有参数

2、箭头:把参数列表和主体分开

3、Lambda主体:是一个表达式或者{}的内容,有返回值(返回值可能为void)

Lambda表达式的语法分下面两种:

1、(parameter)->expression

2、(parameter)->{statement;}

为了更加详细的掌握lambda的语法规则,下面用几个实例表示(伪代码)

代码语言:javascript复制
//用伪代码解析lambda表达式的规则
//参数是String类型的变量,并且返回一个int,这里的retur隐藏起来了
(String s)->s.length()
//参数是Apple类型的变量,返回时一个boolean类型,return隐藏起来了
(Apple apple)->apple.getWeight() > 150
//接受两个int类型的参数,没有返回值类型,或者理解为返回void,表达式可以包括多行,需要用{}包起来
(int x,int y)->{
    System.out.println("x y:");
    System.out.println(x y);
}
//没有参数,返回是int类型
()->42
//接受两个apple类型,返回int类型
(Apple a1,Apple a2)->a1.getWeight()-a2.getWeight()
//return 语句是控制流语句,必须要用{}包起来,下面写法是错误的
(int a)-> return "test" a;
//上面的正确写法应该是这样的,接受一个int类型的参数,返回一个String类型
(int a)->{return "test" a};
//“test”是表达式,不是语句,不能用大括号包起来,这种写法是错误的。有两种修改的方式
(String s)->{"test";}
//正确写法1:不用{}:接受一个String类型的参数,返回String类型
(String s)->"test"
//正确写法2:加return
(String s)->{return  "test";}

通过上面的实例,相信大家应该都掌握了编写lambda表达式的技巧了,其实lambda表达式可以理解为一个匿名函数,将行为参数化。

二、在哪里使用、如何使用lambda

回想一下我们在前面学习的时候,在哪些场景里面使用过lambda呢?Runnable、Comparator、ApplePredicate、FruitsPredicate?当参数为这些的时候,我们用过lambda是不是?是的,不错,官网上说的是“你可以在函数式接口上使用lambda表达式”,这里有一个关键字是函数式接口,什么叫做函数式接口?下面给出它的定义

函数式接口:只定义一个抽象方法的接口

这好像跟我们刚才想的Runnable、Comparator、ApplePredicate一样,就是一个函数式接口,我们可以尝试一下在FruitsPredicate中编写两个方法,看看会发生什么。

代码语言:javascript复制
public interface FruitsPredicate<T> {

    boolean test(T fruits);

    boolean test2(T fruits,T t2);
}

会报错,没错,因为这段代码不能确定要传递哪个方法。

在哪里使用lambda这个问题,相应大家应该都知道了,就是在函数式接口中可以使用lambda表达式。那是不是我们知道了lambda表达式的语法、以及在哪里使用lambda表达式就可以了呢?答案是不够的,lambda表达式的编写还需要和函数式接口中的方法有一定的匹配规则,就是lambda表达式的参数(包括类型和个数)和返回值类型要和函数式接口的方法参数和返回值要一致。否则也是无法编译的,我们可以测试一下。

这里报了一个参数不匹配的错误,因为我的函数式接口是这样子的

代码语言:javascript复制
public interface FruitsPredicate<T> {

    boolean test(T fruits);

    //boolean test2(T fruits,T t2);
}

这里要求参数是一个对象,那么我传了两个,所以报错了

这里报的错是我的函数式接口的返回值是boolean类型,而我的lambda表达式返回的int类型,也是不匹配,所以报错。好了,我相信到这里,小伙伴们应该都会使用lambda表达式了。我们有没有想过,lambda表达式是如何和函数式接口发生关联的呢?当我们使用下面的代码他是如何工作的呢?

代码语言:javascript复制
List<Apple> heavyApples = filterFruits(inventory,(Apple apple)-> apple.getWeight() > 150);

简单的用一个流程图来描述一下:

三、lambda表达式扩展

1、同样的lambda表达式,不同的函数式接口

这个很简单,就是lambda表达式相同,比如都是(String name)->name “hello world”

但是函数式接口不一样,定义两个不同的函数式接口就行啦,返回值和参数一样

2、类型推断

你有可能见过下面类似的lambda表达式的写法

代码语言:javascript复制
redApples.sort(( a1, a2)->a1.getWeight()-a2.getWeight());
redApples.sort((Apple a1,Apple a2)->a1.getWeight()-a2.getWeight());

这两者有什么不一样吗?可以尝试下,运行的结果是一样的,这两个的唯一的区别就是第一句代码中省略了类型,这也是ok的,这是因为lambda表达式可以根据函数式接口的方法推断出参数的类型,所以以后遇到了没有类型的参数,不用大惊小怪,当然还是建议使用加上参数类型,因为这样可读性会好很多(即便省略了参数类型,性能上也没任何的提升)

3、方法引用

你或许见过下面的lambda表达式的写法

代码语言:javascript复制
redApples.sort(comparing(Apple::getWeight));

当然comparing()方法是Comparator里面的方法,这是通过静态导入的方式,让我们使用起来就像调用本类的方法一样,这不是我们这次关注的重点,我们重点关注的是Apple::getWeight,这种方式就是方法引用。方法引用让你可以重复使用现有的方法定义,并想lambda一样传递它们,有时候,它比lambda表达式可读性更好。它的基本思想是:如果一个lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是用描述如何调用它。它是如何工作的呢?当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面,不需要使用括号,因为并没有实际调用这个方法,上面的方法引用其实就是(Apple apple)->apple.getWeight()的快捷写法。好了希望通过这篇文章可以让小伙伴们学会如何使用lambda表达式,下面我还会跟大家一起学习Stream流和Optional等java8的新特性。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/111219.html原文链接:https://javaforall.cn

0 人点赞