深入理解方法引用

Posted by SFHJavaer on 2024-12-08
Estimated Reading Time 5 Minutes
Words 1.3k In Total
Viewed Times

GPT提供的学习方式

1. 类比匿名内部类的过程

方法引用其实是对匿名内部类的一种简化。当你看到方法引用时,可以尝试在脑海里把它转换成匿名内部类,因为它们在底层是一样的处理方式。例如:

1
BiConsumer<StringJoiner, CharSequence> biConsumer = StringJoiner::add;

可以转换成:

1
2
3
4
5
6
BiConsumer<StringJoiner, CharSequence> biConsumer = new BiConsumer<StringJoiner, CharSequence>() {
@Override
public void accept(StringJoiner joiner, CharSequence element) {
joiner.add(element);
}
};

2. 熟悉函数式接口

每次使用方法引用时,明确你在使用的函数式接口类型,如 BiConsumer<T, U>, Function<T, R>, Predicate<T> 等。了解这些接口的参数和返回值定义会极大帮助你理解方法引用的工作原理。例如:

  • BiConsumer<T, U>accept(T t, U u) 方法没有返回值,但接收两个参数。
  • Function<T, R>apply(T t) 方法接收一个参数,并返回一个值。

理解这些接口后,当你看到 BiConsumer<StringJoiner, CharSequence> biConsumer = StringJoiner::add;,你可以知道:

  • BiConsumer 需要两个参数 StringJoinerCharSequence
  • StringJoiner::add 是一个实例方法,它接收一个 CharSequence 作为参数,返回一个 StringJoiner,但 BiConsumer 不关心返回值。

3. 重写方法引用为lambda表达式

在适应方法引用之前,可以先使用 lambda 表达式。lambda 表达式和方法引用是等效的,但 lambda 更加直观。你可以先把复杂的引用转换成 lambda 表达式,从而逐步理解其行为。比如:

1
BiConsumer<StringJoiner, CharSequence> biConsumer = (joiner, element) -> joiner.add(element);

这与方法引用 StringJoiner::add 是等价的。然后,你可以看到:

  • joiner.add(element) 实际上对应的是 BiConsumeraccept(joiner, element)
  • 当你熟悉 lambda 之后,就能逐渐简化为方法引用。

4. 步骤化思考方法引用

当你看到方法引用时,可以按以下步骤思考:

  1. 这个方法引用属于哪个类或对象?
    比如 StringJoiner::add 属于 StringJoiner 类的实例方法。
  2. 这个方法需要的参数是什么?
    StringJoiner.add 接收一个 CharSequence 参数。
  3. 方法引用使用的函数式接口是什么?
    BiConsumer<StringJoiner, CharSequence> 需要两个参数 StringJoinerCharSequence
  4. 匹配的参数类型是否正确?
    验证方法引用的参数是否符合函数式接口的要求。

5. 练习更多常见的函数式接口

常用的函数式接口有以下几种:

  • Consumer<T>:接收一个参数,没有返回值。
  • BiConsumer<T, U>:接收两个参数,没有返回值。
  • Function<T, R>:接收一个参数,返回一个值。
  • Supplier<R>:不接收参数,返回一个值。
  • Predicate<T>:接收一个参数,返回一个 boolean

尝试用这些接口去写一些方法引用和 lambda 表达式的练习,会帮助你熟悉它们的使用。例如:

1
2
3
4
5
6
7
8
9
10
11
// 使用 Consumer 接口
Consumer<String> print = System.out::println;
print.accept("Hello, World!");

// 使用 Function 接口
Function<String, Integer> strLength = String::length;
int len = strLength.apply("Hello");

// 使用 Predicate 接口
Predicate<String> isEmpty = String::isEmpty;
boolean result = isEmpty.test("");

6. 一开始不要强求简化

一开始如果觉得方法引用难以理解,不要强行使用,而是先用更直观的匿名内部类或 lambda 表达式。当你熟悉了函数式接口的行为之后,再慢慢尝试用方法引用。

几个重要的点:

第一个,使用的函数式接口没返回值,比如consumer,也可以使用有返回值的方法引用,原因是不取就可以了,但是有返回值的(这里的T U R都是’类型‘),因为类型是不可缺少的,比如Function<T,R>,R在action方法的返回位置上,其里类型必须指明(不同类型的给的多了但是不用 这没关系,同类型给多\少了没法占位就有问题,其实就是拿真正的类型占T的位)

:::info
Lambda注重的是操作(动作)

:::

第二个

区分方法引用的类型,实例第一个参数为this(的类的类型!),之后填参数,为什么?原因是实例方法就是实例发起调用的方法,存在修改了其本身,所以必须将其传入Function中,作为参数使用处理!

静态方法因为是类直接调用,完全可只关注内部处理,修改的变量就是入参的n个。


所以我们平时写的lambda可以直接,只关注步骤流程(几个参数就写几个)->{},因为完全覆盖入参(被操作要修改的值)


所有方法引用的入出参具体类型和函数式接口参数具体类型没什么关系,比如BiFunction<T,U,R>,传入为StringJoiner::add,StringJoiner::add改成Lambda还是(T paramA,U paramB) ->{

具体逻辑

}

还是需要两个入参,只不过实例方法引用的比较特殊,本质上还是相当于把调用对象当作了参数,因为他也参与了修改,本质上还是是只关注处理逻辑,不关注具体类型


想象成匿名内部类,一切都能理解的通


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !