Lambda表达式简介
Lambda表达式用于将代码块作为参数传入方法中,有的时候,在你创建某个方法的时候,你并不知道方法应该对参数进行怎样的操作,只有到了使用的时候,才能真正确定。这种情境下,就可以借助接口来实现,具体可以使用接口的实现类、匿名内部类和Lambda表达式来实现:
//Command接口用以封装方法的”处理行为“
interface Command{
// process代表了处理行为,具体处理行为是怎样的,现在无法确定,需要通过实现类、匿名内部类或Lambda表达式三种方式实现
void process(int[] target);
}
//ProcessArray类用接口中的处理方法来处理数组
class ProcessArray{
public void process(int[] target,Command cmd) {
cmd.process(target);
}
}
Command接口中封装了一个process方法,用以处理数据,在定义该接口时,还未确定应该对数据进行怎样的处理。
ProcessArray类中封装了一个process方法,这个方法调用Command接口中的process方法来处理数据。以下通过三种方式来实现将代码块作为参数传入方法:
借助接口的实现类实现:
class AddCommand implements Command{
public void process(int[] target) {
int sum = 0;
for(int tmp : target) {
sum += tmp;
}
System.out.println("数组元素总和为:" + sum);
}
}
public class CommandTest {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] target = {1,2,3,4};
pa.process(target, new AddCommand());
}
}
借助匿名内部类实现
public class CommandTest {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] target = {1,2,3,4};
pa.process(target, new Command() {
public void process(int[] target) {
int sum = 0;
for(int tmp:target) {
sum += tmp;
}
System.out.println("数组元素总和为:"+sum);
}
});
}
}
借助Lambda表达式实现:
public class CommandTest {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] target = {1,2,3,4};
pa.process(target, (int[] targetProcess)->{
int sum = 0;
for(int tmp : targetProcess) {
sum += tmp;
}
});
}
}
相比于其他两种方式,借助Lambda表达式更为简洁。
Lambda表达式由三部分组成:
- 形参列表:Lambda表达式中,允许省略形参类型,如果只有一个形参,可以直接省略形参。
- 箭头:->,英文字符
- 代码块:若代码块只包含一条语句,则可以省略代码块的{}。
具体如下:
interface Eatable{
void taste();
}
interface Flyable{
void fly(String weather);
}
interface Addable{
int add(int a,int b);
}
public class LambdaQs {
public void eat(Eatable e) {
System.out.println("Lambda表达式被当作Eatable接口对象");
e.taste();
}
public void drive(Flyable f) {
System.out.println("Lambda表达式被当作Flyable接口对象");
f.fly("晴天");
}
public void test(Addable add) {
System.out.println("Lambda表达式被当作Addable接口对象");
System.out.println("1+1="+add.add(1, 1));
}
public static void main(String[] args) {
LambdaQs lq = new LambdaQs();
//Lambda表达式只有一个形参,一条语句,可以省略形参,省略形参的(),省略{}
lq.eat(()->System.out.println("牛肉吃起来不错"));
lq.drive(weather ->
{
System.out.println("今天的天气是"+weather);
System.out.println("飞机飞行平稳");
});
lq.test((a,b)->a+b);
}
}
上述代码的输出结果为:
Lambda表达式被当作Eatable接口对象
牛肉吃起来不错
Lambda表达式被当作Flyable接口对象
今天的天气是晴天
飞机飞行平稳
Lambda表达式被当作Addable接口对象
1+1=2
可见,Lambda表达式到底会被当作何种类型对象,取决于运行环境。
Lambda表达式与函数式接口
函数式接口就是只包含一个抽象方法的接口,那么函数式接口和Lambda有什么联系呢?请看:
public class lambdaTest {
//下方代码运行正常
Runnable r = () ->{
System.out.println("成功执行Lambda表达式");
};
//下方代码报错
Object obj = ()->{
System.out.println("成功执行Lambda表达式");
};
}
Runnable是JAVA自带的函数式接口,Object不是函数式接口,而是所有类的父类。为什么一个正常运行?一个报错呢?
Lambda表达式本身没有方法名,所以Lambda表达式的目标类型只能是明确的函数式接口,因为如果一个接口包含多个方法的话,程序就不知道Lambda表达式到底执行的是哪个方法了,所以会报错。
综上,Lambda表达式本质上就是使用更简洁的语法来创建函数式接口的实例,相比于实现类、匿名内部类更为简洁方便。
要保证Lambda表达式的目标类型是明确的函数式接口,可以借助以下三种方法:
- 将Lambda表达式赋值给函数式接口类型的变量
- 将Lambda表达式作为函数式接口类型的变量,传递给某个方法
- 对Lambda表达式进行强制类型转换,例如上述报错代码可修改为:
Object obj = (Runnable)()->{
System.out.println("成功执行Lambda表达式");
};
在java.util.function包中预定义了大量函数式接口,大致包括以下四类:
- XxxFunction:包含apply()方法,通常用于对数据进行转换处理
- XxxConsumer:包含accept()方法,与apply()方法类似,但不会返回处理结果
- XxxPredicate:包含test()方法,判断参数是否满足某种条件,返回boolean值
- XxxSupplier:包含getAsXxx()方法,根据某种逻辑算法,返回一个数据
Lambda表达式中的方法引用与构造器引用
如果Lambda表达式的代码块只有一条代码,就可以在代码块中使用方法引用和构造器引用,是得Lambda表达式更为简洁,常见有四种引用方式:引用类方法、引用特定对象的实例方法、引用某类对象的实例方法、引用构造器,具体如下:
1.引用类方法,代码如下:
interface Converter{
Integer convert(String a);
}
public class lambdaTest {
public static void main(String[] args) {
Converter con = a -> Integer.valueOf(a);
int b = con.convert("71");
//下方代码输出71
System.out.println(b);
}
}
上方代码中,定义了函数式接口converter用以转换数据类型,在main函数中,借助Lambda表达式来实现接口。该Lambda表达式仅包含一行代码,可以省略花括号,并且可以引用类方法,也就是Integer类的valueof方法。可见,最终把字符串转化为了整数。调用代码可以进一步简化:
Converter con = Integer::valueOf;
2.引用特定对象的实例方法,代码如下:
interface Converter{
Integer convert(String a);
}
public class lambdaTest {
public static void main(String[] args) {
String a = "The Lakers are the championship";
Converter con = from -> a.indexOf(from);
int b = con.convert("L");
//下方输出4,因为L在a字符串中的索引是4
System.out.println(b);
}
}
与1中不同的是,这里Lambda表达式调用的并非类方法,而是对象的实例方法,也就是代码中的a的indexof方法。那么在后面调用接口对象的indexof方法时,就会相应的去a字符串中的对应字母的索引。类似的,调用代码可进一步简写为:
Converter con = a::indexOf(from);
3.引用某类对象的实例方法,代码如下:
interface MyTest{
String test(String a, int b, int c);
}
public class lambdaTest {
public static void main(String[] args) {
MyTest mt = (a,b,c) -> a.substring(b,c);
String str = mt.test("The Lakers are the championship", 2, 8);
System.out.println(str);
}
}
与2中不同,2是引用特定对象,3是引用某类对象,具体对象需要你在引用的时候再指定。类似的,调用代码可进一步简写为:
MyTest mt = String::substring;
此时,函数式接口中,被实现方法的第一参数作为调用者,后面的全部参数传递给该方法作为方法参数。
4.引用构造器,代码如下
class lambdaClass{
public lambdaClass(String a) {
System.out.println("构造器中参数为"+a);
}
}
public class lambdaTest {
public static void main(String[] args) {
MyTest mt = (String a) -> new lambdaClass(a);
//下方代码输出:构造器中参数为a
mt.test("a");
}
}
上方lambda表达式中,引用的就是lambdaClass类的构造器。可简化为:
MyTest mt = lambdaClass :: new;
Lambda表达式和匿名内部类
可见,lambda表达式可以视作匿名内部类的简化,但是二者存在区别,如下:
- lambda表达式只能为函数式接口创建实例,但匿名内部类无此限制
- 匿名内部类可以为抽象类或普通类创建实例,但lambda表达式不可以
- 匿名内部类可以调用接口中定义的默认方法,但lambda表达式不可以