Java双冒号运算符引用任意对象实例方法的编译报错与适用场景
类名引用实例方法的双冒号语法规则与报错原因
核心认知纠偏
类名::实例方法名 这种双冒号写法,从来不会凭空隐式创建任意类的实例,之前的认知偏差是导致对代码行为产生困惑的根本原因。
这个语法的本质逻辑是:
- 把引用的实例方法,适配成对应函数式接口的抽象方法
- 适配时,会把函数式接口抽象方法的第一个入参,作为该实例方法的调用接收者(也就是
this指向的对象)
举个等价的lambda例子,Point::filter 本质完全等价于lambda表达式 (Point p) -> p.filter(),全程没有任何new Point的操作。
两个代码场景的行为解释
第n2行无报错的原因
代码行:list.stream().filter(Point::filter).forEach(System.out::println);
这里Stream.filter()方法要求传入的参数是Predicate<Point>类型,这个函数式接口的抽象方法签名为 boolean test(Point t):
- 抽象方法刚好有一个入参,类型是
Point - 这个入参会被自动作为
filter()实例方法的调用接收者 - 方法的返回值是
boolean,和Predicate要求的返回值完全匹配
整个签名适配完全顺畅,因此不会触发编译错误。
第n1行编译报错的原因
代码行:Supplier<Boolean> s = Point::filter;
这里Supplier<Boolean>的抽象方法签名为 Boolean get():
- 抽象方法没有任何入参,意味着没有可以传给
filter()方法当接收者的Point对象 - 编译器不会凭空创建一个新的Point实例来充当接收者,找不到和
filter()方法匹配的入参来源
此时Point::filter要求的调用前置条件(必须存在一个Point对象才能调用)和Supplier的无参方法签名完全不匹配,因此直接触发编译错误。
已存在对象的方法引用可正常赋值的原因
当使用new Point(0,0)::filter这种实例对象加双冒号的写法时,逻辑完全不同:
- 它会把已经创建好的Point对象,固定作为
filter()方法的永久接收者 - 后续调用这个方法引用时,不需要外部传任何对象当接收者
这种场景下,引用的方法签名变成了「无入参、返回boolean」,刚好和Supplier<Boolean>的抽象方法签名完全匹配,因此不会触发编译错误。
类名引用实例方法可正常工作的适用场景
这类写法能通过编译的场景,全部满足一个核心前提:对应函数式接口的抽象方法,第一个入参的类型和被引用实例方法所在的类完全一致,常见场景包括:
- Stream流处理中,当流的元素类型刚好是方法所在类时,传入
map、filter、peek等接收对应泛型函数接口的方法 - 为
Function<T,R>赋值T类::实例方法,把Function的入参自动作为实例方法的接收者 - 为
Comparator<T>赋值T类::比较类实例方法,把Comparator的两个入参中第一个作为接收者,第二个作为方法入参完成适配
所有适用场景中,不存在任何隐式创建新对象的逻辑,所有作为接收者的对象,都是由调用方在执行函数接口方法时传入的。
内容的提问来源于stack exchange,提问作者Jon B
相关产品推荐
相关产品推荐

