序
在看lambda的时候,经常看到 :: 这个双冒号符号,合着也不是c那一系列的语言,这玩意什么意思?百度了很久,原来是java8的新特性——方法引用。网上的看来看去就几篇,有几篇貌似还有冲突,现在总结一下自己的理解。
正文
首先,很重要的一点,方法引用是一个(精简的)lambda表达式。这就使得本被lambda精简过的代码更加精简了,让人摸不着头脑。(-> :: 尝尝给我一种我在看c++代码的错觉)所以,方法引用是一个函数式接口的实现(简化),由此当看的很懵逼的时候,把他还原有助于理解
ok,现在说说4种方法引用,这个网上有很多很多版本(叫法),看得我有点…这边我按照我的理解分一下。
名称 | 形式 |
---|---|
1 静态方法的引用 | 类名::静态方法名 |
2 构造方法的引用 | 类名::new |
3 实例对象的成员方法的引用 | 实例对象::成员方法名 |
4 类的成员方法的引用 | 类名::成员方法名 |
为什么要用方法引用?lambda意在精简,那么有时候我们仅仅是需要调用一个已存在的方法,如果使用方法引用就可以进一步精简(到丧心病狂的地步) 在详细解释之前,先来创造一个试验对象,然后详细的解释
试验对象
1 | // 实验对象 |
静态方法的引用
首先是最容易理解的,静态方法的引用
1 | class magicTest{ |
我在刚刚看方法应用的时候,经常会报错,各种各样的错。踩了很多坑之后,发现了一个要点:使用方法引用,必须注意入参与返回参,正文第一句话,强调过他其实是一个lambda表达式,所以该表达式实现的接口 ,引用方法,以及 接口调用处(使用 lambda表达式/方法引用表达式 的地方)的入参 与 返回参 的类型必须是要一致的。
用上面的代码来解释看看,首先我用的方法是Stream下的forEach方法,这个方法接收的是一个 Consumer的实现类 ,forEach内Consumer 默认的入参是 Stream转化前Collection的定义时传入的泛型类,在上面的代码中就是 Person。
1.接口调用处 入参 person ,同时实现的接口 入参 person 返回 void
2.再看看那个静态方法,入参 person 返回 void
3.ojbk
构造方法的引用
构造方法的引用看起来应该是最简单的,但是我在摸索的时候也进了很多坑,不过记住上面说的 入参 出参 保持一致,那应该就不会出什么问题了。
1 | //首先是java8系统提供的一个函数式接口Supplier,没有入参,获得一个泛型指定类型的对象,这对应的一般是默认的构造方法:无入参,返回对象 |
说道这,就有问题了,构造函数有入参呢?我也不能再new后面加个括号写参数啊!注意啊,方法引用是只写方法名的,不要(能)写括号和参数。回忆一下,方法引用是啥?是lambda表达式,是函数式接口的实现!所以这个这个问题可以自己定义函数式接口来解决。
1 | //先自己定义一个函数式接口:只能有一个方法,用@FunctionalInterface标注 |
实例对象的成员方法的引用
实例对象的引用,这个顾名思义,就是引用一个实例的方法,不多废话上代码
1 | class magicTest{ |
类的成员方法的引用
好了,终于到这里了。仔细看看这个标题,有没有违和感?类的成员方法的引用。一般来说的话,除了静态方法,有见过直接用类调用方法的么???没有吧!这tm就很蛋疼了,网上百度方法引用,全部直接给你个例子:
1 | //假设 O是类 |
这样的文章看了很多篇,经常还是第一个讲这种引用方式,给人一种很简单的感觉。先思考一下,下面的代码有没有问题:
1 | class magicTest{ |
好像也没什么问题嘛…playWith的方法接受一个person作为入参,而forEach正好提供了一个person,是不是?不是!把他还原出来看看问题就出来了!
1 | class magicTest{ |
真的是坑死我了,为什么?为什么刚刚的实例调用没问题,现在用类调用就有问题?对比一下accept的实现方法看看:
1 | //对象方法引用 |
一目了然,在对象方法引用的时候,接受的参数person很明显的作为对象boy的成员方法palyWith的入参,这实在是太正常了,让人不假思索就这样认为了。而类方法引用时就出了一点问题,貌似…一个参数不够啊?forEach提供了一个person,这一个person到底作为方法的调用者呢?还是方法的参数呢? 不可确定…
好,现在回过头去执行一下上一节的T4方法得输出到结果:
1 | magic play with a |
这输出结果和我们还原的实现方法一样!实例对象的引用没问题了,现在接着刚刚的话题,类方法所需对象少了一个。如果执行一个不需要的入参的方法会怎么样?在person类中新增一个成员方法:
1 | public class Person { |
由此可见,forEach中提供的参数person确实是被当做了类方法的调用者,由于Consumer在stream中使用的很频繁,那么做一个不负责的总结:实现Consumer的 类方法引用,那个类必须是Consumer的泛型类,并且方法不可接受参数,换句刷说就是 Consumer
继续思考这个问题,假如我就是要在用playWith方法的时候(有入参的方法)用类方法引用呢?刚刚已经想到了他缺了一个参数,那试验的方向就很明显了…
1 | //首先自建一个函数接口 |
到了这里就几乎已经有一个答案呼之欲出了,首先再次验证了在静态方法引用那一节归纳的,需要满足入参和出参对应一致。然后在做一个猜测:在stream.forEach中Consumer用类成员方法引用实现的时候,java可能已经隐式的替我们传入了一个对象,那一个对象作为consumer实现方法的执行者。 上面的几个试验也确实验证了这个猜测,无参方法(play)使用forEach传入的对象作为执行者。有参方法(playWith)仅有执行者无执行对象所以会报错,而使用了自定义的双参函数接口后,可以正常使用。
说道这里,回到本节开头,类的方法引用没有没违和感?有!确实,类不能直接使用成员方法(static除外),但是 类的成员方法引用 其实用了第一个接受的参数(该类的实例对象)作为方法的执行者,类并没有直接调用方法。所以在使用类方法引用的时候一定要注意,要提供该类的实例(验证就请各位自己验证吧,如果说错了请记得联系我打我脸)。
后记
这篇文章把自己在看方法引用的时候遇到的问题,思考的过程,个人的总结都记录了下来,以防自己忘记。
由于我没有给网站配置搜索和留言,这文在搜索引擎应该是搜不到的。如果有人能看到这篇文章,并且有疑问的话,可以联系我的qq 418379149.