【javaSE-语法】lambda表达式
- 1. 先回忆一下:
- 1.1 接口不能直接通过关键字new进行实例化
- 1.2 函数式接口
- 1.3 匿名内部类
- 1.31 匿名内部类在代码中长啥样?
- 1.32 构造一个新的对象与构造一个扩展了某类的匿名内部类的对象,两者有什么区别?
- 1.33 如何调用匿名内部类对象中的成员
- 1.34 匿名内部类的变量捕获
- 2. lambda表达式
- 2.1 语法
- 2.2 基本使用
- 2.3 lambda的变量捕获
- 2.4 lambda在集合中的使用
- 2.41 Collection接口下的forEach()方法和lambda的梦幻联动
- 2.42 List接口下的sort()方法和lambda的梦幻联动
- 4. lambda表达式的优缺点
1. 先回忆一下:
1.1 接口不能直接通过关键字new进行实例化
-
接口是引用数据类型,代码中使用interface定义一个接口。接口中的成员变量默认被
public static final
修饰,接口中的成员方法默认被public abstract
修饰,抽象方法没有具体的实现。 -
接口中成员方法如果用defaul或者static修饰,该成员方法可以有具体的实现。
-
-
接口不能通过new关键字进行实例化:
-
类通过关键字implement实现接口,类实现接口后,要重写接口中的所有抽象方法。通过实例化(实现了某接口的)类,间接实例化某接口。
-
-
接口间可以通过关键字extends进行拓展,即把多个接口合并在一起。
1.2 函数式接口
如果一个接口中有且只有一个抽象方法,那么该接口就是一个函数式接口。
- 在写代码时,如果在某个接口上声明了
@FunctionalInterface
,表示该接口必须是一个函数式接口,否则程序编译会报错。 - 下面这种也是函数式接口:
1.3 匿名内部类
1.31 匿名内部类在代码中长啥样?
如果一个类定义在类的内部或者方法的内部,该类就是内部类。根据内部类在代码中的位置,内部类可以分为成员内部类和局部内部类。成员内部类与外部类成员所处位置相同,局部内部类定义在方法内部。
匿名内部类和局部内部类一样,都在方法内部,且匿名内部类必须实现接口或者继承其他类。
我们在写代码的时候,如果想在方法内部创建一个类,但是不会用到该类的名字,此时就可以使用匿名内部类。如上述代码中的两个匿名内部类:
-
第一个匿名内部类的意思是:有一个没有名字的类,该类实现了A接口,并且重写了A接口中的testA方法。
-
如果用局部内部类实现,代码如下:
-
第二个匿名内部类的意思是:有一个没有名字的类,该类继承了类B,但没有进行扩展。(一般都要进行扩展,即添加新的成员,否则该匿名内部类无意义)。
-
如果用局部内部类实现,代码如下:
1.32 构造一个新的对象与构造一个扩展了某类的匿名内部类的对象,两者有什么区别?
如果构造参数列表的,结束小括号后面跟一个开始大括号,就是在定义匿名内部类对象。
1.33 如何调用匿名内部类对象中的成员
1.34 匿名内部类的变量捕获
在匿名内部类中,不仅能访问外部类的字段,还能访问局部变量。但是无论是字段还是局部变量,都只能是一旦赋值绝不会改变的变量或者被final修饰的常量。
2. lambda表达式
lambda表达式是匿名内部类的简化。说的再详细一点,lambda表达式创建了一个类,实现了一个接口,重写了接口的方法。
看下面代码,在代码中对象priorityQueue和对象priorityQueue1完全一样,但是显然,创建priorityQueue1的代码要更加简洁。这是因为创建priorityQueue的时候,构造方法的参数是一个匿名内部类,而创建priorityQueue1的时候,构造方法的参数一个lambda表达式。
所以,使用lambda表达式能让能让代码更加简洁,开发也随之更加迅速,这就是我们学习lambda表达式的原因。
2.1 语法
lambda表达式的语法: (parameters) -> expression 或 (parameters) ->{ statements; }
- parameters是参数,根据实际情况,参数可以有,也可以没有。这里的参数等同于方法的参数列表。
- 参数的数据类型可以明确声明,也可以不声明而是让JVM推断。如果要省略,每个参数的数据类型都要省略。
- 当只有一个没有声明类型的参数时,可以省略外面的小括号()。
expression
是表达式,是函数式接口里方法的实现。statements;
是代码块,是函数式接口里方法的实现。这里的代码块和表达式等同于方法里面的方法体。- 如果方法体中只有一条代码,大括号{}可以省略
- 如果方法体中只有一条语句,且是return语句,大括号和关键字return可同时省略。
例如:
// 1. 不需要参数,返回值为 2
() -> 2
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的和
(x, y) -> x + y
// 4. 接收2个int型整数,返回他们的乘积
(int x, int y) -> x * y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
2.2 基本使用
接下来跟着具体场景看看lambda表达式在代码中究竟如何使用:
- 已知函数式接口NoParameterNoReturn,接口中的test()方法没有参数,没有返回值。如何调用接口中的test()方法呢?运行结果:
- 下面是使用lambda表达式的更多例子:
public class Test {
public static void main1(String[] args) {
//如何调用接口NoParameterNoReturn中的test方法?
//不使用lambda表达式(通过匿名内部类实现接口,重写方法):
NoParameterNoReturn noParameterNoReturn =
new NoParameterNoReturn() {
@Override
public void test() {
System.out.println("test......");
}
};
noParameterNoReturn.test();
//使用lambda表达式
NoParameterNoReturn noParameterNoReturn1 =
()-> System.out.println("test......");//重写接口中的方法,该方法没有参数,没有返回值
noParameterNoReturn1.test();
}
public static void main2(String[] args) {
//如何调用接口OneParameterNoReturn中的test方法?
OneParameterNoReturn oneParameterNoReturn = (x)->{
System.out.println(x);
};//只有一个没声明类型的参数,小括号()可以省略;方法体中只有一条语句,{}可以省略
oneParameterNoReturn.test(10);
//如何调用接口MoreParameterNoReturn中的test()方法?test()方法无返回值但是有多个参数
MoreParameterNoReturn moreParameterNoReturn =
(int x,int y) -> {
System.out.println(x+y);
};
moreParameterNoReturn.test(10,20);
}
public static void main(String[] args) {
//如何调用接口NoParameterReturn中的test方法?test方法中有返回值,无参数
NoParameterReturn noParameterReturn =
() -> {return 10;};//return和{}可同时省略
System.out.println(noParameterReturn.test());
//如何调用接口OneParameterReturn中的test方法?test方法中有返回值,有一个参数
OneParameterReturn oneParameterReturn = x -> x;
oneParameterReturn.test(20);//传20返回20
//如何调用接口MoreParameterReturn中的test方法?test方法中有返回值,有多个参数
MoreParameterReturn moreParameterReturn =
(a,b) -> {return a*b;};
moreParameterReturn.test(10,20);//返回200
}
}
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
void test(int a,int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
int test(int a);
}
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
int test(int a,int b);
}
所以如果想在代码中使用lambda表达式,一定要清楚函数式接口中要被重写的方法,该方法中是否有返回值,是否有参数。
2.3 lambda的变量捕获
同匿名内部类的变量捕获一样,lambda表达式内不仅能访问外部类的字段,还能访问局部变量。但是无论是字段还是局部变量,都只能是一旦赋值绝不会改变的变量或者被final修饰的常量。
例如在下面代码中:
在多线程编程中,经常会用到lambda表达式,尤其要注意变量捕获这一点,如果在lambda表达式这种用了某变量,但在后续又修改了该变量,代码就会出现问题。
2.4 lambda在集合中的使用
lambda表达式是JavaSE8中的一个重要的新特性。为了能让Lambda和Java的集合类集更好的一起使用,集合当中,也新增了部分接口,以便与Lambda表达式对
接。
对应的接口 | 新增的方法 |
---|---|
Collection | removeIf() spliterator() stream() parallelStream() forEach() |
List | replaceAll() sort() |
Map | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() |
接下来会分别演示在Collection接口forEach() 方法中,在List接口sort()方法中,分别如何使用lambda表达式。
其他方法在遇到时,查看Java帮助手册或查看对应源码,同样的方法学习掌握即可。
2.41 Collection接口下的forEach()方法和lambda的梦幻联动
- 先找到Collection接口下forEach()方法中的函数式接口:
Collection接口拓展了Iterable接口,
forEach()方法在Iterable接口中,
forEach()方法的源码如下:从源码中看出,forEach()方法的功能就是遍历当前集合中的元素,然后让每个元素执行action.accept()操作。
forEach()方法的参数action的类型是一个函数式接口,其中的void accept(T t)抽象函数
,有一个参数无返回值。
所以当我们调用forEach()方法的时候,forEach()方法的参数是一个函数式接口action,我们需要实现该接口并且重写接口中的accept()的方法。此时我们可以使用匿名内部类或者lambda表达式:
- 例如下列代码:
2.42 List接口下的sort()方法和lambda的梦幻联动
- 先找到List接口下sort()方法中涉及的函数式接口:
sort()方法的源码如下:
从源码中看出,sort()方法的功能就是先调用Arrays工具包中的sort方法,并依据比较器c的规则对当前容器元素进行排序。然后再对已经排好序的当前容器进行迭代操作。
sort方法的参数c的类型是一个函数式接口,其中的int compare(T o1, T o2)抽象函数,有两个参数有返回值。
所以当我们调用sort()方法的时候,sort()方法的参数是一个函数式接口c,我们需要实现该接口并且重写接口中的compare()的方法。此时我们可以使用匿名内部类或者lambda表达式:
2.例如下面代码:
4. lambda表达式的优缺点
Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- Java 引入 Lambda,改善了集合操作
缺点:
- 代码可读性变差
- 在非并行计算中,很多计算未必有传统的 for 性能要高
- 不容易进行调试
好了,本篇到这就结束了,相信读完本篇的你已经能读懂,会用lambda表达式了。最后祝你我早日成为技术流。
我是一朵忽明忽暗的云,点赞收藏加关注,我们一起进步!