Java Lambda

Lambda 允许把函数作为方法的参数,可以使代码变得更加简洁紧凑。

一、通过示例了解 lambda 的好处

1. 需求

假设有这样一个需求:

某个方法需要完成某一个行为,但该行为无法事先确认,应该在调用时再确定。

2. 实现-接口实现类

定义接口,用于放置”虚拟方法”

1
2
3
public interface Command {
void fun();
}

定义方法在方法中接收”接口的实现对象”并通过它调用”虚拟方法”

1
2
3
4
5
public class CommandManager {
public void fun(Command command) {
command.fun();
}
}

定义接口实现类,实现接口

1
2
3
4
5
6
public class MyCommand implements Command{
@Override
public void fun() {
System.out.println("Hello World!");
}
}

调用方法,传入接口实现类的实例

1
2
3
4
5
public static void main(String[] args) {
CommandManager commandManager = new CommandManager();
MyCommand myCommand = new MyCommand();
commandManager.fun(myCommand);
}

3. 简化-匿名内部类

定义接口,用于放置”虚拟方法”

1
2
3
public interface Command {
void fun();
}

定义方法,在方法中接收”接口的实现对象”并通过它调用”虚拟方法”

1
2
3
4
5
public class CommandManager {
public void fun(Command command) {
command.fun();
}
}

调用方法,传入实现了接口的匿名内部类

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
CommandManager commandManager = new CommandManager();
commandManager.fun(new Command() {
@Override
public void fun() {
System.out.println("Hello World");
System.out.println(" -- 匿名内部类");
}
});
}

4. 简化-Lambda

定义接口,用于放置”虚拟方法”

1
2
3
public interface Command {
void fun();
}

定义方法,在方法中接收”接口的实现对象”并通过它调用”虚拟方法”

1
2
3
4
5
public class CommandManager {
public void fun(Command command) {
command.fun();
}
}

调用方法,传入 Lambda 表达式,在其中给出重写方法的形参及方法体

1
2
3
4
5
6
7
public static void main(String[] args) {
CommandManager commandManager = new CommandManager();
commandManager.fun(() -> {
System.out.println("Hello World");
System.out.println(" -- Lambda");
});
}

5. 对比

(1) 接口实现类

1
2
3
4
5
6
7
8
9
10
public class MyCommand implements Command{
@Override
public void fun() {
System.out.println("Hello World!");
}
}


MyCommand myCommand = new MyCommand();
commandManager.fun(myCommand);

(2) 匿名内部类

1
2
3
4
5
6
7
commandManager.fun(new Command() {
@Override
public void fun() {
System.out.println("Hello World");
System.out.println(" -- 匿名内部类");
}
});

(3) Lambda

1
2
3
4
commandManager.fun(() -> {
System.out.println("Hello World");
System.out.println(" -- Lambda");
});

(4) 总结

  • 匿名内部类相比于接口实现类,减少了声明类实例化类的代码
  • Lambda 相比于匿名内部类,减少了除形参和方法体之外的大量代码
  • 使用 Lambda 使代码更加简洁

二、Lambda

1. 语法

1
2
3
(形参) ->{
方法体
}

2. 使用前提

Lambda 表达式的使用前提是:

  • 有一个接口

  • 接口中有且仅有一个抽象方法

  • Lambda 表达式中的形参与接口中的形参相同

  • 使用接口类型的变量进行接收

    • 接口 变量名 = () -> { ··· }
      
      1
      2
      3
      4
      5

      * ```java
      fun (接口 变量名) { ··· }

      fun(() -> { ··· })
      有且仅有一个抽象方法的接口:
    1
    2
    3
    public interface Runable {
    void run();
    }

    接收接口的实例,并调用其方法的方法:

    1
    2
    3
    void fun(Runable runable) {
    runable.run();
    }

    调用方法,传入 Lambda 表达式:

    1
    2
    3
    4
    5
    fun(
    () -> {
    System.out.println("我跑呀跑");
    }
    );

3. 省略写法

  • 形参的类型可以省略

    1
    2
    3
    (x, y) -> {

    }
  • 若形参只有一个,则小括号可以省略

    1
    2
    3
    x -> {

    }
  • 若方法体中的语句只有一条,则大括号和分号可以省略

    1
    x -> System.out.println(x)
  • 若方法体中的语句只有一条,且是 return,则大括号、分号和 return 都可以省略

    1
    x -> 2 * x

三、方法引用和构造器引用

1. 说明

如果方法体中的语句只有一条,且此语句是调用某一类/某一对象的方法,可以使用方法引用和构造器引用,它比 Lambda 表达式更加简洁。

2. 示例

(1) 引用类方法

1
类名::方法名

对应的 Lambda 表达式

1
(形参) -> 类名.方法名(形参)

(2) 引用实例方法

1
对象名::方法名

对应的 Lambda 表达式

1
(形参) -> 对象名.方法名(形参)

(3) 引用某类对象的实例方法

类名填写第一个参数的类名,实例方法填写第一个参数的成员方法

1
类名::实例方法

对应的 Lambda 表达式

调用传入的第一个参数的实例方法

1
(形参1, 形参列) -> 形参1.方法名(形参列)

接口:

1
2
3
4
public interface MyInterface {
String testFun(String string, String string2);
}

以接口对象作为参数,并调用接口方法的方法:

1
2
3
4
static void fun(MyInterface myInterface) {
String string = myInterface.testFun("你好", "世界");
System.out.println(string);
}

通过 Lambda 传入接口对象并调用方法:

1
2
3
4
5
public static void main(String[] args) {
fun((String string, String string2) -> {
return string.concat(string2);
});
}

注意到此代码符合方法应用的条件,并且此代码中以第一个参数为调用者,调用其实例方法,因此可以改写为以下形式:

1
2
3
public static void main(String[] args) {
fun(String::concat);
}

(4) 引用构造器

1
类名::new

对应的 Lambda 表达式

1
(形参) -> new 类名(形参)

参考