【Java干货教程】Java中Lambda表达式详解
Java中Lambda表达式详解
一、函数式编程思想概述
- 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”
- 面向对象思想强调“必须通过对象的形式来做事情”
- 函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而我们要学习的Lambda表达式就是函数式思想的体现
二、体验Lambda表达式
- 需求:启动一个线程,在控制台输出一句话:多线程程序启动了
2.1、方式1:定义一个类MyRunnable实现Runnable接口,重写run()方法
//创建MyRunnable类的对象
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("多线程程序启动了...");
}
}
public class LambdaDemo {
public static void main(String[] args) {
//实现类的方式实现需求
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable的对象作为构造参数传递
Thread t = new Thread(my);
//启动线程
t.start();
}
}
2.2、方式2:匿名内部类的方式改进
public class LambdaDemo {
public static void main(String[] args) {
//匿名内部类的方式改进
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程程序启动了...");
}
}).start();
}
}
- 匿名内部类中重写run()方法的代码分析
①、方法形式参数为空,说明调用方法时不需要传递参数
②、方法返回值类型为void,说明方法执行没有结果返回
③、方法体中的内容,是我们具体要做的事情
2.3、方式3:Lambda表达式的方式改进
public class LambdaDemo {
public static void main(String[] args) {
//Lambda表达式改进
new Thread(() -> {
System.out.println("多线程程序启动了...");
}).start();
}
}
- Lambda表达式的代码分析
①、():里面没有内容,可以看成是方法形式参数为空
②、->:用箭头指向后面要做的事情
③、{ }:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
2.4、三种方式实现需求的优缺点
- 综合上述三个方法可以看出,当我们使用实现类的方式实现需求的时候,我们可以看到我们需要先创建一个实体类,然后将实现类的对象传进去才可以使用。
- 当我们使用你们匿名内部类的方法实现需求时,我们可以发现需要重写run方法(当我们调用其他方法,忘记去重写什么方法时会比较懵逼),相比较也是比较麻烦的。
- 当我们使用Lambda表达式来实现需求时,我们可以看到我们不用关心创建了什么实体类、重写了什么方法。我们只需要关心它最终要做的事情是System.out.println("多线程程序启动了...");
三、Lambda表达式的标准格式
组成Lambda表达式的三要素:形式参数,箭头,代码块
Lambda表达式的格式
- ①、格式:(形式参数) -> {代码块}
- ②、形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ③、->:由英文中画线和大于符号组成,固定写法。代表指向动作
- ④、代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
四、Lambda表达式的练习
- Lambda表达式的使用前提
①、有一个接口
②、接口中有且仅有一个抽象方法
4.1、练习1(抽象方法无参无返回值)
- 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
public interface Eatable {
void eat();
}
- 定义一个测试类(EatableDemo),在测试类中提供两个方法
①、一个方法是:useEatable(Eatable e)
②、一个方法是主方法,在主方法中调用useEatable方法
4.1.1、通过实现类方法
class EatableImpl implements Eatable{
@Override
public void eat() {
System.out.println("一天一苹果,医生远离我");
}
}
public class EatableDemo {
public static void main(String[] args) {
//在主方法中调用useEatable方法
Eatable e = new EatableImpl();
useEatable(e);
}
private static void useEatable(Eatable e){
e.eat();
}
}
4.1.2、匿名内部类的方式改进
public class EatableDemo {
public static void main(String[] args) {
//匿名内部类
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("一天一苹果,医生远离我");
}
});
}
private static void useEatable(Eatable e){
e.eat();
}
}
4.1.3、Lambda表达式的方式改进
public class EatableDemo {
public static void main(String[] args) {
//用Lambda表达式
useEatable(() -> {
System.out.println("一天一苹果,医生远离我");
});
}
private static void useEatable(Eatable e){
e.eat();
}
}
4.2、练习2(抽象方法带参无返回值)
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
public interface Flyable{
void fly(String s);
}
- 定义一个测试类(FlyableDemo),在测试类中提供两个方法
①、一个方法是:useFlyable(Flyable f)
②、一个方法是主方法,在主方法中调用useFlyable方法
4.2.1、匿名内部类的方式
public class FlyableDemo {
public static void main(String[] args) {
//在主方法中调用useFlyable方法
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("飞机自驾游");
}
});
}
private static void useFlyable(Flyable f){
f.fly("风和日丽,晴空万里");
}
}
4.2.2、Lambda表达式的方式改进
public class FlyableDemo {
public static void main(String[] args) {
//Lambda
useFlyable((String s)-> {
System.out.println(s);
System.out.println("飞机自驾游");
});
}
private static void useFlyable(Flyable f){
f.fly("风和日丽,晴空万里");
}
}
4.3、练习3(抽象方法带参有返回值)
- 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
public interface Addable {
int add(int x,int y);
}
- 定义一个测试类(AddableDemo),在测试类中提供两个方法
①、一个方法是:useAddable(Addable a)
②、一个方法是主方法,在主方法中调用useAddable方法
public class AddableDemo {
public static void main(String[] args) {
//在主方法中调用useAddle方法
//Lambda
useAddable((int a, int b) -> {
return a+b;
});
}
private static void useAddable(Addable a){
int sum = a.add(10,20);
System.out.println(sum);
}
}
4.4、实现Comparator接口
4.4.1、匿名内部类的方式实现
List<Map<Integer, QueryUrgentQueryPullTotalRate>> result = new ArrayList<>();
//添加数据,其中Integer是月份
// 根据月份进行排序
Collections.sort(result, new Comparator<Map<Integer,
QueryUrgentQueryPullTotalRate>>() {
@Override
public int compare(Map<Integer, QueryUrgentQueryPullTotalRate> o1,
Map<Integer, QueryUrgentQueryPullTotalRate> o2) {
Integer key1 = o1.keySet().iterator().next();
Integer key2 = o2.keySet().iterator().next();
return key1.compareTo(key2);
}
});
4.4.2、lambda表达式的方式
List<Map<Integer, QueryUrgentQueryPullTotalRate>> result = new ArrayList<>();
//添加数据,其中Integer是月份
Collections.sort(result,
(Map<Integer, QueryUrgentQueryPullTotalRate> o1,
Map<Integer, QueryUrgentQueryPullTotalRate> o2) -> {
Integer key1 = o1.keySet().iterator().next();
Integer key2 = o1.keySet().iterator().next();
return key1.compareTo(key2);
});
五、Lambda表达式的省略模式
- 省略规则:
①、参数类型可以省略。但是有多个参数的情况下,不能只省略一个
②、如果参数有且仅有一个,那么小括号可以省略
③、如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
public interface Flyable{
void fly(String s);
}
- 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
public interface Addable {
int add(int x,int y);
}
/*Lambda表达式的省略模式*/
public class LambdaDemo {
public static void main(String[] args) {
//useAddable((int x,int y) -> {
// return x+y;
//});
//1.参数的类型可以省略,但是有多个参数的情况下,不能只省略一个
useAddable((x,y) -> {
return x+y;
});
//useFlyable((String s) ->{
// System.out.println(s);
//});
//2.如果参数有且只有一个,那么小括号可以省略
useFlyable( s->{
System.out.println(s);
});
//3.如果代码块的语句只有一条,可以省略大括号和分号
useFlyable(s -> System.out.println(s));
//4.如果代码块的语句只有一条,可以省略大括号和分号;如果有return,return也要省略
useAddable((x,y) -> x+y );
}
private static void useFlyable(Flyable f){
f.fly("风和日丽,晴空万里");
}
private static void useAddable(Addable a){
int sum = a.add(10,20);
System.out.println(sum);
}
}
六、Lambda表达式的注意事项
注意事项:
- ①、使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- ②、必须有上下文环境,才能推导出Lambda对应的接口
- ③、根据局部变量的赋值得知Lambda对应的接口:Runnable r = () -> System.out.println(“Lambda表达式”);
- ④、根据调用方法的参数得知Lambda对应的接口:new Thread(() -> System.out.println(“Lambda表达式”)).start();
/*Lambda表达式的注意事项*/
public class LambdaDemo {
public static void main(String[] args) {
//使用Lambda表达式必须要有接口,并且要求接口中有且仅有一个抽相方法
useInter(()->System.out.println("好好学习,天天向上"));
//必须有上下文环境,才能推导出Lambda对应的接口
Runnable r = () -> System.out.println("Lambda表达式");
new Thread(r).start();
new Thread(
() -> System.out.println("Lambda表达式")
).start();
}
private static void useInter(Inter i){
i.show();
}
}
七、Lambda表达式和匿名内部类的区别
- 所需类型不同
①、匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
②、Lambda表达式:只能是接口
abstract class Animal {
public abstract void method();
}
class Student {
public void study(){
System.out.println("爱生活,爱java");
}
}
public class LambdaDemo{
public static void main(String[] args) {
//匿名内部类
useInter(new Inter() {
@Override
public void show() {
System.out.println("接口");
}
});
useAnimal(new Animal() {
@Override
public void method() {
System.out.println("抽象类");
}
});
useStudent(new Student(){
@Override
public void study(){
System.out.println("具体类");
}
});
//Lambda
useInter(() -> System.out.println("接口"));
//useAnimal(() -> System.out.println("抽象类"));//不可以
//useStudent(() -> System.out.println("具体类"));//不可以
}
private static void useStudent(Student s){
s.study();
}
private static void useAnimal(Animal a){
a.method();
}
private static void useInter(Inter i){
i.show();
}
}
- 使用限制不同
①、如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
②、如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
public interface Inter{
void show();
void show1();
}
public class LambdaDemo{
public static void main(String[] args) {
useInter(new Inter() {
@Override
public void show() {
System.out.println("show");//👈只打印这一个show
}
@Override
public void show1() {
System.out.println("show");
}
});
}
private static void useInter(Inter i){
i.show();
}
}
- 实现原理不同
①、匿名内部类:编译之后,产生一个单独的.class字节码文件
②、Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
八、Lambda表达式的使用场景
以下是我总结的使用场景
- 集合操作
- 多线程编程
- 事件处理
- 排序
- 过滤
- 映射
- 聚合
- 函数式编程
- 数据库操作
- 并行计算
- GUI编程
- 测试
8.1、集合操作
Lambda表达式可以与Java 8的新集合操作方法(如stream()和forEach())一起使用,使集合的处理更加简单、灵活和易于读写。
例如,假设有一个字符串列表,想要对该列表中的所有元素进行大写转换并输出到控制台上,可以使用以下代码:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream().map(String::toUpperCase).forEach(System.out::println);
这里,使用了stream()方法将列表转换为一个流,然后使用map()方法将每个字符串转换为大写形式,最后使用forEach()方法将结果输出到控制台。
8.2、多线程编程
Lambda表达式可以与Java中的函数式接口一起使用,使多线程编程更加简单和可读。
例如,有一个线程,需要在后台执行某些任务,并在任务完成时通知主线程。可以使用以下代码创建一个新的线程并将任务作为Lambda表达式传递给它:
new Thread(() -> { // 执行后台任务 // ... // 通知主线程任务已完成 }).start();
这里,使用了Java中的Thread类,并将一个Lambda表达式作为参数传递给它,该表达式将在新线程中执行后台任务。
8.3、事件处理
Lambda表达式可以作为事件监听器传递给GUI组件等,使事件处理更加简单和可读。
例如,假设我们有一个按钮,需要在用户单击它时执行某些操作。可以使用以下代码将Lambda表达式作为事件监听器传递给该按钮:
button.addActionListener(event -> { // 处理按钮单击事件 // ... });
这里,使用了Java中的ActionListener接口,并将一个Lambda表达式作为参数传递给它,该表达式将在用户单击按钮时执行。
8.4、排序
Lambda表达式可以用于Java中的排序算法中,使排序更加灵活和可读。
例如,假设我们有一个Person对象的列表,需要按照年龄进行排序。可以使用以下代码将Lambda表达式作为排序算法的参数传递给Collections.sort()方法:
List<Person> people = Arrays.asList(new Person("Alice", 25), new Person("Bob", 30), new Person("Charlie", 20)); Collections.sort(people, (p1, p2) -> p1.getAge() - p2.getAge());
这里,我们使用了Java中的Collections类的sort()方法,并将一个Lambda表达式作为参数传递给它,该表达式将比较两个Person对象的年龄并返回一个整数值,以指示它们的排序顺序。
8.5、过滤
Lambda表达式可以用于过滤集合中的元素,使代码更加简单和可读。
例如,假设有一个整数列表,需要过滤掉其中的偶数。可以使用以下代码将Lambda表达式作为过滤器传递给Java中的stream()方法:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); List<Integer> oddNumbers = numbers.stream().filter(n -> n % 2 != 0). collect(Collectors.toList());
这里,使用了Java中的stream()方法将列表转换为一个流,然后使用filter()方法过滤掉其中的偶数,最后使用collect()方法将过滤后的结果转换为一个新的列表。
8.6、映射
Lambda表达式可以用于将一个集合中的元素映射到另一个集合中,使代码更加简单和可读。
例如,假设我们有一个字符串列表,需要将其中的每个字符串转换为大写形式并存储到另一个列表中。可以使用以下代码将Lambda表达式作为映射器传递给Java中的stream()方法:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<String> upperCaseNames = names.stream().map(String::toUpperCase) .collect(Collectors.toList());
这里,我们使用了Java中的stream()方法将列表转换为一个流,然后使用map()方法将每个字符串转换为大写形式,最后使用collect()方法将转换后的结果存储到一个新的列表中。
8.7、聚合
Lambda表达式可以用于聚合集合中的元素,例如,计算集合中的元素之和、平均值、最大值、最小值等。
以下是一个计算列表中元素之和的示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream().reduce(0, (a, b) -> a + b); System.out.println("Sum of numbers: " + sum);
这里,使用了Java中的stream()方法将列表转换为一个流,并使用reduce()方法计算流中元素的总和。reduce()方法接受两个参数:起始值和一个BinaryOperator类型的Lambda表达式。Lambda表达式将两个元素相加并返回它们的和。在这个例子中,将起始值设置为0,表示计算从0开始的累加和。
除了计算元素之和之外,还可以使用Lambda表达式计算元素的平均值、最大值、最小值等。以下是一个计算列表中元素平均值的示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); double average = numbers.stream().mapToInt(Integer::intValue).average().orElse(0.0); System.out.println("Average of numbers: " + average);
首先使用mapToInt()方法将流中的元素转换为int类型,然后使用average()方法计算这些元素的平均值。如果列表为空,则orElse()方法返回默认值0.0。
8.8、函数式编程
Lambda表达式可以使Java更加接近函数式编程,使代码更加简洁和易于理解。
例如,假设有一个接口,其中包含一个抽象方法,需要在程序中实现该接口并调用该方法。可以使用以下代码将Lambda表达式作为接口实现传递给该方法:
interface MyInterface { int doSomething(int x, int y); } MyInterface myLambda = (x, y) -> x + y; int result = myLambda.doSomething(3, 4);
这里,定义了一个名为myLambda的变量,它的类型是MyInterface,它接受两个整数参数并返回它们的和。然后,我们调用myLambda的doSomething()方法,并传递两个整数参数,得到它们的和并将结果存储在result变量中。
8.9、数据库操作
Lambda表达式可以与Java中的数据库操作API(如JDBC和Hibernate)一起使用,使数据库操作更加简单和可读。
例如,假设有一个数据库表,需要查询其中的数据并输出到控制台上。可以使用以下代码将Lambda表达式作为查询操作的参数传递给数据库API:
try (Connection conn = DriverManager.getConnection(url, username, password); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM my_table")) { while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); System.out.println("id: " + id + ", name: " + name); } }
这里,使用了Java中的JDBC API,并将一个Lambda表达式作为查询操作的参数传递给executeQuery()方法,该表达式将处理查询结果集并输出到控制台上。
8.10、并行计算
Lambda表达式可以与Java中的并行计算API(如Java 8中的Parallel Streams和Fork/Join框架)一起使用,使计算更加高效和快速。
例如,假设有一个大型的整数列表,需要计算其中所有元素的平方和。可以使用以下代码将Lambda表达式作为计算器传递给Java 8中的Parallel Streams API:
List<Integer> numbers = new ArrayList<>(); for (int i = 1; i <= 1000000; i++) { numbers.add(i); } long sum = numbers.parallelStream().mapToLong(i -> i * i).sum(); System.out.println("Sum of squares: " + sum);
这里,使用了Java 8中的Parallel Streams API,它将列表转换为一个并行流,并使用mapToLong()方法计算每个元素的平方值,最后使用sum()方法将它们加起来得到总和。在此过程中,计算将在多个线程上并行执行,从而提高了计算效率。
8.11、GUI编程
Lambda表达式可以与Java中的GUI编程框架(如JavaFX和Swing)一起使用,使GUI编程更加简单和可读。
例如,假设有一个JavaFX应用程序,需要在用户单击按钮时执行一些操作。可以使用以下代码将Lambda表达式作为事件处理器传递给JavaFX框架:
Button button = new Button("Click me!"); button.setOnAction(event -> System.out.println("Button clicked!"));
这里,使用了JavaFX框架,并将一个Lambda表达式作为按钮的事件处理器传递给setOnAction()方法。该表达式将在用户单击按钮时执行,并输出一条消息到控制台上。
8.12、测试
Lambda表达式可以用于编写更加简洁和可读的单元测试代码,使测试更加容易理解和维护。
例如,假设有一个函数,需要进行单元测试以确保其正确性。可以使用以下代码将Lambda表达式作为测试断言传递给JUnit测试框架:
@Test public void testMyFunction() { int result = myFunction(2, 3); assertEquals(6, result, () -> "myFunction(2, 3) should return 6"); }
这里,使用了JUnit测试框架,并将一个Lambda表达式作为测试断言传递给assertEquals()方法。该表达式将在测试失败时计算并返回错误消息,以帮助定位和修复问题。
九、总结
- Lambda表达式是Java 8中最强大和灵活的新特性之一,它可以用于各种不同的编程任务,使代码更加简单、灵活和易于读写。Lambda表达式的语法非常简洁,通常由一个参数列表、一个箭头符号和一个表达式主体组成。
- Lambda表达式可以用于各种不同的编程任务,包括函数式编程、集合处理、数据库操作、Web开发、并行计算、GUI编程、测试等。使用Lambda表达式可以使代码更加简单、灵活和易于读写,并帮助开发人员减少代码的冗余和重复。
- 除了Lambda表达式之外,Java 8还引入了其他重要的新特性,例如Stream API、接口默认方法、方法引用、Optional类型等。这些新特性一起使Java变得更加现代化、强大和易于使用。
总之,Lambda表达式是Java编程中不可或缺的一部分,它使Java变得更加现代化、灵活和强大,并且是Java 8中最重要的新特性之一。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)