Java lambda表达式

这篇博客介绍了Java中的Lambda表达式,包括其语法、函数式接口、方法引用、构造器引用、变量作用域以及Comparator的使用。Lambda表达式简化了代码,特别是与函数式接口结合时,如Comparator,可以用于定制排序。方法引用提供了更简洁的代码表示,如对象::实例方法、Class::instanceMethod和Class::staticMethod。同时,博客还讨论了lambda表达式如何捕获和处理外部变量,并强调了它们在延迟执行和多线程场景中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


lambda表达式是一个可传递的代码块,传递之后可以被执行多次。

最简单的例子是排序时传入的定制比较器Comparator:

class LenComparator implements Comparator<String>
{
	public int compare(String first, String second){
		return first.length() - second.length();
	}
}
...
Arrays.sort(strings, new LenComparator());

Arrays.sort(strings, new Comparator<String>() {
	@Override
	public int compare(String o1, String o2) {
		return o1.length() - o2.length();
	}
});

不能直接传递代码,Java是面向对象语言,要构造一个对象,将需要执行的方法包含在对象的方法中。

这种直接处理代码块的方式称为函数式编程

语法

考虑这个传递代码的过程,我们实际上只需要两个要素,变量类型和表达式,而lambda表达式也是这样设计的,包含参数、箭头->以及表达式。上面的代码改写为(至于为什么能用一个lambda表达式替换comparator对象在函数式接口有解释):

Arrays.sort(strings, (String o1, String o2) -> (o1.length() - o2.length()));

如果用来计算的代码太长,用大括号

(String o1, String o2) -> {
	if(o1.length() > o2.length()) return 1;
	else if(o1.length() == o2.length()) return 0;
	else return -1;
}

没有参数时,

() -> { for(int i = 100; i >= 0; i--) System.out.println(i); }

编译器可以推导出参数类型时可以省略,

Arrays.sort(strings, (o1, o2) -> (o1.length() - o2.length()));

如果只有一个参数且参数类型可以被推导,小括号也可以省略,

ActionListener listener = event -> System.out.println("The time is + Instant.ofEpochMilli(event.getWhen()));

lambda表达式不需要指定返回值,由编译器推导。

函数式接口

lambda表达式和封装代码的接口,比如ActionListener和Comparator,是兼容的。对于只有一个抽象方法的接口,需要这种接口时可以用一个lambda表达式替换,这种接口称为函数式接口,Comparator就是一个函数式接口。

java.util.function 包定义了很多通用函数式接口,但我现在还没有接触到,之后有机会再补充。

方法引用

基本用法

如果一个lambda表达式用了已经存在的方法,方法引用可以替代lambda表达式简化代码。

上面按照字符串长度对数组排序用方法引用可以改写为:

Arrays.sort(strings,Comparator.comparingInt(String::length));

更简单的例子是忽略大小写对字符串进行排序:

Arrays.sort(strings, String::compareToIgoreCase);

方法引用主要有三种情况:

  1. object::instanceMethod
  2. Class::instanceMethod
  3. Class::staticMethod

第一种:等价于传递参数的lambda表达式。比如System.out::println,对象是System.out,实际上就是给这个对象的println方法传递一个参数,相当于lambda表达式x -> System.out.println(x)

第二种:我们知道,Java中方法只有两种:类的静态方法和对象的实例方法,所以在没有对象且方法不是静态方法时,需要隐式的给出一个对象,比如String::compareToIgnoreCase相当于lambda表达式(x,y) -> x.compareToIgnore(y)

第三种:不需要对象,所以所有参数都会传递到静态方法,如Math::pow等价于(x,y) -> Math.pow(x,y)

专门用于方法引用的方法

有些专门用于方法引用的方法,比如Objects类的isNull方法,测试obj == null比调用这个方法Objects.isNull(obj)更直观,但这样的方法可以用方法引用传递到任何有Predicate参数的方法,比如:

ArrayList<Integer> list = new ArrayList<>();
list.add(null);
list.add(1);
list.removeIf(Objects::isNull);

构造器引用

将方法引用中的方法名换成new就是构造器引用,例如要将一个字符串列表转为一个Integer对象列表,需要对每个字符串调用Integer的构造器:

ArrayList<String> strings = new ArrayList<>();
strings.add("1");
Stream<Integer> stream = strings.stream().map(Integer::new);
List<Integer> integers = stream.collect(Collectors.toList());
System.out.println(integers.get(0));

另一个类似的例子,将一个字符串列表转为一个Person对象列表,对每个字符串调用Person的构造器:

static class Person{
	String name;
	public Person() {}
	
	public Person(String name) {
		this.name = name;
	}
}
...
ArrayList<String> names = new ArrayList<>();
names.add("charlie");
Stream<Person> stream1 = names.stream().map(Person::new);
List<Person> personList = stream1.collect(Collectors.toList());
System.out.println(personList.get(0).name);

如果有多个构造器,编译器会根据上下文推导。

变量作用域

lambda表达式可以访问外围方法或类中的参数或变量,实际上就是闭包(closure)。

public static void repeatMessage(String text, int delay)
{
	ActionListener listener = event ->
	{
		System.out.println(text);
	};
	new Timer(delay, listener).start();
}

在执行这个lambda表达式时,方法repeatMessage可能已经返回了,此时这个方法的参数也不复存在,但lambda表达式能捕获需要的变量,相当于把变量复制到一个实例对象里。但只能捕获引用值不会改变的变量,不管这个变量是在lambda表达式内部还是外部被改变,也就是初始化之后就不会再赋新值。

处理lambda表达式

使用lambda表达式的最重要的原因是可以延迟执行代码,比如触发了一定的条件再执行,如果总是可以直接执行代码那也就不需要它了。延迟执行代码的场景有很多:

  1. 在单独的线程中运行代码
  2. 多次运行代码
  3. 在合适的位置运行代码(排序时的比较)
  4. 在触发某种情况才执行代码(按钮、定时)

一个简单的例子:重复打印字符串,将这个动作和重复次数传递到repeat方法,为了接受lambda表达式,需要用函数式接口。

static void repeat(int n, Runnable action) 
{
	for (int i = 0; i < n; i++) {
		action.run();
	}
}
...
repeat(10, () -> System.out.println("hello");

Runnable接口没有参数,也没有返回值,其抽象方法名为run,调用这个抽象方法就会执行lambda表达式。

如果给lambda表达式传入一个表示次数的参数:

static void repeat(int n, IntConsumer action) 
{
	for (int i = 0; i < n; i++) {
		action.accept(i);
	}
}
...
repeat(10, i -> System.out.println(i + "times");

IntConsumer接口可以处理一个整数。

还有很多其他的函数式接口用于处理不同的参数和返回值。

Comparator

Comparator接口包含很多静态方法可以用于lambda表达式和方法引用。

comparing方法将要比较多对象提取出一个key,对这个key进行比较。比如按名字字典顺序对Person数组进行排序。

Person[] people = new Person[]{new Person("William Re"), new Person("Alice Sun")};
Arrays.sort(people, Comparator.comparing(Person::getName));

后面还可以接thenComparing方法,处理比较结果相同的情况。

Arrays.sort(people, Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName));

根据字符串长度排序:

Arrays.sort(people, Comparator.comparing(Person::getName), (s, t) -> Integer.compare(s.length(), t.length());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值