之前在自学的时候接触过 Lambda 表达式,但那会其实用的并不多,对于其也是大概有个了解,但在阅读公司代码的时候,发现在对于集合的处理时都是转成stream
流再通过foreach
结合 Lambda 表达式进行处理的,所以打算再重新学学这部分的内容
先来看看网上对于它的定义
Lambda 表达式是 Java 8 的重要更新,它支持将代码块作为方法参数、允许使用更简洁的代码来创建只有一个抽象方法的接口的实例。 Lambda 表达式的主要作用就是可以用于简化创建匿名内部类对象,Lambda 表达式的代码块将会用于实现抽象方法的方法体,Lambda 表达式就相当于一个匿名方法
结合上面的描述可以得到一个结论,Lambda 表达式在匿名函数以及集合的 stream 操作有着重要的作用
public class TestLambda { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); thread.close(); } } class MyRunnable implements Runnable{ @Override public void run() { System.out.println("Hello"); } }
现在将代码简化一下,使用匿名内部类来实现 Runnable 接口
public class TestLambda { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("Hello"); } }); thread.start(); } }
然而上面这段代码还不是最简单的,再看下面这个
public class TestLambda { public static void main(String[] args) { new Thread(() -> System.out.println("Hello")).start(); } }
这样对比下来,发现使用 Lambda 表达式确实能极大的让代码更简洁
Lambda 表达式由三部分构成
总结一下就是:
实现的这个接口中的抽象方法中的形参列表 -> 抽象方法的处理
public interface MyInterface { public abstract void show(int a,int b); }
public class MyTest { public static void main(String[] args) { MyInterface myInterface = new MyInterface() { @Override public void show(int a, int b) { System.out.println(a + b); } }; //简写1:方法名可以自己推断出来 MyInterface myInterface1 = (int a, int b) -> { System.out.println(a + b); }; //简写2:可以省略形参列表中的形参类型 MyInterface myInterface2 = (a, b) -> { System.out.println(a + b); }; //简写3:如果抽象方法中只有一行代码,可以省略方法体的大括号,当然,如果不止一行,就不能省略 MyInterface myInterface3 = (a, b) -> System.out.println(a + b); } }
public interface MyInterface { public abstract int test(int a,int b); }
public class MyTest { public static void main(String[] args) { MyInterface test1 = new MyInterface() { @Override public int test(int a, int b) { return a - b; } //简写1: MyInterface test3 = (a, b) -> {return a - b;}; //简写3:这个有返回值的方法,不能直接去掉大括号,还需要去掉return关键字 MyInterface test4 = (a, b) -> a - b; } }
public interface MyInterface { public abstract int show(int a); }
public class MyTest { public static void main(String[] args) { //形参列表中只有一个参数,可以去掉形参的括号 MyInterface myInterface = a -> a-20; } }
public class Main { public static void main(String[] argv) { engine((x,y)-> x + y); engine((x,y)-> x * y); engine((x,y)-> x / y); engine((x,y)-> x % y); } private static void engine(Calculator calculator){ int x = 2, y = 4; int result = calculator.calculate(x,y); System.out.println(result); } } @FunctionalInterface interface Calculator{ int calculate(int x, int y); }
在以往的资料上,他们大多爱说这么一句话
Lambda 表达式的类型,也被称为目标类型(target type)。Lambda 表达式的目标类型必须是函数式接口(functional interface)
我相信很多想要了解Lambda表达式的人一查资料,发现这么一句话,肯定是一头雾水的。暂且先不讨论这句话的含义,但有一点是可以达成共识的,那就是:Lambda表达式和函数接口一定存在着千丝万缕的关联
事实上确实是这样的,函数式接口和Lambda表达式同为Java8的新特性,它大致可以用以下的文字来形容
函数式接口就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。可以通过 Lambda 表达式来创建该接口的对象
通过查询 Java 8 的 API 文档,可以发现大量的函数式接口,例如熟知的 Runnable 接口就是一个函数式接口(其中只有一个抽象的run()
方法)。Java 8 还提供了@FunctionalInterface
注解,该注解用于告诉编译器校验接口必须是函数式接口,否则就报错
由于 Lambda 表达式的结果就是被当做对象/实例,因此,可以使用 Lambda 表达式进行赋值
但是下面这个是个错误的实例
Object obj = () -> { for (int i = 0; i < 100; i++) { System.out.println(i); } };
其实如果已经了解了 Lambda 表达式与函数式接口的关系,大概都不会犯这个错,但考虑到之前有提到过“Lambda 表达式的结果就是被当成对象/实例的”,所以还是把它拿出来说说。这个程序执行之后会报Target type of a lambda conversion must be an interface
,将 Lambda 表达式赋值给 Object 类型的变量,编译器只能推断出它的表达类型为 Object,而 Object 并不是函数式接口,因此就报错了
为了保证 Lambda 表达式的目标类型是明确的函数式接口,有如下三种常见方式:
那么以上的错误就可以更改成
//这里就使用函数式接口 Runnable 对 Lambda 表达式进行了强制转换 Object obj = (Runnable)() -> { for (int i = 0; i < 100; i++) { System.out.println(i); } };
在使用 Lambda 表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在 Lambda 中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?
来看例子:冗余的 Lambda 场景
@FunctionalInterface public interface Printable { /** * 接收一个字符串参数,打印显示它 * @param str 字符串 */ public abstract void print(String str); } ------------------------------------------------------- public class Demo01 { public static void main(String[] args) { printString(s -> System.out.println(s)); } private static void printString(Printable printable) { printable.print("Hello, World!"); } }
这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 System.out 对象中的println(String)
方法。既然 Lambda 希望做的事情就是调用println(String)
方法,那何必自己手动调用呢?
解决方式
public class Demo02 { public static void main(String[] args) { printString(System.out::println); } private static void printString(Printable printable) { printable.print("Hello, World!"); } }
双冒号::
为引用运算符,而它所在的表达式被称为方法引用。如果 Lambda 要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为 Lambda 的替代者
例如上例中,System.out
对象中有一个重载的println(String)
方法恰好就是我们所需要的。那么对于printString
方法的函数式接口参数,对比下面两种写法,完全等效
// Lambda表达式写法 s -> System.out.println(s); // 方法引用写法 System.out::println
Lambda 中传递的参数,一定是方法引用中的那个方法可以接收的类型,否则会出现编译错误。上例中 println 方法可以接收 String 类型的参数,所以才有两种等价的写法
假如我们已经在一个类中定义了一个方法的具体实现
public class MethodRefObject { public void printUpperCase(String str) { System.out.println(str.toUpperCase()); } }
然后函数式接口定义如下
@FunctionalInterface public interface Printable { public abstract void print(String str); }
拿到参数之后经 Lambda 之手,继而传递给toUpperCase()
方法去处理
public class Demo { public static void main(String[] args) { printString(s -> s.toUpperCase()); } private static void printString(Printable lambda) { lambda.print("Hello"); } }
折腾了一大圈发现,其实已经有方法实现了 Lambda 表达式想要实现的功能
这个时候,当需要使用这个 printUpperCase 成员方法来替代 Printable 接口的 Lambda 的时候,已经具有了 MethodRefObject 类的对象实例,则可以通过对象名引用成员方法
public class Demo { public static void main(String[] args) { MethodRefObject obj = new MethodRefObject(); printString(obj::printUpperCase); } private static void printString(Printable lambda) { lambda.print("Hello"); } }
Math 类中已经存在了静态方法abs()
,现在需要通过 Lambda 去调用这个方法,有两种表示方式
第一种,使用函数式接口
@FunctionalInterface public interface CalculationAble { int calculation(int num); }
public class Demo { public static void main(String[] args) { method(-666, n -> Math.abs(n)); } private static void method(int num, CalculationAble lambda) { System.out.println(lambda.calculation(num)); } }
第二种,可以使用类名来引用静态方法
public class Demo { public static void main(String[] args) { method(-666, Math::abs); } private static void method(int num, CalculationAble reference) { System.out.println(reference.calculation(num)); } }
函数式接口
@FunctionalInterface public interface GreetAble { void greet(); }
父类
public class Human { public void sayHello() { System.out.println("Hello!"); } }
子类
public class Man extends Human { @Override public void sayHello() { System.out.println("hey,bro!"); } /** * 定义方法method,参数传递GreetAble接口 * @param g 这里传入的是Lambda表达式 */ public void method(GreetAble g) { g.greet(); } /** * 调用method方法,使用Lambda表达式 */ public void show(){ // 创建Human对象,调用sayHello方法 method(() -> { new Human().sayHello(); }); // 简化Lambda method(() -> new Human().sayHello()); // 使用super关键字代替父类对象 method(() -> super.sayHello()); // 再简化 method(supper::sayHello); } }
函数式接口
@FunctionalInterface public interface RichAble { void buy(); }
一个方法要以函数式接口为参数
public class Husband { /** * 结婚 * @param lambda 函数式接口,买东西 */ private void marry(RichAble lambda) { lambda.buy(); } /** * 要开心 */ public void beHappy() { marry(() -> System.out.println("买套房子")); } }
开心方法 beHappy 调用了结婚方法 marry ,后者的参数为函数式接口 Richable ,所以需要一个 Lambda 表达式。 但是如果这个 Lambda 表达式的内容已经在本类当中存在了,则可以对 Husband 丈夫类进行修改
public class Husband { /** * 买房子 */ private void buyHouse() { System.out.println("买套房子"); } /** * 结婚 * @param lambda 函数式接口,买东西 */ private void marry(RichAble lambda) { lambda.buy(); } /** * 要开心 */ public void beHappy() { marry(() -> this.buyHouse()); } }
如果希望去掉 Lambda 表达式,可以使用 this 去引用成员方法
public class Husband03 { /** * 买房子 */ private void buyHouse() { System.out.println("买套房子"); } /** * 结婚 * @param lambda 函数式接口,买东西 */ private void marry(RichAble lambda) { lambda.buy(); } /** * 要开心 */ public void beHappy() { marry(this::buyHouse); } }
方法引用其实还好,至少方法名是固定的。但是构造器的名字与类名相同,根本不固定,所以可以通过类名称::new
的方式引用
一个简单的类
public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } }
函数式接口
@FunctionalInterface public interface PersonBuilder { /** * 创建 Person 对象 * @param name Person对象名 * @return Person对象 */ Person buildPerson(String name); }
要使用这个接口就需要通过 Lambda 表达式
public class Demo { public static void main(String[] args) { printName("Tom", (name) -> new Person(name)); } public static void printName(String name, PersonBuilder builder) { System.out.println(builder.buildPerson(name).getName()); } }
如果换成类构造器引用,就得这么写
public class Demo { public static void main(String[] args) { printName("Tom", Person::new); } public static void printName(String name, PersonBuilder builder) { System.out.println(builder.buildPerson(name).getName()); } }
如果是数组,那写法就有些区别了
函数式接口
@FunctionalInterface public interface ArrayBuilder { /** * 创建数组的函数式接口 * @param length 数组长度 * @return 存储int类型的数组 */ int[] buildArray(int length); }
使用 Lambda 表达式应用接口
public class Demo { public static void main(String[] args) { int[] array = initArray(10, length -> new int[length]); } private static int[] initArray(int length, ArrayBuilder builder) { return builder.buildArray(length); } }
使用构造器引用应用接口
public class Demo { public static void main(String[] args) { int[] array = initArray(10, int[]::new); } private static int[] initArray(int length, ArrayBuilder builder) { return builder.buildArray(length); } }
很显然,数组的构造器引用就不能通过类名了,这个需要注意
看到过其他人的文章中有一句话形容 Lambda 表达式非常贴切
Lambda 表达式的原则是“可推导就是可省略”
关于 Lambda 表达式我认为我学习到的内容才是冰山一角,很多东西可能还需要在以后的实践过程中再去熟悉再去体会,后续有其他关于 Lambda 的想要分享的内容还是会在博客中分享
参考内容
Java基础——Lambda表达式
理解 Java 方法引用(方法引用符:“双冒号 :: ”)