写在前面

我们已经知道,lambda表达式是一个匿名函数,可以用lambda表达式来实现一个函数式接口。
 
很自然的,我们会想到类的方法也是函数,本质上和lambda表达式是一样的,那是否也可以用类的方法来实现一个函数式接口呢?答案是可以的。我们称之为方法引用(method reference)。
 
本文的示例代码可从gitee上获取:https://gitee.com/cnmemset/javafp
 

方法引用

一个典型例子,向一个Map中写入单词以及它的长度:
public static void simpleMethodReference() {
Map<String, Integer> wordMap = new HashMap<>(); // 等同于 wordMap.computeIfAbsent("hello", s -> s.length());
wordMap.computeIfAbsent("hello", String::length); // 输出为 {hello=5}
System.out.println(wordMap);
}
上述代码中,String::length 就是方法引用,它用 :: 来分割类名或对象与方法名,:: 左侧是类名或对象,:: 右侧是方法名。
一般来说,方法引用有4种情况:
1. object::instanceMethod —— 对象 + 实例方法
2. Class::staticMethod —— 类名 + 静态方法
3. Class::instanceMethod —— 类名 + 实例方法
4. Class::new —— 类名 + 关键字 new ,这种情况又称为构造器引用(constructor reference)
 

1. object::instanceMethod

object::instanceMethod,:: 左侧是一个对象,:: 右侧是实例方法名。
它等价于提供了 instanceMethod 方法的参数列表的 lambda表达式。
 
形象来说,假设方法 instanceMethod 的参数列表为 (x, y),那么 object::instanceMethod 等价于 (x, y) -> object.instanceMethod(x, y) 。
 
例如对于字符串 str (String str = ""):
str::compareTo 等价于  s -> str.compareTo(s)
 
示例代码如下:
public static void objectInstanceMethodReference() {
String me = "me"; // wordMap 的 key 是给定的单词,value是不区分大小写,与单词 "me" 比较后得出的值
Map<String, Integer> wordMap = new HashMap<>(); // me::compareToIgnoreCase 等价于 s -> me.compareToIgnoreCase(s)
wordMap.computeIfAbsent("him", me::compareToIgnoreCase);
wordMap.computeIfAbsent("you", s -> me.compareToIgnoreCase(s)); System.out.println(wordMap);
}
上述代码的输出是:
{him=5, you=-12}
 

2. Class::staticMethod

Class::staticMethod,:: 左侧是一个类,:: 右侧是静态方法名。
它等价于提供了staticMethod方法的参数列表的lambda表达式。
 
形象来说,假设静态方法 staticMethod 的参数列表为 (x, y),那么 Class::staticMethod 等价于 (x, y, z) -> Class.staticMethod(x, y) 。
 
例如:
System.out::println 等价于 x -> System.out.print(x)
 
示例代码:
public static void classStaticMethodReference() {
List<String> list = Arrays.asList("Guangdong", "Zhejiang", "Jiangsu"); // System.out::println 等价于 s -> System.out.println(s)
list.forEach(System.out::println);
}
上述代码输出为:
Guangdong
Zhejiang
Jiangsu
 

3. Class::instanceMethod

对于Class::instanceMethod,:: 左侧是一个类,:: 右侧是实例方法名。
 
假设 instanceMethod 的参数列表是 (x, y),那么Class::instanceMethod 等价于lambda表达式  (obj, x, y) -> obj.instanceMethod(x, y),其中 obj 是 Class 的对象实例。
 
例如:
String::length 等价于 s -> s.length()
String::compareToIgnoreCase 等价于 (s1, s2) -> s1.compareToIgnoreCase(s2)
 
示例代码:
public static void classInstanceMethodReference() {
Map<String, Integer> wordMap = new HashMap<>();
Integer wordLen = wordMap.computeIfAbsent("hello", String::length);
System.out.println(wordMap);
}
上述代码输出为:
{hello=5}
 

4. Class::new

对于Class::new,new的含义是指Class的构造函数,所以又称为构造器引用(constructor reference)。
 
假设Class的构造函数有两个,它们的参数列表分别是(x)和(x, y),那么 Class::new 可能等价于 x -> new Class(x),也有可能等价于 (x, y) -> new Class(x, y),具体是哪个,编译器会在编译阶段通过上下文推断出来。
 
例如:
BigDecimal::new ,根据上下文,可能等价于 (String s) -> new BigDecimal(s)
 
特别的,数组类型也可以使用构造器引用。数组类型只有一个构造参数,表示数组的长度:
String[]::new 等价于 x -> new String[x]
 
示例代码:
public static void ctorMethodReference() {
List<String> list = Arrays.asList("1.1", "2.2", "3.3"); // BigDecimal::new 根据上下文推断,等价于 s -> new BigDecimal(s)
Stream<BigDecimal> stream = list.stream().map(BigDecimal::new);
List<BigDecimal> decimalList = stream.collect(Collectors.toList());
System.out.println(decimalList); // 构建一个新的 Stream ,之前的 Stream 已经被关闭了
Stream<BigDecimal> stream1 = list.stream().map(BigDecimal::new); // BigDecimal[]::new ,数组的构造器引用,等价于 x -> new BigDecimal[x]
BigDecimal[] decimalArray = stream1.toArray(BigDecimal[]::new);
for (BigDecimal d : decimalArray) {
System.out.println(d);
}
}
上述代码的输出为:
[1.1, 2.2, 3.3]
1.1
2.2
3.3
 

5. this::instanceMethod和super::instanceMethod

对于this::instanceMethod,很容易理解,相当于把this关键字看做是当前类的实例对象即可。
 
