Lambda表达式的方法引用

Lambda表达式的方法引用


方法引用其实就是把一个已经存在的方法拿来用,不需要再写一遍代码。可以理解为:“别写重复的东西,直接拿来用”。比如,如果你要做一件事情,而某个方法已经写好了,那你就直接用它的名字就行,不用再写一个lambda表达式。

  1. 静态方法引用
    想象你要把列表里的每个数字都打印出来,原来你可能会这样写:

    1
    list.forEach(n -> System.out.println(n));

    这其实就是每次把n传到System.out.println()方法里。
    println这个方法已经存在了,你可以直接用它的名字,像这样:

    1
    list.forEach(System.out::println);

    这样代码就更简洁了!不需要重复地写n ->,因为Java知道要用println这个方法。

  2. 实例方法引用
    假设你有一个对象apple,它有一个方法叫eat()。原来你可能会这么写:

    1
    list.forEach(item -> apple.eat(item));

    但其实apple.eat()已经写好了,所以你可以这样直接引用:

    1
    list.forEach(apple::eat);
  3. 类方法引用
    如果你有一个方法需要调用列表中每个对象的某个功能,比如字符串转换成大写:

    1
    list.forEach(str -> str.toUpperCase());

    因为toUpperCase()这个方法已经写好了,你可以直接用它的名字:

    1
    list.forEach(String::toUpperCase);
  4. 构造方法引用
    当你需要创建对象时,像这样:

    1
    Supplier<List<String>> supplier = () -> new ArrayList<>();

    new ArrayList()这个构造器已经写好了,所以可以直接用方法引用:

    1
    Supplier<List<String>> supplier = ArrayList::new;

lambda表达式只是简单调用一个已有方法时,就可以用方法引用

用方法引用会让代码更简洁、少重复,不需要每次都写lambda表达式。

在Lambda表达式的方法引用中,主要有实例方法引用、静态方法引用、特殊方法引用和构造方法引用、数组引用这五种情况

1. 实例方法引用


实例方法引用是指使用某个对象的实例方法,而不需要显式地写出lambda表达式。用实例方法引用,可以让代码更加简洁。当你用实例方法引用时,它会自动把参数传给该方法,前提是这个方法已经写好了,并且适合传入的参数

1.1 实例方法引用的使用

假设我们有一个对象apple,它有一个eat()方法,用来吃水果。

1
2
3
4
5
class Apple {
public void eat(String fruit) {
System.out.println("Eating " + fruit);
}
}

1.1.1 使用lambda表达式

原本可能会用lambda表达式来遍历一个水果列表,并调用apple.eat()

1
2
3
4
Apple apple = new Apple();
List<String> fruits = Arrays.asList("banana", "orange", "apple");

fruits.forEach(fruit -> apple.eat(fruit)); // lambda表达式

1.1.2 使用实例方法引用:

既然apple.eat()已经存在,我们可以直接引用它的方法名:

1
fruits.forEach(apple::eat);  // 实例方法引用

这两种写法的效果完全一样,但使用方法引用的代码更简洁!

1.2 什么时候可以用实例方法引用?

  1. 已有的实例方法:你已经有一个对象(比如上面的apple),并且你想对列表中的每一项调用这个对象的某个方法。

  2. 方法参数匹配:lambda表达式中的参数要和实例方法的参数一致。例如,fruits.forEach(apple::eat)中,forEach会把fruits中的每个元素传递给apple.eat(),而eat刚好是接受一个String参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.camellia.lambda;

public class MethodRefDemo {
public static void main(String[] args) {
RefClass refClass = new RefClass();

// 1. 匿名类
RefInterface anonymous = new RefInterface() {
@Override
public String getValue(int a) {
return refClass.getValue(a);
}
};
System.out.println(anonymous.getValue(1));

// 2. Lambda 表达式
RefInterface lambda = a -> refClass.getValue(a);
System.out.println(lambda.getValue(2));

// 3. 方法引用
RefInterface methodRef = refClass::getValue;
System.out.println(methodRef.getValue(3));
}
}

interface RefInterface {
String getValue(int a);
}

class RefClass {
public String getValue(int a) {
return String.format("输出值:%d", a);
}
}

