Java 8中形参为Function却传入疑似Supplier引用为何正常运行
问题背景
需要基于Person类的多个属性过滤重复元素,对应的去重工具方法定义如下:
private static <T> Predicate<T> distinctByKeys(final Function<? super T, ?>... keyExtractors) { final Map<List<?>, Boolean> seen = new ConcurrentHashMap<>(); return t -> { final List<?> keys = Arrays.stream(keyExtractors) .map(ke -> ke.apply(t)) .collect(Collectors.toList()); return seen.putIfAbsent(keys, Boolean.TRUE) == null; }; }
方法调用代码如下:
List<Person> distinctPersons = list.stream() .filter(distinctByKeys(Person::getFirstName, Person::getLastName)) .collect(Collectors.toList());
疑问点:distinctByKeys的入参要求是Function类型(函数式接口,签名为接收1个入参、返回1个结果),但传入的Person::getFirstName、Person::getLastName是无参的getter方法,直觉上更符合Supplier(无入参、返回结果)的签名,为什么代码可以正常编译运行?
实现原理解释
这个现象的核心是Java方法引用的类型匹配规则,本质是混淆了不同写法的方法引用的适配逻辑:
- Java的方法引用存在多种匹配模式,
Person::getFirstName属于类名引用实例方法的模式,和绑定了具体实例的方法引用适配规则完全不同:- 如果提前持有一个Person类的实例对象
person,写person::getFirstName时,调用getter方法的目标对象已经固定为person,这个方法引用不需要额外传入参数即可返回结果,此时确实匹配Supplier<String>的函数签名。 - 代码中写法为
Person::getFirstName时,没有提前绑定调用方法的具体实例,Java编译器会自动将适配的函数式接口的第一个入参,作为调用该实例方法的目标对象。它等价的Lambda表达式为(Person t) -> t.getFirstName(),这个签名完全符合Function<Person, String>的类型要求:接收一个Person类型入参,返回对应属性值,自然满足distinctByKeys方法的入参约束。
- 如果提前持有一个Person类的实例对象
- 方法执行时,流中的每个Person对象会被传入
Function的apply方法,正好作为调用getter的目标实例,逻辑完全自洽。
快速判断规则:如果方法引用写法为
类名::实例方法,函数式接口的第一个参数会被隐式作为方法调用的实例对象;如果写法为具体实例::实例方法/类名::静态方法,才会直接按照方法本身的参数列表匹配函数式接口的入参。
内容的提问来源于stack exchange,提问作者jayendra bhatt
相关产品推荐
相关产品推荐

