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
实例并返回一个等效的新实例,同时切断了 before
和 after
引用之间的连接。
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 也是由霍普团队首先使用并流传开来的。——译者注)