Java 方法引用
介绍
方法引用是Java 8引入的一种语法糖,它允许我们直接引用已经存在的方法或构造函数,使代码更加简洁可读。本质上,方法引用是Lambda表达式的一种简化形式,当Lambda表达式的主体仅为调用一个已存在的方法时,我们可以使用方法引用替代它。
方法引用使用双冒号(::
)操作符来表示,左侧是类名或实例,右侧是方法名。例如:System.out::println
。
方法引用并不是独立于Lambda表达式的新特性,而是Lambda表达式的一种特殊形式,用于进一步简化代码。
方法引用的类型
Java中有四种类型的方法引用:
- 静态方法引用:
ClassName::staticMethodName
- 特定对象的实例方法引用:
instance::instanceMethodName
- 特定类型的任意对象的实例方法引用:
ClassName::instanceMethodName
- 构造函数引用:
ClassName::new
接下来,我们将详细介绍每种类型,并提供具体的示例。
静态方法引用
静态方法引用是引用类的静态方法。语法为:ClassName::staticMethodName
。
示例
import java.util.Arrays;
import java.util.List;
public class StaticMethodReferenceExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Maria", "Alex", "Bob");
// 使用Lambda表达式打印列表元素
names.forEach(name -> System.out.println(name));
// 使用静态方法引用打印列表元素
names.forEach(System.out::println);
// 使用自定义静态方法
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 9);
// 使用Lambda表达式
numbers.forEach(n -> printSquare(n));
// 使用静态方法引用
numbers.forEach(StaticMethodReferenceExample::printSquare);
}
public static void printSquare(int number) {
System.out.println(number * number);
}
}
输出:
John
Maria
Alex
Bob
25
9
64
1
81
在上面的例子中,System.out::println
是对 System.out
对象的 println
方法的引用。StaticMethodReferenceExample::printSquare
是对 StaticMethodReferenceExample
类的 printSquare
静态方法的引用。
特定对象的实例方法引用
这种方法引用是引用特定对象实例的方法。语法为:instance::instanceMethodName
。
示例
import java.util.Arrays;
import java.util.List;
public class InstanceMethodReferenceExample {
public static void main(String[] args) {
// 创建一个Greeter实例
Greeter greeter = new Greeter();
// 创建一个人名列表
List<String> names = Arrays.asList("John", "Maria", "Alex");
// 使用Lambda表达式
names.forEach(name -> greeter.greet(name));
// 使用实例方法引用
names.forEach(greeter::greet);
// 字符串处理示例
StringProcessor processor = new StringProcessor();
List<String> messages = Arrays.asList("hello", "world", "java");
// 使用Lambda表达式
messages.stream()
.map(s -> processor.capitalize(s))
.forEach(System.out::println);
// 使用实例方法引用
messages.stream()
.map(processor::capitalize)
.forEach(System.out::println);
}
}
class Greeter {
public void greet(String name) {
System.out.println("Hello, " + name + "!");
}
}
class StringProcessor {
public String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}
输出:
Hello, John!
Hello, Maria!
Hello, Alex!
Hello
World
Java
Hello
World
Java
在这个例子中,greeter::greet
是对 greeter
对象的 greet
方法的引用,而 processor::capitalize
是对 processor
对象的 capitalize
方法的引用。
特定类型的任意对象的实例方法引用
这种方法引用是引用特定类型的任意对象的实例方法。语法为:ClassName::instanceMethodName
。这种情况下,方法的第一个参数将成为方法调用的目标对象。
示例
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.BiFunction;
public class TypeMethodReferenceExample {
public static void main(String[] args) {
// 字符串长度示例
List<String> names = Arrays.asList("John", "Maria", "Alexander");
// 使用Lambda表达式
names.stream()
.map(name -> name.length())
.forEach(System.out::println);
// 使用实例方法引用
names.stream()
.map(String::length)
.forEach(System.out::println);
// 字符串比较示例
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia" };
Arrays.sort(stringArray, (s1, s2) -> s1.compareToIgnoreCase(s2));
System.out.println(Arrays.toString(stringArray));
Arrays.sort(stringArray, String::compareToIgnoreCase);
System.out.println(Arrays.toString(stringArray));
// 演示参数传递机制
BiFunction<String, Integer, String> substringLambda = (str, i) -> str.substring(i);
BiFunction<String, Integer, String> substringReference = String::substring;
String result1 = substringLambda.apply("Hello World", 6);
String result2 = substringReference.apply("Hello World", 6);
System.out.println(result1); // World
System.out.println(result2); // World
}
}
输出:
4
5
9
4
5
9
[Barbara, James, John, Mary, Patricia]
[Barbara, James, John, Mary, Patricia]
World
World
在这个例子中,String::length
是对 String
类的 length
实例方法的引用,实际调用时会调用第一个参数(name
)的 length()
方法。类似地,String::compareToIgnoreCase
是对 String
类的 compareToIgnoreCase
实例方法的引用。
构造函数引用
构造函数引用是引用类的构造函数。语法为:ClassName::new
。
示例
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ConstructorReferenceExample {
public static void main(String[] args) {
// 使用无参构造函数
Supplier<Person> personSupplier = () -> new Person();
Person person1 = personSupplier.get();
// 使用构造函数引用替代上面的Lambda表达式
Supplier<Person> personSupplier2 = Person::new;
Person person2 = personSupplier2.get();
// 使用带参数的构造函数
Function<String, Person> personFunction = name -> new Person(name);
Person person3 = personFunction.apply("John");
// 使用构造函数引用替代上面的Lambda表达式
Function<String, Person> personFunction2 = Person::new;
Person person4 = personFunction2.apply("Maria");
// 创建一个名称数组并转换为Person对象列表
String[] names = {"John", "Maria", "Alex"};
// 使用Lambda表达式
List<Person> personList1 = Arrays.stream(names)
.map(name -> new Person(name))
.collect(Collectors.toList());
// 使用构造函数引用
List<Person> personList2 = Arrays.stream(names)
.map(Person::new)
.collect(Collectors.toList());
// 打印结果
personList2.forEach(p -> System.out.println(p.getName()));
// 创建ArrayList的示例
Supplier<ArrayList<String>> arrayListSupplier = ArrayList::new;
ArrayList<String> stringList = arrayListSupplier.get();
stringList.add("Hello");
System.out.println(stringList);
}
}
class Person {
private String name;
public Person() {
this.name = "Unknown";
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
输出:
John
Maria
Alex
[Hello]
在这个例子中,Person::new
是对 Person
类的构造函数的引用。在 Supplier<Person> personSupplier2 = Person::new;
中,它引用的是无参构造函数。在 Function<String, Person> personFunction2 = Person::new;
中,它引用的是带一个 String
参数的构造函数。在处理流时,它可以很方便地将流中的元素映射到新的对象。
方法引用与Lambda表达式的对比
为了更好地理解方法引用,让我们看看它与等价的Lambda表达式的对比:
Lambda表达式 | 方法引用 |
---|---|
() -> Math.random() | Math::random |
(s) -> System.out.println(s) | System.out::println |
(str, i) -> str.substring(i) | String::substring |
(s1, s2) -> s1.compareToIgnoreCase(s2) | String::compareToIgnoreCase |
(name) -> new Person(name) | Person::new |
当Lambda表达式仅调用一个现有方法且不做其他操作时,方法引用可以使代码更简洁、更可读。
实际应用场景
事件处理
// 传统方式
button.setOnAction(event -> handleButtonClick(event));
// 使用方法引用
button.setOnAction(this::handleButtonClick);
数据转换和过滤
// 转换数据
List<String> names = Arrays.asList("John", "Maria", "Alex");
List<Person> people = names.stream()
.map(Person::new) // 使用构造函数引用
.collect(Collectors.toList());
// 过滤数据
List<String> nonEmptyStrings = strings.stream()
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
// 使用方法引用改写过滤
List<String> nonEmptyStrings = strings.stream()
.filter(Predicate.not(String::isEmpty))
.collect(Collectors.toList());
排序
// 对人员按姓名排序
List<Person> people = ...;
// 使用Lambda表达式
people.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
// 使用方法引用和Comparator
people.sort(Comparator.comparing(Person::getName));
并行处理
// 并行处理大型数据集
List<Integer> numbers = ...;
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
总结
方法引用是Java 8中引入的一种简化Lambda表达式的语法,使得代码更加简洁和可读。它有四种类型:静态方法引用、特定对象的实例方法引用、特定类型的任意对象的实例方法引用和构造函数引用。
使用方法引用时,需要确保方法引用的参数类型和返回类型与函数式接口的参数类型和返回类型匹配。方法引用特别适用于那些只是调用一个已有方法而不需要添加额外逻辑的场景。
在实际开发中,方法引用常用于事件处理、数据转换、排序以及流处理等场景,能够使代码更加优雅和易于维护。
练习
- 编写一个程序,使用方法引用从字符串列表中过滤出长度大于5的字符串。
- 创建一个程序,使用构造函数引用将一个包含整数的列表转换为自定义对象的列表。
- 使用方法引用实现一个简单的事件处理系统,响应不同类型的事件。
- 尝试使用四种不同类型的方法引用完成同一个任务,比如打印一个集合中的元素。
通过这些练习,您将能够更好地理解和应用Java方法引用,使您的代码更加简洁和现代化。