2019-11-21-Java8方法引用实践

为什么要使用方法引用

在我们日常使用Java8的时候,经常要对一个集合进行操作,要对他做点什么,类似于下面这样

1
2
3
List<User> list = new ArrayList();
要获取所有用户的id
List<Integer> userIds = list.stream().map(e -> e.getId()).collect(Collectors.toList());

其实这段代码可以用一个方法引用来替换掉e -> e.getId(),变成下面这样

1
List<Integer> userIds = list.stream().map(User:getId).collect(Collectors.toList());

是不是很酷,咋一看,貌似很高级的样子。对,我们就是要“高级”一点~ ps:其实就是为了装逼

方法引用基本介绍

什么是方法引用

方法的引用是用来直接访问类或者实例的已经存在的方法或者构造方法,方法引用提供了一种引用而不执行方法的方式

方法引用的分类

类型 语法 对应lambda表达式
构造方法引用 类名::new (args) -> new 类名(args)
对象方法引用 对象::实例方法 (args) ->对象.实例方法(args)
实例方法引用 类名::实例方法 (对象,args) -> 类名.实例方法(args)
静态方法引用 类名::静态方法 (args) -> 类名.静态方法(args)

什么时候使用

如果函数式接口的实现恰好可以通过调用一个方法(对象|实例|静态|构造)来实现,那么就可以使用方法引用

特别注意下实例方法引用:

  1. 抽象方法的第一个参数类型刚好是实例方法的对象,抽象方法剩余的参数恰好可以当作实例方法的参数
  2. 抽象方法的第一个参数类型刚好是实例方法的对象,并且只有一个参数,返回值是实例方法对应的类型

上面的这个抽象方法,实际上就是函数式接口里面的方法

类似于这样,get()方法就是一个抽象方法

1
2
3
4
@FunctionalInterface
public interface Supplier<T> {
T get();
}

函数式接口介绍

1
2
3
4
5
6
| 类型        | 接口名          | 方法        |
| ------- | ------------ | --------- |
| 供给型接口 | Supplier<T> | T get() |
| 消费型接口 | Consumer<T> | void accept(T t) |
| 断言型接口 | Predicate <T> | boolean test(T t) |
| 函数型接口 | Function<T,R> | R apply(T t) |

Java提供了四大基本的函数式接口

以此作为基础其实又派生了很多的函数式接口BiConsumer,IntFunction等等…

如何使用方法引用

先创建一个测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class MyTest {
/**
* 静态方法
*/
public Object getObj() {
return new Object();
}
/**
* 实例方法
*/
public static Object getStaticObj() {
return new Object();
}
/**
* 实例方法
*/
public void consumerObj(Object o) {
System.out.println(o);
}
/**
* 静态方法
*/
public static void consumerStaticObj(Object o) {
System.out.println(o);
}
/**
* 实例方法
*/
public boolean testObj(Object obj) {
return obj.getClass() == Object.class;
}
/**
* 静态方法
*/
public static boolean testStaticObj(Object obj) {
return obj.getClass() == Object.class;
}
/**
* 实例方法
*/
public Integer func(Integer i) {
return i + 520;
}
public Integer func() {
return 520;
}
/**
* 静态方法
*/
public static Integer staticFunc(Integer i) {
return i + 520;
}

/**
* 实例方法
*/
public void hello(String obj) {
System.out.println(obj);
}
}

供给型接口的使用

1
2
3
4
5
6
7
8
9
10
// 1.正常的lambda表达式
Supplier<Object> s2 = () -> new Object();
// 2.使用方法引用替换
Supplier<Object> s1 = Object::new; // 构造方法引用
Supplier<Object> s3 = new MyTest()::getObj; // 对象方法引用
Supplier<Object> s4 = MyTest::getStaticObj; // 静态方法引用
System.out.println(s1.get());
System.out.println(s2.get());
System.out.println(s3.get());
System.out.println(s4.get());

小伙伴看懂了吗~如果没看懂的话再结合我开头的方法引用分类表格看,