例如:
this::equals 等价于 x -> this.equals(x)
 
对于super::instanceMethod,会相对复杂一些,相当于在this对象上,调用的指定方法父类版本。
 
示例代码:
public class SuperMethodReferenceExample {
public static void main(String[] args) {
ThreadWaiter waiter = new ThreadWaiter();
waiter.run();
} public static class Waiter {
public void sayHi() {
System.out.println("Hello, man!");
}
} public static class ThreadWaiter extends Waiter {
@Override
public void sayHi() {
System.out.println("Hello, thread!");
} public void run() {
// 指定调用父类 Waiter 的 sayHi 方法
Thread t = new Thread(super::sayHi);
t.start();
}
}
}

上述代码的输出为:

Hello, man!
 
 

结语

方法引用可以视为lambda表达式的一个语法糖。

Java中的函数式编程(四)方法引用method reference的更多相关文章

  1. Java 中的函数式编程(Functional Programming):Lambda 初识

    Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...

  2. Java中的函数式编程(二)函数式接口Functional Interface

    写在前面 前面说过,判断一门语言是否支持函数式编程,一个重要的判断标准就是:它是否将函数看做是"第一等公民(first-class citizens)".函数是"第一等公 ...

  3. 方法引用(Method reference)和invokedynamic指令详细分析

    方法引用(Method reference)和invokedynamic指令详细分析 invokedynamic是jvm指令集里面最复杂的一条.本文将详细分析invokedynamic指令是如何实现方 ...

  4. Java中的函数式编程(三)lambda表达式

    写在前面 lambda表达式是一个匿名函数.在Java 8中,它和函数式接口一起,共同构建了函数式编程的框架.   lambda表达式乍看像是匿名内部类的一种语法糖,但实际上,它们是两种本质不同的事物 ...

  5. Java中的函数式编程(五)Java集合框架中的高阶函数

    写在前面 随着Java 8引入了函数式接口和lambda表达式,Java 8中的集合框架(Java Collections Framework, JCF)也增加相应的接口以适应函数式编程.   本文的 ...

  6. Java中的函数式编程(六)流Stream基础

    写在前面 如果说函数式接口和lambda表达式是Java中函数式编程的基石,那么stream就是在基石上的最富丽堂皇的大厦. 只有熟悉了stream,你才能说熟悉了Java 的函数式编程. 本文主要介 ...

  7. Java中的函数式编程(七)流Stream的Map-Reduce操作

    写在前面 Stream 的 Map-Reduce 操作是Java 函数式编程的精华所在,同时也是最为复杂的部分.但一旦你啃下了这块硬骨头,那你就真正熟悉Java的函数式编程了. 如果你有大数据的编程经 ...

  8. Java中的函数式编程(八)流Stream并行编程

    写在前面 在本系列文章的第一篇,我们提到了函数式编程的优点之一是"易于并发编程". Java作为一个多线程的语言,它通过 Stream 来提供了并发编程的便利性. 题外话: 严格来 ...

  9. java 方法引用(method reference)

    it -> it != null等价于Objects::nonNull

随机推荐

  1. ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1122)

    只需执行 /Applications/Python\ 3.9/Install\ Certificates.command

  2. Disable_functions绕过整合

    转载 https://whoamianony.top/2021/03/13/Web安全/Bypass Disable_functions/ https://www.mi1k7ea.com/2019/0 ...

  3. 关于antd Select 限制选择个数的解决方案

    应用场景描述: Select 被form 所包裹,且被getFieldDecorator修饰.所以值的改变应该通过form的setFieldsValue方法. Select模式肯定会是multiple ...

  4. 启动线程组报错:Error occurred starting thread group :test_1, error message:Invalid duration 0 set in Thread Group:test_1, see log file for more details

    线程组基础信息都已经配置好,启动时报错,如下图: 排查原因:勾选了线程组调度器,并未设置参数 解决方案:取消勾选或者设置参数

  5. vscode快速添加引号 批量增加引号(用于批量格式化代码)

    一.在浏览器中将Params复制到pycharm的py文件中 二.选中需要添加引号的部分,Ctrl+H 调出替换工具栏 三.填写正则表达式 (.*?): (.*) '$1':'$2', 右侧注意点击使 ...

  6. Docker系列(18)- 具名挂载和匿名挂载

    容器数据卷挂载方式 容器的数据卷可以看成就是容器的挂载方式:一个宿主机有多个容器,多个容器挂载方式不同,因此宿主机就有多个卷 每一个挂载方式在宿主机上都有一个名称,即卷名 宿主机如何查看这些卷,对使用 ...

  7. shell脚本中 /dev/null 的用途

    /dev/null 是一个特殊的设备文件,它丢弃一切写入其中的数据 可以将它 视为一个黑洞, 它等效于只写文件, 写入其中的所有内容都会消失, 尝试从中读取或输出不会有任何结果,同样,/dev/nul ...

  8. java中的swing设计界面时怎么加上背景图片。而不覆盖其他控件?

    通过以下方式设置下背景就可以了: import java.awt.Container; import javax.swing.ImageIcon; import javax.swing.JFrame; ...

  9. PHP 7.4 checking for libzip 和 failed to open error_log 问题

    来源: https://hqidi.com/154.html 两个深坑,成年阿根廷龙踩出来的坑,网上都没找到解决方法,都是自己摸索出来的. 前面一切顺利: yum install -y libxml2 ...

  10. requests接口自动化-列表与字典参数化

    def server_ip(): # 配置文件,通过修改配置,在不同环境进行测试 # dev_ip='https://www.baidu.com/' # sit_ip='https://cn.bing ...