1.3 实例方法引用的格式:

1
实例对象::方法名

注意:方法引用等价于lambda表达式,也就是:

1
apple::eat  // 等价于 (fruit) -> apple.eat(fruit)

2. 静态方法引用


静态方法引用是指直接引用类中的静态方法,而不需要通过对象调用。静态方法引用使代码更加简洁,特别是在lambda表达式中,静态方法可以直接作为逻辑处理的实现。

静态方法引用的格式

1
类名::静态方法名

2.1 静态方法引用的使用

假设有一个类MathUtils,其中有一个静态方法doubleNumber(),用于将数字乘以2:

1
2
3
4
5
class MathUtils {
public static int doubleNumber(int num) {
return num * 2;
}
}

2.1.1 使用lambda表达式

将一个数字列表中的每个数字都乘以2,可能会用lambda表达式来写:

1
2
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
numbers.stream().map(n -> MathUtils.doubleNumber(n)).forEach(System.out::println);

2.1.2 使用静态方法引用

因为doubleNumber()是一个静态方法,你可以直接用方法引用来简化代码:

1
numbers.stream().map(MathUtils::doubleNumber).forEach(System.out::println);

2.2 什么时候可以用静态方法引用?

  1. 有静态方法可以使用:当你已经有一个现成的静态方法,并且这个静态方法可以被重用时,比如上面的MathUtils.doubleNumber

  2. 参数匹配:lambda表达式中的参数要和静态方法的参数一致。在上面的例子中,stream().map()会把数字传递给MathUtils.doubleNumber(),它正好接受一个int参数并返回int

2.3 常见场景

  1. 工具类中的静态方法:静态方法引用通常用于像MathCollections这样的工具类,因为它们通常包含常用的、与实例无关的逻辑。

    例如,使用Math.max()来获取两个数中的最大值:

    1
    BinaryOperator<Integer> max = Math::max;
    1
    2
    // 使用 lambda 表达式的等效写法
    BinaryOperator<Integer> max = (a, b) -> Math.max(a, b);
  2. 转换、计算逻辑:如果你有一些转换或计算逻辑,并且这些逻辑已经通过静态方法实现,可以使用静态方法引用。

    例如,使用Integer.parseInt()将字符串转换为数字:

    1
    Stream.of("1", "2",      "3").map(Integer::parseInt).forEach(System.out::println);

3. 特定类型的任意对象的实例方法引用(通过参数对象调用实例方法)


在Lambda表达式的方法体中,通过参数对象调用实例方法就是方法的第一个形参来调用指定的某个“实例方法”。

接口方法的第一个参数就是你用来调用实例方法的对象。接口方法中除第一个参数外的其他参数,必须与实例方法的参数列表一一对应。

3.1 特定类型的任意对象的实例方法引用的使用

1
2
3
4
5
6
7
8
// 方式一:使用匿名内部类来实现
Comparator<Double> comparator1 = new Comparator<Double>() {
@Override
public int compare(Double o1, Double o2) {
return o1.compareTo(o2);
}
};
System.out.println(comparator1.compare(10.0, 20.0));
1
2
3
4
5
6
7
8
9
// 方式一:使用匿名内部类来实现
Teacher teacher = new Teacher("ande", 18);
Function<Teacher, String> function1 = new Function<Teacher, String>() {
@Override
public String apply(Teacher teacher) {
return teacher.getName();
}
};
System.out.println(function1.apply(teacher));

3.1.1 使用Lambda表达式

1
2
3
// 方式二:使用Lambda表达式来实现
Comparator<Double> comparator2 = (o1, o2) -> o1.compareTo(o2);
System.out.println(comparator2.compare(10.0, 20.0));
1
2
3
// 方式二:使用Lambda表达式来实现
Function<Teacher, String> function2 = e -> e.getName();
System.out.println(function2.apply(teacher));

3.1.2 使用方法引用来

1
2
3
// 方式三:使用方法引用来实现
Comparator<Double> comparator3 = Double :: compareTo;
System.out.println(comparator3.compare(10.0, 20.0));
1
2
3
// 方式三:使用方法引用来实现
Function<Teacher, String > function3 = Teacher :: getName;
System.out.println(function3.apply(teacher));