不过很遗憾,供给型接口并不可以使用实例方法引用来实现,

原因:因为这个接口并不满足实例方法引用使用的条件

消费型接口的使用

1
2
3
4
5
6
7
8
// 1.正常的lambda表达式
Consume<Object>r c1 = (obj) -> System.out.println(obj);
// 2.使用方法引用替换
Consumer<Object> c2 = new MyTest()::consumerObj; // 对象方法引用
Consumer<Object> c3 = MyTest::consumerStaticObj; // 静态方法引用
c1.accept(new Object());
c2.accept(new Object());
c3.accept(new Object());

不过很遗憾,供给型接口并不可以使用构造方法引用实例方法引用来实现,

原因:因为这个接口并不满足使用构造方法引用和实例方法引用的使用条件

断言型接口的使用

1
2
3
4
5
6
7
8
// 1.正常的lambda表达式 
Predicate<Object> p1 = (obj) -> obj.getClass() == Object.class;
// 2.使用方法引用替换
Predicate<Object> p2 = new MyTest()::testObj; // 对象方法引用
Predicate<Object> p3 = MyTest::testStaticObj; // 静态方法引用
System.out.println(p1.test(new Object()));
System.out.println(p2.test(new Object()));
System.out.println(p3.test(new Object()));

不过很遗憾,供给型接口并不可以使用构造方法引用实例方法引用来实现,

原因同消费型接口

函数型接口的使用

1
2
3
4
5
6
7
8
9
10
11
// 1.正常的lambda表达式 
Function<Integer, Integer> f1 = (e) -> e + 520;
// 2.使用方法引用替换
Function<Integer, Integer> f2 = new MyTest()::func; // 对象方法引用
Function<Integer, Integer> f3 = MyTest::staticFunc; // 静态方法引用
Function<MyTest, Integer> f4 = MyTest::func; // 实例方法引用 注意看重载和第一个泛型类型
System.out.println(f1.apply(520));
System.out.println(f2.apply(520));
System.out.println(f3.apply(520));
// 要使用实例方法第一个泛型类型一定保持和当前引用的类一致,因为它相当于调这个类的方法,传了一个对象进去
System.out.println(f3.apply(new MyTest()));

不过很遗憾,供给型接口并不可以使用构造方法引用

因为不满足

实例方法引用的使用

定义一个函数式接口需要 使用@FunctionalInterface标记,并且接口中只能有一个方法(使用default修饰的默认方法除外)这是规范哦!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);

}
// 1.正常的lambda表达式
BiConsumer<String, String> bi1 = (x, y) -> System.out.println(x + y);
// 2.使用方法引用替换
BiConsumer<MyTest, String> bi2 = MyTest::hello; // 实例方法引用
bi1.accept("hello", "World");
bi2.accept(new MyTest(), "helloWorld");
console: 输出
helleWorld
helleWorld

啦啦啦啦~ 是不是超级简单呢

总结

我们操作steam流的时候基本上都要结合函数式接口来完成,弄清楚这些基本的函数式接口以及他们对应函数接口的写法,那么会对我们使用stream提供莫大的帮助

回到最初的那个lambda表达式

1
2
List<User> list = new ArrayList();
List<Integer> userIds = list.stream().map(e -> e.getId()).collect(Collectors.toList());

可以看一下map()这个方法接收一个什么样的函数式接口,下面是map方法的定义

1
2
// 可以看到是接收一个函数型的接
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

再看一下User类中getId()方法的定义

1
2
3
public Integer getId() {
return id;
}

咦~这不正是符合了实例方法引用的要求嘛,所以刚好可以用User::getId来实现

啦啦啦,是不是瞬间恍然大悟,哈哈哈哈~其实编程本身是枯燥的,但通过自己的努力理解或掌握知识的结果是很美好的。

后续还会继续更新关于Java8的一些文章,敬请期待

最后,希望大家都被岁月温柔以待 !

-------------本文结束 感谢您的阅读-------------
湖南有北 wechat
扫码进群哦~~
你可以对我打赏哦