一、Consumer:消费型接口
源码如下:
@FunctionalInterface 注解说明该接口是一个函数式接口(接口中只有一个抽象方法)。
参数 T 就是我们要处理的对象类型。
功能:实现 Consumer 接口中的 accept 方法,对类型 T 的对象进行任意操作(自己实现处理逻辑),所以这个接口也称之为消费性接口。
使用:
方式一
创建一个类,实现该接口
class MyConsumer<T> implements Consumer<T> { @Override public void accept(T t) { // 自己的处理逻辑 System.out.println("======开始做一下事情,对传入的 T======"); System.out.println("正在对 T(" + t + ") 做事情..."); System.out.println("======end======"); } }
测试
public class ConsumerTest { @Test public void test() { MyConsumer<String> stringMyConsumer = new MyConsumer<>(); List<String> stringList = Arrays.asList("a", "b", "c", "d"); // 遍历 for (String s : stringList) { // 处理每一个元素 stringMyConsumer.accept(s); } } }
这种方式完全没有利用到 JDK8 的特性,只是用了它提供的一个接口而已,那下面我们就换种方式。
方式二
直接一步到位:
public class ConsumerTest { @Test public void test2() { List<String> stringList = Arrays.asList("a", "b", "c", "d"); // 利用 Lambda 表达式 stringList.forEach(str -> { System.out.println("我可以对 str(" + str + ") 做任何事情"); }); } }
如果上面不理解哪里用到了消费型接口,那我拆分一下
public class ConsumerTest { @Test public void test3() { List<String> stringList = Arrays.asList("a", "b", "c", "d"); handle(stringList, s -> { // 内部类参数,实现处理逻辑 System.out.println("我可以对 s(" + s + ") 做任何事情,而且我还知道他是什么类型"); }); } public <T> void handle(List<T> list, Consumer<T> consumer) { for (T t : list) { // 调用处理方法 consumer.accept(t); } } }
是不是非常清晰了!
其实我们在 JDK8 中经常使用的 forEach 遍历就是一种消费型接口的实现模式。
二、Supplier:供给型接口
源码:
功能:提供指定类型的数据出去,所以也称该接口为供给型接口。
使用
:
方式一:
- 创建一个类,实现该接口
@Data class MySupplier<T> implements Supplier<T>{ private String name; @Override public T get() { // 我将提供一个指定 T 类型数据出去 // 这里的 T 我先内定为 MySupplier 类型进行编写案例 MySupplier<T> t = new MySupplier<>(); t.setName("哈哈!"); return (T) t; } }
测试
public class SupplierTest { @Test public void test1(){ MySupplier<MySupplier> stringMySupplier = new MySupplier<>(); System.out.println(stringMySupplier.get()); } } // SupplierTest.MySupplier(name=哈哈!)
还是老样子,这种方式也是没有利用 JDK8 特性,那我们改造一下
方式二:
public class SupplierTest { @Test public void test2(){ Supplier<String> stringSupp = () ->{ // 你的逻辑 return "调用get方法,我才给你数据"; }; System.out.println(stringSupp.get()); } } // 调用get方法,我才给你数据
说一下我的理解,这个类型接口主要的就是提供数据,那么在项目中的话我可以事先定义好一组数据,在特定的时候通过事先定义好的对象去 get 就行。
场景:
用户购买商品,进入业务,判断用户购买的条件是否满足附送礼品的条件,如果满足,那么判断礼品种类(A,B,C等),然后根据 Supplier 类型接口实现类获取对应的礼品返回给用户购买的商品列表中。这样的好处就是用户不满足条件时是不会给项目创建礼品对象的,只有 get 方法调用,才会生成对象形成一个懒加载的效果。
三、Function:函数型接口
源码:
参数 R
就是该接口方法返回的类型。
功能:传入一个 T 类型对象,调用 apply 方法进行处理,最终于返回处理后的数据类型为 R
。
使用
:
方式一:
- 编写一个类,实现该接口
class MyFunction<T, R> implements Function<T, R> { @Override public R apply(T t) { System.out.println("处理逻辑....【" + t + "】"); return null; } }
2.测试
public class FunctionTest { @Test public void test1() { MyFunction<String, String> myFunction = new MyFunction<>(); myFunction.apply("J3-白起..."); } } // 处理逻辑....【J3-白起...】
老样子,我们用新特性改造一下。
方式二:
public class FunctionTest { @Test public void test2() { Function<String, String> myFunction = (t) ->{ System.out.println("处理逻辑....【" + t + "】"); return "success"; }; myFunction.apply("J3-白起..."); } } // 处理逻辑....【J3-白起...】
上面就是这个接口的简单使用,那我再来写个案例方便理解。
案例:
需求:根据一个姓名列表,获取对应姓名长度的列表,实现代码如下:
public class FunctionTest { @Test public void test3() { List<String> nameList = Arrays.asList("J3-baiqi", "shaheshagn", "sunwukong"); List<Integer> nameLengthList = functionHandle(nameList, String::length); System.out.println(nameLengthList); // [8, 10, 9] } /** * 获取列表中,姓名的长度 * * @param nameList 名字列表 * @param function 名字长度列表 * @return */ public List<Integer> functionHandle(List<String> nameList, Function<String, Integer> function) { List<Integer> nameLengthList = new ArrayList<>(nameList.size()); nameList.forEach(name -> nameLengthList.add(function.apply(name))); return nameLengthList; } }
讲解一下:
我分两个地方说明,一个是实参、一个是形参。
先说形参,对于该需求,我抽了一个方法出来,专门实现获取列表中元素的长度。功能很明确就是获取一个长度值,但具体是如何实现,我不管,我只要传入数据给你,你返回我需要的数字就行,所以我将形参定义为 Function 接口,在要获取数字的的时候,调用接口的 apply 方法即可。
对于实参,调用方法时,我需要传递 Function 接口类型对象进去,我可以写一个实现类,然后实现 apply 方法,接着通过各种新奇古怪的技术,获取传入的对象长度就行。但既然是 JDK8 那我就完全可以用新特性去实现(方法引用)。
场景:这个在项目中使用的场景是非常多的,我经常是需要抽取方法,然而方法上的形参我会定义成一个接口形成规范,因为我不管实现,我只管调用就行。以后有其他同事使用我的方法时,你自己去实现我形参中的接口就行,然后我的方法就会根据你实现的接口获取数据进而给你想要的结果。
四、Predicate:断定型接口
源码:
功能:传入指定类型对象,返回 true 或 false,所以也称该接口为断定型接口。
使用
:
方式一:
- 编写一个类,并实现该接口
class MyPredicate<T> implements Predicate<T> { @Override public boolean test(T t) { // 判断如果传入的类型 T 是 String 那么判断是否含有 "J3" 字样 if (t instanceof String) { return ((String) t).contains("J3"); } if (t instanceof Integer) { return ((Integer) t) == 18; } // 如果是 Integer 类型,判断是否等于 18 return false; } }
2.测试
public class PredicateTest { @Test public void test1(){ MyPredicate<String> stringMyPredicate = new MyPredicate<>(); System.out.println(stringMyPredicate.test("J3-白起")); System.out.println(stringMyPredicate.test("白起")); MyPredicate<Integer> integerMyPredicate = new MyPredicate<>(); System.out.println(integerMyPredicate.test(18)); System.out.println(integerMyPredicate.test(28)); } } //true //false //true //false // 本人依旧是 J3 依旧是18,哈哈!
老样子,看一下下面改造的样子
方式二:
public class PredicateTest { @Test public void test2() { Predicate<String> myStringPredicate = (t) -> t.contains("J3"); System.out.println(myStringPredicate.test("J3-白起")); System.out.println(myStringPredicate.test("白起")); Predicate<Integer> myIntegerPredicate = (t) -> t == 18; System.out.println(myIntegerPredicate.test(18)); System.out.println(myIntegerPredicate.test(28)); } }
这个类型接口关键点在于返回的结果是一个 boolean 类型,那它的定位就非常清楚了,用于判断而已,具体你要判断什么,这就取决于你了。
需求案例:
现在我们有个系统是分 pc 和 app 端的,用户注册后信息进入业务中,就要区别出来是哪个端的用户注册。这就可以用到 Predicate 接口了,将用户信息传给 test,然后就可以编写判断逻辑根据对应的信息区别出是 pc 还是 app 了。
五、最后
最后,画一张表简单归纳一下上面分析的四个接口如下:
好了,今天的内容到这里就结束了,关注我,我们下期见
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
感谢您的阅读,十分欢迎并感谢您的关注。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^