Java 构造函数引用

Java 构造函数引用,用户希望将方法引用作为流的流水线(stream pipeline)的一部分,以实例化某个对象。在方法引用中使用 new 关键字。

Java 构造函数引用 问题描述

用户希望将方法引用作为流的流水线(stream pipeline)的一部分,以实例化某个对象。

Java 构造函数引用 解决方案

在方法引用中使用 new 关键字。

Java 构造函数引用 具体实例

在讨论 Java 8 引入的新语法时,通常会提及 lambda 表达式、方法引用以及流。例如,我们希望将一份人员列表转换为相应的姓名列表。例 1-13 的代码段显示了解决这个问题的两种方案。

例 1-13 将人员列表转换为姓名列表

List<String> names = people.stream()
    .map(person -> person.getName()) ➊
    .collect(Collectors.toList());

// 或者采用以下方案

List<String> names = people.stream()
    .map(Person::getName)             ➋
    .collect(Collectors.toList());

❶ lambda 表达式
❷ 方法引用
那么,是否存在其他解决方案呢?如何根据字符串列表来创建相应的 Person 引用列表呢?尽管仍然可以使用方法引用,不过这次我们改用关键字 new,这种语法称为构造函数引用(constructor reference)。
为了说明构造函数引用的用法,我们首先创建一个 Person 类,它是最简单的 Java 对象(POJO)。Person 类的唯一作用是包装一个名为 name 的简单字符串特性,如例 1-14 所示。

例 1-14 Person

public class Person {
    private String name;

    public Person() {}

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

    // getter和setter

    // equals、hashCode与toString方法
}

如例 1-15 所示,给定一个字符串集合,通过 lambda 表达式或构造函数引用,可以将其中的每个字符串映射到 Person 类。

例 1-15 将字符串转换为 Person 实例

List<String> names =
    Arrays.asList("Grace Hopper", "Barbara Liskov", "Ada Lovelace",
        "Karen Spärck Jones");

List<Person> people = names.stream()
    .map(name -> new Person(name)) ➊
    .collect(Collectors.toList());

// 或采用以下方案

List<Person> people = names.stream()
    .map(Person::new)              ➋
    .collect(Collectors.toList());

❶ 使用 lambda 表达式来调用构造函数
❷ 使用构造函数引用来实例化 Person
Person::new 的作用是引用 Person 类中的构造函数。与所有 lambda 表达式类似,由上下文决定执行哪个构造函数。由于上下文提供了一个字符串,使用单参数的 String 构造函数。

1.复制构造函数
复制构造函数(copy constructor)传入一个 Person 参数,并返回一个具有相同特性的新 Person,如例 1-16 所示。

例 1-16 Person 的复制构造函数

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

如果需要将流代码从原始实例中分离出来,复制构造函数将很有用。假设我们有一个人员列表,先将其转换为流,再转换回列表,那么引用不会发生变化,如例 1-17 所示。

例 1-17 将列表转换为流,再转换回列表

Person before = new Person("Grace Hopper");
 
List<Person> people = Stream.of(before)
    .collect(Collectors.toList());
Person after = people.get(0);
 
assertTrue(before == after);                          ➊
 
before.setName("Grace Murray Hopper");                ➋
assertEquals("Grace Murray Hopper", after.getName()); ➌

❶ 对象相同
❷ 使用 before 引用修改人名
after 引用中的人名已被修改
如例 1-18 所示,可以通过复制构造函数来切断连接。

例 1-18 使用复制构造函数

people = Stream.of(before)
      .map(Person::new)           ➊
      .collect(Collectors.toList());
 
after = people.get(0);
assertFalse(before == after);     ➋
assertEquals(before, after);      ➌
 
before.setName("Rear Admiral Dr. Grace Murray Hopper");
assertFalse(before.equals(after));

❶ 使用复制构造函数
❷ 对象不同
❸ 但二者是等效的
可以看到,当调用 map 方法时,上下文是 Person 实例的流。因此,Person::new 调用构造函数,它传入一个 Person 实例并返回一个等效的新实例,同时切断了 beforeafter 引用之间的连接。

2.可变参数构造函数
接下来,我们为 Person POJO 添加一个可变参数构造函数(varargs constructor),如例 1-19 所示。

例 1-19 构造函数 Person 传入 String 的可变参数列表

public Person(String... names) {
    this.name = Arrays.stream(names)
                      .collect(Collectors.joining(" "));
}

上述构造函数传入零个或多个字符串参数,并使用空格作为定界符将这些参数拼接在一起。
那么,如何调用这个构造函数呢?任何传入零个或多个字符串参数(由逗号隔开)的客户端都会调用这个构造函数。一种方案是利用 String 类定义的 split 方法,它传入一个定界符并返回一个 String 数组。

String[] split(String delimiter)

因此,例 1-20 中的代码将列表中的每个字符串拆分为单个单词,并调用可变参数构造函数。

例 1-20 可变参数构造函数的应用

names.stream()                      ➊
    .map(name -> name.split(" "))   ➋
    .map(Person::new)               ➌
    .collect(Collectors.toList());  ➍

❶ 创建字符串流
❷ 映射到字符串数组流
❸ 映射到 Person
❹ 收集到 Person 列表
在本例中,map 方法的上下文包含 Person::new 构造函数引用,它是一个字符串数组流,因此将调用可变参数构造函数。如果为该构造函数添加一个简单的打印语句:

System.out.println("Varargs ctor, names=" + Arrays.asList(names));

则输出如下结果:

Varargs ctor, names=[Grace, Hopper]
Varargs ctor, names=[Barbara, Liskov]
Varargs ctor, names=[Ada, Lovelace]
Varargs ctor, names=[Karen, Spärck, Jones]

3.数组
构造函数引用也可以和数组一起使用。如果希望采用 Person 实例的数组(Person[])而非列表,可以使用 Stream 接口定义的 toArray 方法,它的签名为:

<A> A[] toArray(IntFunction<A[]> generator)

toArray 方法采用 A 表示返回数组的泛型类型(generic type)。数组包含流的元素,由所提供的 generator 函数创建。我们甚至还能使用构造函数引用,如例 1-21 所示。

例 1-21 创建 Person 引用的数组

Person[] people = names.stream()
    .map(Person::new)         ➊
    .toArray(Person[]::new);  ➋

Person 的构造函数引用
Person 数组的构造函数引用
toArray 方法参数创建了一个大小合适的 Person 引用数组,并采用经过实例化的 Person 实例进行填充。
构造函数引用其实是方法引用的别称,通过关键字 new 调用构造函数。同样,由上下文决定调用哪个构造函数。在处理流时,构造函数引用可以提供很大的灵活性。

需要说明的是,将葛丽丝 • 霍普(Grace Hopper)将军作为本书代码中的“对象”绝无冒犯之意。作者深信,尽管霍普将军已于 1992 年去世,她的水平仍然是作者所无法企及的。(葛丽丝 • 霍普是美国海军准将,也是全球最早的程序员之一。霍普开发了 COBOL 语言,被誉为“COBOL 之母”,计算机术语 bug 和 debug 也是由霍普团队首先使用并流传开来的。——译者注)

赞(6)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

Java 实例