为什么JAVA可以通过 “类::实例方法” 的方式获取到方法的引用?

对于JAVA的方法引用“类::实例方法”的方式的疑惑

/*
1. 被引用方法的参数,需要与抽象方法的第2个到最后1个参数保持一致
    ps: 实例方法的第1个参数其实是隐含的this
        如果这个this (实例对象) 的类型与抽象方法的第一个参数类型一致,就可以用类名引用实例方法
*/

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c");
// 将集合中的字母转为大写
list.stream()
        // 数据流中的数据类型为 String,所以可以引用 String 类中的实例方法
        .map(String::toUpperCase)
        .forEach(System.out::println);

请问按照注释中的理解,是否是正确的呢?

阅读 713
3 个回答

这个是 Method Reference Expression

此时,如果它是类成员函数,调用的第一个参数就是调用函数的对象,后续参数是调用成员函数的参数。

基本跟你注释里写的差不多。

If the form is ReferenceType :: [TypeArguments] Identifier, the body of the invocation method similarly has the effect of a method invocation expression for a compile-time declaration which is the compile-time declaration of the method reference expression. Run-time evaluation of the method invocation expression is as specified in §15.12.4.3, §15.12.4.4, and §15.12.4.5, where:

  • The invocation mode is derived from the compile-time declaration as specified in §15.12.3.
  • If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference.
  • If the compile-time declaration is an instance method, then the arguments to the method invocation expression (if any) are the second and subsequent formal parameters of the invocation method. Otherwise, the arguments to the method invocation expression are the formal parameters of the invocation method.

可以用String.toUpperCase不是因为toUpperCase是String的方法,而是因为参数是String,你这里也可以调用Integer::valueOf,只要参数一样就可以,这其实是一个Function实现的简单写法,比如我想把一个字符串集合转成整形的

1.匿名实现

List<String> list = new ArrayList<>();
List<Integer> integers = list.stream().map(new Function<String, Integer>() {
            @Override
            public Integer apply(String string) {
                return Integer.valueOf(string);
            }
        }).collect(Collectors.toList());

2.等价于lambda

List<String> list = new ArrayList<>();
List<Integer> integers = list.stream().map(a->Integer.valueOf(a)).collect(Collectors.toList());

3.等价于方法引用

List<String> list = new ArrayList<>();
List<Integer> integers = list.stream().map(Integer::valueOf).collect(Collectors.toList());

常见的还有list.sort方法传Comparator,那个是两个参数,也可以简写。

一般写到lambda就行了,但是Idea会提示你转成方法引用,你一点就变成双冒号了。

====更新====
你说的我不敢确定,gpt说是传了隐式,是不是this我不太清楚;

我当初在stackoverflow提问过comparator方法引用为什么传一个参数就行了,但是compare要两个参数,他们也提到了一个是invoker,一个是参数
方法引用提问

当你在Java代码中调用方法时,编译器会将这些方法调用转换为Java字节码中的不同指令,以实现相应的功能。以下是这些指令的简要介绍:

invokedynamic
作用:在运行时动态选择要调用的方法。
用途:主要用于实现动态语言和动态生成的代码,以及在Java中与其他语言集成。
示例:在Java 7中引入,允许在运行时动态地绑定到目标方法。

invokevirtual
作用:调用对象的实例方法。
用途:用于调用普通的实例方法。
示例:常用于调用对象的方法,方法的选择在编译期间就确定。

invokeinterface
作用:调用接口中的方法。
用途:专门用于接口方法的调用。
示例:通常用于调用接口中定义的方法,灵活性相对于invokevirtual更高。

invokestatic
作用:调用类的静态方法。
用途:用于调用类的静态方法,无需实例化对象。
示例:常用于调用工具类的静态方法,或者在没有实例对象的情况下执行某些操作。

invokespecial
作用:调用特殊方法,如私有方法、构造方法和超类方法。
用途:用于调用私有方法、构造方法、超类方法等。
示例:常用于调用构造方法初始化对象,或者调用类中的私有方法。

这些指令在Java字节码中扮演着不同的角色,用于实现方法调用的不同场景。了解它们可以帮助你更好地理解Java程序的执行过程,以及在需要时进行优化或调试。

以上是AI介绍,Lambda表达式就是利用第一个指令完成的,没记错的话这个指令一直到14/15版本还在更新,你可以简单理解为有些方法调用是编译期就确定的,这就是一般理解的方法解析,但是使用invokedynamic指令可以做到在运行期动态绑定

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题