Java8 方法引用的一个疑问:为什么能够引用接口的抽象方法? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
logtheone
V2EX    Java

Java8 方法引用的一个疑问:为什么能够引用接口的抽象方法?

  •  
  •   logtheone 2018-10-24 11:21:13 +08:00 3488 次点击
    这是一个创建于 2557 天前的主题,其中的信息可能已经有所发展或是发生改变。
    代码如下
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("hello");
    list.add("alex");
    list.add("frot");
    BiConsumer<List<String>, String> v = List::add;
    System.out.println(v == null);
    v.accept(list, "ddd");
    System.out.println(list);
    }

    IDE 为 IDEA,不报错,正常运行。
    我的疑问在于为什么下面这句话没有报错:
    BiConsumer<List<String>, String> v = List::add;
    List 是个接口,add 方法只有声明没有具体的实现,而且其前面明显和 BiConsumer 接口的 accept 不匹配。
    另外如果我把泛型去掉,变成下面这样就报错了:
    BiConsumer v = List::add;
    这又是为什么?
    17 条回复    2018-10-24 18:58:19 +08:00
    xbigfat
        1
    xbigfat  
       2018-10-24 11:33:59 +08:00 via iPhone
    new ArrayList ()实现了 add 方法
    logtheone
        2
    logtheone  
    OP
       2018-10-24 11:35:24 +08:00
    @xbigfat 能解释再清楚一点么?
    ffeii
        3
    ffeii  
       2018-10-24 11:37:16 +08:00 via iPhone
    List::add 等同于 (list, str) -> list.add(str)
    Cbdy
        4
    Cbdy  
       2018-10-24 11:38:26 +08:00
    List::add 你可以理解成为这样一个函数
    BiConsumer<List<String>, String> v = (List<String> l, String e) -> l.add(e);
    solu
        5
    solu  
       2018-10-24 11:41:10 +08:00
    取决于 List<String>的实现,你这里就是 ArrayList 了呀
    xbigfat
        6
    xbigfat  
       2018-10-24 11:43:38 +08:00
    1.泛型去掉后,编译器不知道你输入输出的是什么类型,所以报错。BiConsumer 是输入 T 返回 R,不写出类型是编译器报错,运行时泛型是擦除掉的。
    2. new ArrayList( ) 里面,ArrayList 实现了 add ( ) 方法,所以可以运行。
    3.你困惑是因为 Lambda 的缩减.将
    ```BiConsumer<List<String>, String> v = List::add;```
    替换为:
    ```
    List<String> list = new ArrayList<>();
    list.add("hello");
    list.add("alex");
    list.add("front");
    BiConsumer<List<String>, String> v = new BiConsumer<List<String>, String>() {
    @Override
    public void accept(List<String> strings, String e) throws Exception {
    strings.add(e);
    }
    };
    ```
    这里还原成匿名内部类,能看懂了吗?

    strings 传递来的是 list 的引用,e 传来的是 “ ddd"
    serical
        7
    serical  
       2018-10-24 11:48:29 +08:00 via Android
    具体实现取决于 v.accept 第一个参数的类型
    xbigfat
        8
    xbigfat  
       2018-10-24 11:48:46 +08:00
    补充一下去除泛型报错的原因。
    public interface BiConsumer<T1, T2> {

    /**
    * Performs an operation on the given values.
    * @param t1 the first value
    * @param t2 the second value
    * @throws Exception on error
    */
    void accept(T1 t1, T2 t2) throws Exception;
    }

    accept( ) 方法依赖泛型指定对象的类型,要不然,谁都没法操作吧。。。
    kuko126
        9
    kuko126  
       2018-10-24 11:48:55 +08:00   2
    方法引用的几种写法,其中有
    类名::实例方法名
    若 Lambda 表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,就可以使用这种方法
    https://blog.csdn.net/TimHeath/article/details/71194938
    所以可以从
    BiConsumer<List<String>, String> v = (list1, s) -> list1.add(s);
    转换成
    BiConsumer<List<String>, String> v = List::add;

    2 是因为泛型不写默认就是 Object,Object 里没有 add 方法所以编译会报错
    可以试一下下面的看一下区别
    BiConsumer<List<String>, String> v = (list1, s) -> list1.add(s);
    BiConsumer<List<String>, String> v = ArrayList::add;
    BiConsumer v = Object::equals;
    yidinghe
        10
    yidinghe  
       2018-10-24 11:50:23 +08:00
    这条语句是在声明一个方法引用,而并不是真的调用这个方法。这样理解就知道为什么符合语法了。
    logtheone
        11
    logtheone  
    OP
       2018-10-24 14:13:12 +08:00
    @ffeii
    你这个等同有依据么?? List 的 add 的方法签名都和 accept 不一样,怎么等同过来的?
    logtheone
        12
    logtheone  
    OP
       2018-10-24 14:24:40 +08:00
    @kuko126
    你的这个回答说到点子上了。

    再问一句,这句话“若 Lambda 表达式的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,就可以使用这种方法”当 Lambda 表达式有 3 个以上的参数时,还适用么?另外这句话有权威来源么?
    kuko126
        13
    kuko126  
       2018-10-24 14:49:28 +08:00   1
    如果有三个及以上参数 就要用 (a, b, c) -> a.func(b, c); 这种形式
    要权威的话可以看下这个 https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
    logtheone
        14
    logtheone  
    OP
       2018-10-24 15:43:14 +08:00
    @kuko126
    感谢!
    passerbytiny
        15
    passerbytiny  
       2018-10-24 18:26:14 +08:00
    Lambda 表达式为啥要跟函数式接口一起出现,把我给看晕了。

    List::add 的意思,不是调用 List 接口的 add 方法,你这点理解错了,所有后面的理解就走不通了。

    A::B 是一种 Lambda 缩写方式,并不是调用 A 的 B 方法,当 Lambda 表达式只有一个语句,并且可以通过函数式接口、A、B,来进行推断的时候,才能使用这种缩写形式。当 A 是类的实例,B 是方法名的时候,看起来可能像是表示调用 A 对象的 B 方法。但当 A 是类,或者 B 是 new 这种特殊字的时候,就不能那么看了。

    函数式接口是“ BiConsumer<T, U> void accept(T t, U u)”,上下文语句“ BiConsumer<List<String>, String> v ”决定了:T 是 List<String>、U 是 String。

    结合查看 void accept(T t, U u) 和 List::add:
    accept 提供了两个参数; List.add 方法只需要一个参数; List 是接口定义而不是具体实例。

    那么自然的,将第一个参数当成 List.add 方法的执行主体,将第二个参数当成方法的参数,于是还原成了:(arg_List,arg_String)->{arg_List.add(arg_String);}


    你也可以这样认为,List::add 定义了一个代表 List.add 方法的 Method,然后执行的时候就是 Method.invoke(obj,args...)。此时,“ v.accept(list, "ddd")”相当于:method.invoke(list,"aaa")。执行的是你通过“ List<String> list = new ArrayList<>();”创建的 list 对象,而不是 List 接口。
    passerbytiny
        16
    passerbytiny  
       2018-10-24 18:48:10 +08:00 via Android
    忽略我上边的还原过程吧,错了。A::b 表示的就是方法签名,即 A 类 /接口的 b 方法,所以 List::add 就代表 List 接口的 add 方法,然后再结合 accpet ( list, "aaa"),要推断要执行的是:<List>list.add("aaa")。
    passerbytiny
        17
    passerbytiny  
       2018-10-24 18:58:19 +08:00 via Android
    SomeClass::new,就是该类构造器的方法签名,invoke 的时候不需要第一个参数; someInstance::method,表示方法签名以及 invoke 时的第一个参数。因此,Lambda 表达式右边方法的参数个数,才跟函数式接口方法参数的个数相同。

    跟反射机制已对比,容易理解多了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana   span class="snow">   4851 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 05:41 PVG 13:41 LAX 22:41 JFK 01:41
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86