4. 构造方法引用


允许你使用类的构造方法来创建对象,构造方法引用是一个简洁的方式来传递构造函数作为参数。

4.1 构造方法引用的使用

构造方法引用的基本语法是:

1
ClassName::new

其中,ClassName 是想要引用构造方法的类名。

  • 假设有一个 Person 类,其构造方法如下:
1
2
3
4
5
6
7
8
9
10
11
public class Person {
private String name;

public Person(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
  • 有一个接口 PersonFactory,它有一个 createPerson 方法:
1
2
3
4
@FunctionalInterface
public interface PersonFactory {
Person create(String name);
}

4.1.1 使用 lambda 表达式

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
// 使用 lambda 表达式创建一个 PersonFactory 实例
PersonFactory factory = name -> new Person(name);
// 使用工厂实例创建 Person 对象
Person person = factory.create("John Doe");
// 输出 Person 的名字
System.out.println(person.getName()); // 输出 "John Doe"
}
}

4.1.2 使用构造方法引用

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
// 使用构造方法引用创建一个 PersonFactory 实例
PersonFactory factory = Person::new;
// 使用工厂实例创建 Person 对象
Person person = factory.create("John Doe");
// 输出 Person 的名字
System.out.println(person.getName()); // 输出 "John Doe"
}
}

4.2 什么时候可以用构造方法引用?

  1. 已有的构造方法:已经有一个类及其构造方法,并且想用这个构造方法来创建对象。
  2. 参数匹配:构造方法引用的参数需要与接口的抽象方法参数匹配。例如,PersonFactory 接口的 create 方法需要一个 String 类型的参数,而 Person 类的构造方法也正好接受一个 String 参数。

4.3 使用场景

1. 工厂模式:可以用来创建对象的工厂方法。
2. 流处理:在 Java Streams API 中,可以用构造方法引用来创建对象。例如:

1
2
3
4
List<String> names = Arrays.asList("John", "Jane", "Jack");
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());

这里,Person::new 是构造方法引用,它将 names 列表中的每个名字映射到一个新的 Person 对象。

构造方法引用提供了一种更简洁的方式来传递构造方法,同时保持代码的可读性和简洁性。

5. 数组引用


数组引用是一种方法引用形式,用于创建指定类型的数组。在 Java 8 中,数组引用提供了一种简洁的方式来创建数组,尤其是在使用 lambda 表达式时。通过数组引用,可以在 lambda 表达式的方法体中创建并返回一个指定类型的数组。

5.1 数组引用的语法

语法如下:

1
数组类型::new

5.2 数组引用的特点

  • 要求:重写的方法必须有且只有一个 int 类型的参数,该参数用来设置数组的空间长度。
  • 返回值类型:重写方法的返回值类型必须与创建的数组类型一致。

5.3 数组引用的使用

假设要创建一个 Function<Integer, int[]> 对象,并使用数组引用来创建不同长度的 int 数组。以下是三种实现方式:

5.3.1 使用匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Arrays;
import java.util.function.Function;

public class ArrayRefDemo {
public static void main(String[] args) {
// 方式一:使用匿名内部类来实现
Function<Integer, int[]> function1 = new Function<Integer, int[]>() {
@Override
public int[] apply(Integer integer) {
return new int[integer];
}
};
System.out.println(Arrays.toString(function1.apply(10))); // 输出 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
}

5.3.2 使用 Lambda 表达式

1
2
3
4
5
6
7
8
9
10
import java.util.Arrays;
import java.util.function.Function;

public class ArrayRefDemo {
public static void main(String[] args) {
// 方式二:使用 Lambda 表达式来实现
Function<Integer, int[]> function2 = num -> new int[num];
System.out.println(Arrays.toString(function2.apply(20))); // 输出 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
}

5.3.3 使用数组引用

1
2
3
4
5
6
7
8
9
10
import java.util.Arrays;
import java.util.function.Function;

public class ArrayRefDemo {
public static void main(String[] args) {
// 方式三:使用数组引用来实现
Function<Integer, int[]> function3 = int[]::new;
System.out.println(Arrays.toString(function3.apply(30))); // 输出 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
}