引言:什么是方法引用?
方法引用(Method Reference)是Java 8引入的重要特性,是Lambda表达式的一种简化形式。当Lambda表达式只是直接调用某个方法时,可以使用方法引用来更简洁地表达。
方法引用类比
想象你在指挥乐队:
Lambda表达式写法:
"请这位乐手(小明)演奏他的小提琴"
方法引用写法:
"小明::演奏小提琴" 或直接 "小明::演奏"
这就是方法引用:直接指向某个方法,而不是描述整个动作
为什么使用方法引用?
更简洁:代码更短,更易读
更清晰:直接指向已有方法,而不是描述动作
更高效:编译器可以直接优化
Lambda vs 方法引用对比
// Lambda表达式
list.forEach(s -> System.out.println(s));
// 方法引用(更简洁)
list.forEach(System.out::println);
System.out::println 指向 System.out.println(String) 方法
四种方法引用类型
| 类型 | 语法 | 说明 | 示例 |
|---|---|---|---|
| 静态方法引用 | ClassName::staticMethod | 引用静态方法 | Integer::parseInt |
| 实例方法引用(特定对象) | instance::instanceMethod | 引用某对象的实例方法 | str::length |
| 实例方法引用(任意对象) | ClassName::instanceMethod | 引用某类型任意对象的实例方法 | String::toUpperCase |
| 构造方法引用 | ClassName::new | 引用构造方法 | ArrayList::new |
类型1:静态方法引用
静态方法引用指向静态方法,语法为 ClassName::staticMethod。
语法规则
// Lambda表达式
(input) -> ClassName.staticMethod(input)
// 方法引用
ClassName::staticMethod
示例
// Lambda表达式
Function parser1 = s -> Integer.parseInt(s);
// 静态方法引用
Function parser2 = Integer::parseInt;
// 使用
System.out.println(parser2.apply("123")); // 123
更多静态方法引用示例
// Math类的静态方法
Predicate isEven1 = n -> Math.abs(n) % 2 == 0;
Predicate isEven2 = Math::isNaN; // 注意:这个不对,应该用abs
// 正确示例
UnaryOperator abs = Math::abs;
System.out.println(abs.apply(-10)); // 10
// String类的静态方法
Function length1 = s -> s.length();
Function length2 = String::length; // length不是静态的
// 正确的静态方法引用
Function upper1 = s -> s.toUpperCase();
Function upper2 = String::toUpperCase; // 错误!toUpperCase不是静态的
类型2:实例方法引用(特定对象)
实例方法引用指向某个具体对象的实例方法,语法为 instance::instanceMethod。
语法规则
// Lambda表达式
() -> instance.instanceMethod()
// 方法引用
instance::instanceMethod
示例
String str = "Hello World";
// Lambda表达式
Supplier len1 = () -> str.length();
// 实例方法引用
Supplier len2 = str::length;
// 使用
System.out.println(len2.get()); // 11
更多示例
// 使用System.out
Consumer printer1 = s -> System.out.println(s);
Consumer printer2 = System.out::println;
printer2.accept("Hello"); // 打印 "Hello"
// 使用某个具体对象的方法
List list = Arrays.asList("a", "b", "c");
list.forEach(s -> System.out.println(s)); // Lambda
list.forEach(System.out::println); // 方法引用
类型3:实例方法引用(任意对象)
特定类型的方法引用指向某类型任意对象的实例方法,语法为 ClassName::instanceMethod。也叫绑定方法引用。
关键理解:当Lambda表达式的第一个参数是方法的调用者,后面参数是方法参数时,可以使用这种形式。
语法规则
// Lambda表达式:(obj, param) -> obj.instanceMethod(param)
// 方法引用:ClassName::instanceMethod
// 例如:String::toUpperCase 相当于 (String s) -> s.toUpperCase()
示例
// Lambda表达式
Function upper1 = s -> s.toUpperCase();
// 特定类型的方法引用
Function upper2 = String::toUpperCase;
// 使用
System.out.println(upper2.apply("hello")); // HELLO
两参数的方法引用
// Lambda表达式
BiPredicate contains1 = (s1, s2) -> s1.contains(s2);
// 特定类型的方法引用
BiPredicate contains2 = String::contains;
// 使用
System.out.println(contains2.test("Hello", "ell")); // true
更多示例
List names = Arrays.asList("alice", "bob", "charlie");
// Lambda
names.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
// 方法引用
names.stream().map(String::toUpperCase).forEach(System.out::println);
// 比较器
Comparator comp1 = (s1, s2) -> s1.compareTo(s2);
Comparator comp2 = String::compareTo;
类型4:构造方法引用
构造方法引用指向构造方法,语法为 ClassName::new。
语法规则
// Lambda表达式
() -> new ClassName()
(args) -> new ClassName(args)
// 构造方法引用
ClassName::new
无参构造方法引用
// Lambda表达式
Supplier> list1 = () -> new ArrayList<>();
// 构造方法引用
Supplier> list2 = ArrayList::new;
// 使用
ArrayList arr = list2.get();
有参构造方法引用
// Lambda表达式
Function> list1 = size -> new ArrayList<>(size);
// 构造方法引用
Function> list2 = ArrayList::new;
// 使用
ArrayList arr = list2.apply(10); // 创建初始容量为10的列表
自定义类的构造方法引用
// Lambda表达式
Function person1 = name -> new Person(name);
// 构造方法引用
Function person2 = Person::new;
// 使用
Person p = person2.apply("张三");
System.out.println(p.getName()); // 张三
方法引用在Stream中的应用
map方法
List names = Arrays.asList("alice", "bob", "charlie");
// Lambda
List upper1 = names.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
// 方法引用
List upper2 = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
filter方法
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// Lambda
List evens1 = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 自定义方法引用
List evens2 = numbers.stream()
.filter(NumberUtils::isEven) // 假设有这个静态方法
.collect(Collectors.toList());
forEach方法
List list = Arrays.asList("Java", "Python", "C++");
// Lambda
list.forEach(s -> System.out.println(s));
// 方法引用
list.forEach(System.out::println);
compare方法
List languages = Arrays.asList("Java", "Python", "C++");
// Lambda
languages.sort((s1, s2) -> s1.compareTo(s2));
// 方法引用
languages.sort(String::compareTo);
// 倒序
languages.sort(Comparator.reverseOrder());
方法引用的限制
不是所有Lambda都能变成方法引用
不能使用方法引用的情况
// 情况1:Lambda表达式包含复杂逻辑
list.stream().map(s -> {
String result = s.trim().toUpperCase();
return result;
});
// 不能简化为方法引用,因为逻辑太复杂
// 情况2:需要类型转换
list.stream().map(s -> (String)s.toLowerCase());
// 不能简化为方法引用
// 情况3:方法有多个参数且不只是简单的调用
BiFunction concat = (s1, s2) -> s1.concat(s2);
BiFunction concat2 = String::concat;
// 这个可以!因为concat就是简单的调用
如何选择:Lambda还是方法引用?
| 场景 | 推荐 | 原因 |
|---|---|---|
| Lambda只是调用方法 | 方法引用 | 更简洁、更清晰 |
| Lambda包含复杂逻辑 | Lambda | 方法引用无法表达 |
| Lambda需要计算多个值 | Lambda | 需要多步操作 |
| 只是打印或传递 | 方法引用 | 如System.out::println |
最佳实践
// 推荐:方法引用
list.forEach(System.out::println);
map(String::toUpperCase);
set.stream().map(Object::toString);
// 推荐:Lambda(复杂逻辑)
list.stream().map(s -> s.trim().toLowerCase().replace(" ", ""));
list.stream().filter(s -> {
// 复杂逻辑
return s.length() > 5 && s.startsWith("a");
});
小结
- 方法引用是Lambda的简化形式,使代码更简洁
- 静态方法引用:
ClassName::staticMethod - 实例方法引用(特定对象):
instance::instanceMethod - 实例方法引用(任意对象):
ClassName::instanceMethod - 构造方法引用:
ClassName::new - 当Lambda只是调用方法时,优先使用方法引用
- 方法引用不能替代所有Lambda,复杂逻辑仍用Lambda