Jdk8提供的函数式接口都在java.util.function包下,Jdk8的函数式类型的接口都有@FunctionInterface注解所标注,但实际上即使没有该注解标注的有且只有一个抽象方法的接口,都可以算是函数式接口。
在JDK8中内置的四大核心函数式接口如下:
接口类型 | 参数类型 | 返回类型 | 作用 | Stream流中的应用场景 | |
---|---|---|---|---|---|
Consumer |
消费型接口 | T | void | 对类型为T的对象进行操作,包含方法为accpet(T t) | 如forEach、peek等方法的函数式接口都是Consumer类型 |
Supplier |
供给型接口 | 无 | T | 返回类型为T的对象,包含方法为T get() | 如collect等方法的某些方法重载就是用的Supplier类型 |
Function |
函数型接口 | T | R | 对类型为T的对象进行操作,返回结果为 R类型的对象,包含方法为R apply(T t) | 如map,flatMap等方法的函数式接口都是Function类型 |
Predicate |
断言型接口 | T | boolean | 确定类型为T的对象是否满足约束,并返回约束结果,包含方法为boolean test(T t) |
Consumer
Consumer
消费型接口,顾名思义就是消费并处理参数,且不反馈调用环境
基本使用
public class Main { /** * Consumer* 消费型接口:顾名思义主要用于消费参数,不反馈调用环境(没有返回值) * accept: 抽象方法实现,用于调用方法。 * andThen: 默认实现方法,内部允许我们链式调用 */ public static void main(String[] args) { // 给定字符串转为大写并输出到控制台,匿名内部类的方式实现 Consumercon1 = new Consumer () { @Override public void accept(String str) { System.out.println("通过匿名内部类的方式:"+ str.toUpperCase()); } }; // 执行该方法的时候,我们传入了给定参数字符串,它会去执行我们上述实现的accept方法并传入参数,最后执行我们给定的代码逻辑 con1.accept("abc"); // 给定字符串转为大写并输出到控制台,通过Lambda表达式实现 Consumer con2 = (text)-> System.out.println("通过Lambda表达式的方式:"+ text.toUpperCase()); con2.accept("goods"); /** * 最终结果: * 通过匿名内部类的方式:ABC * 通过Lambda表达式的方式:GOOD * 使用lambda表达式,我们只需要记住参数列表和执行逻辑即可,其他的我们无需关注。 */ }}
学习案例
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 | public class Main { /** * Consumer * 消费型接口:顾名思义主要用于消费参数,不反馈调用环境(没有返回值) * accept: 抽象方法实现,用于调用方法。 * andThen: 默认实现方法,内部允许我们链式调用 */ public static void main(String[] args) { // 1.我们需要将集合进行排序后在输出到控制台 Consumer System.out.println( "排序前的集合:" + list); Collections.sort(list); System.out.println( "排序后的集合:" +list); }; con1.accept(Arrays.asList( 1 , 5 , 3 , 2 , 9 , 6 , 7 )); /** * 最终结果: * 排序前的集合:[1, 5, 3, 2, 9, 6, 7] * 排序后的集合:[1, 2, 3, 5, 6, 7, 9] */ // 上面执行逻辑实现分两步,第一步需要获取到给定集合进行排序,第二个则是输出排序后的集合 // 如果以上两个步骤分别用两个consumer也可以实现,我们可以定义一个方法接收两个consumer进行操作 accept(Arrays.asList( 1 , 5 , 3 , 2 , 9 , 6 , 7 ),list-> { System.out.println( "andThen链式调用前集合:" +list); Collections.sort(list); },list-> System.out.println( "andThen链式调用后集合:" + list)); /** * 最终结果: * andThen链式调用前集合:[1, 5, 3, 2, 9, 6, 7] * andThen链式调用后集合:[1, 2, 3, 5, 6, 7, 9] */ // 如果consumer参数多个的话,我们可以直接在Lambda表达式进行链式调用,不费那劲定义方法了 Consumer System.out.println( "lambda表达式的链式调用前集合:" + list); Collections.sort(list); }).andThen(list -> System.out.println( "lambda表达式的链式调用后集合:" +list)); // 需要注意的是:要使用这种方式,第一个consumer要进行链式调用必须要强行指定为(Consumer)类型,后续的接口才能够调用方法 con2.accept(Arrays.asList( 1 , 53 , 31 , 25 , 99 , 62 , 17 )); /** * 最终结果: * lambda表达式的链式调用前集合:[1, 53, 31, 25, 99, 62, 17] * lambda表达式的链式调用后集合:[1, 17, 25, 31, 53, 62, 99] */ } public static void accept(List // 链式调用时会优先执行左边的接口实现,依次往右执行 我们的需求是先排序后输出,第一个Consumer是排序,第二个是输出。 con1.andThen(con2).accept(list); } } |
总结
1.函数式接口的本质实际上就是将函数以参数的形式进行传递
2.Consumer是消费型的函数式接口,通常用于数据内部处理,没有返回值
3.除了Consumer之外,还有各种消费型的函数式接口,还有IntConsumer、LongConsumer等、如果需要传递两个参数则可以使用BIFunction、也可以根据自身需求进行自定义。
Supplier
Consumer
供给型函数式接口,顾名思义就是供给数据给调用环境,不接收参数传递
基本使用
public class Main{ /** * 供给型函数式接口顾名思义就是顾名思义就是供给数据给调用环境,不接收参数传递 * T get() : 返回泛型T类型的参数到调用环境 */ public static void main(String[] args) { // 返回一个0-100间的随机数 Suppliersup1 = new Supplier () { @Override public Integer get() { int res = new Random().nextInt(100); System.out.println("通过匿名内部类的方式获取到的随机数:"+ res); return res; } }; // 执行该方法的时候,它会去执行我们上述实现的get方法。 sup1.get(); // 通过lambda表达式的方式进行实现 Supplier sup2 = ()-> { int res = new Random().nextInt(100); System.out.println("通过lambda表达式的方式获取到的随机数:"+ res); return res;}; sup2.get(); /** * 最终结果: * 通过匿名内部类的方式获取到的随机数:28 * 通过lambda表达式的方式获取到的随机数:62 * 使用lambda表达式,我们只需要记住参数列表和执行逻辑即可,其他的我们无需关注。 */ } }
学习案例
public class Main{ public static Mapredis = new HashMap(); /** * 供给型函数式接口顾名思义就是顾名思义就是供给数据给调用环境,不接收参数传递 * T get() : 返回泛型T类型的参数到调用环境 */ public static void main(String[] args) { // 1.(模拟)查询某个Key在redis中有没有缓存,缓存没有则从数据库取完存入redis再返回,有的话则直接返回 String val = getCache("title"); String val2 = getCache("title"); String val3 = getCache("title"); /** * 最终结果: 从数据库中获取:我是标题 从缓存中获取:我是标题 从缓存中获取:我是标题 */ // 可以看到经过第一次后续都是直接从缓存中取出的数据 } public static String getCache(String key){ String val = redis.get(key); if(Objects.isNull(val)){ // 获取数据库的数据 val = getDbVal(() -> "我是标题"); System.out.println("从数据库中获取:"+val); redis.put(key,val); return val; } System.out.println("从缓存中获取:"+val); return val; } public static String getDbVal(Supplier supplier){ return supplier.get(); } }
总结
1.函数式接口的本质实际上就是将函数以参数的形式进行传递
2.Supplier是供给型的函数式接口,通常用于构建某个对象处理后返回调用环境
3.除了Supplier之外,还有各种供给型的函数式接口,还有BooleanSupplier、IntSupplier等。
Function
Function
函数型的函数式接口,泛型T是参数、泛型R则是返回值、主要应用场景做数据类型转换等。
基本使用
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 61 62 63 64 | public class Main{ /** * Function * R apply(T t): 抽象方法实现,用于调用方法并返回泛型R * * * */ public static void main(String[] args) { // 传入给定字符串,返回转换后的Integer类型 Function new Function @Override public Integer apply(String s) { Integer convert = Integer.valueOf(s); System.out.println( "通过匿名内部类的方式获取到的值:" + convert + ",数据类型是否为Integer?结果:" + (convert instanceof Integer)); return convert; } }; // 执行该方法的时候,它会去执行我们上述实现的apply方法。 fun1.apply( "10086" ); // 通过lambda表达式的方式进行实现 Function Integer convert = Integer.valueOf(s); System.out.println( "通过lambda表达式的方式获取到的值:" + convert + ",数据类型是否为Integer?结果:" + (convert instanceof Integer)); return convert; }; fun2.apply( "10000" ); /** * 通过匿名内部类的方式获取到的值:10086,数据类型是否为Integer?结果:true * 通过lambda表达式的方式获取到的值:10000,数据类型是否为Integer?结果:true * 使用lambda表达式,我们只需要记住参数列表和执行逻辑即可,其他的我们无需关注。 */ // 我们继续对Function的API做一些理解和补充,毕竟这玩意在工作中经常会用上 // andThen 我们都知道常用于链式调用的,这里必须保证T和V类型是一样的,也就是参数泛型T和返回值泛型V Function System.out.println( "我是fun3的方法" ); return x; }; Function System.out.println( "我是fun4的方法" ); return y; }; fun3.andThen(fun4).apply( "test" ); /** * 最终结果: * 我是fun3的方法 * 我是fun4的方法 */ // 我们发现这里是先执行fun3的apply方法再执行fun4的apply方法的。 // compose 与andThen一样都是链式调用,但结果却大大不同,这里必须保证T和V类型是一样的,也就是参数泛型T和返回值泛型V fun3.compose(fun4).apply( "test" ); /** * 最终结果: * 我是fun4的方法 * 我是fun3的方法 */ // 我们发现这里是先执行的fun4的apply方法再执行fun3的apply方法的 // 由此我们推断出compose和andThen的区别就在于,compose接口方法执行顺序从右到左,而andThen则是从左到右。 Function // Function.identity() 静态方法这里就不好演示了,这个通常在后面搭配Stream流转Map类型的时候用到,它返回本身 } } |
学习案例
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 | public class Main{ /** * Function * R apply(T t): 抽象方法实现,用于调用方法并返回泛型R * * * */ public static void main(String[] args) { List new Person( 1 , "张三" ), new Person( 2 , "李四" )); // 给定一个person对象集、转换成姓名属性集合返回 Function List new ArrayList<>(); for ( int i = 0 ; i < list.size(); i++) { arr.add(list.get(i).getName()); } return arr; }; List System.out.println(personNames); /** * 最终结果: * 结果:[张三, 李四] */ } } class Person{ private Integer id; private String name; public Person(Integer id, String name) { this .id = id; this .name = name; } public void setId(Integer id) { this .id = id; } public void setName(String name) { this .name = name; } public Integer getId() { return id; } public String getName() { return name; } } |
总结
1.函数式接口的本质实际上就是将函数以参数的形式进行传递
2.Function是函数型的函数式接口,通常用于构建某个对象处理后返回调用环境
3.除了Function之外,还有各种函数型的函数式接口,还有BIFunction、ToIntFunction等。
Predicate
断言型的函数式接口,泛型T是参数、返回结果类型为布尔类型的函数接口。
基本使用
public class Main{ /** * Predicate断言型的函数式接口,泛型T是参数、返回结果类型为布尔类型的函数接口。 * boolean test(T t): 抽象方法实现,用于返回传入的参数逻辑运算后布尔类型结果 * Predicate */ public static void main(String[] args) { Predicateand: 默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现 类似于逻辑运算中的短路&操作。 * Predicate negate: 默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现,用于将当前判定结果取反后返回,类似于逻辑运算中的!操作 * Predicate or:默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现 类似于逻辑运算中的||操作。 * Predicate isEqual:静态方法,内部允许我们链式调用,在保证参数不是空的情况下它内部实现逻辑实际上调用的是Object的equals,具体equals看子类有没有重写 pre1 = new Predicate () { @Override public boolean test(String o) { boolean bool = o.matches("[0-9]{1,}"); System.out.println("通过匿名内部类的方式获取到的值:"+ bool); return bool; } }; // 执行该方法的时候,它会去执行我们上述实现的test方法。 pre1.test("10086"); Predicate pre2 = text->{ boolean bool = text.matches("[0-9]{1,}"); System.out.println("通过lambda表达式的方式获取到的值:"+ bool); return bool; }; pre2.test("10086a"); /** * 最终结果: * 通过匿名内部类的方式获取到的值:true * 通过lambda表达式的方式获取到的值:false * 使用lambda表达式,我们只需要记住参数列表和执行逻辑即可,其他的我们无需关注。 */ // 我们继续对Predicate的API做一些理解和补充,毕竟这玩意在工作中经常会用上 // and 实际上等价于逻辑运算符中的短路&操作 Predicate fun3 = x-> { System.out.println("先计算fun3"); return true; }; Predicate fun4 = x-> { System.out.println("先计算fun4"); return false; }; System.out.println("第一次and结果:"+fun3.and(fun4).test("test")); /** * 最终结果: * 先计算fun3 * 先计算fun4 * 本次结果:false */ // 那么为什么我们知道它是短路&的操作 而不是&的操作呢?,我们只需要将第一个函数式接口返回false,看看它还会不会执行第二个函数式接口即可 Predicate fun5 = x-> { System.out.println("先计算fun5"); return false; }; Predicate fun6 = x-> { System.out.println("先计算fun6"); return true; }; System.out.println("第二次and结果:"+fun5.and(fun6).test("test")); /** * 最终结果: * 先计算fun5 * 第二次and结果:false。 */ // 从结果我们其实可以推断出,在第一个结果为true的情况下第二个fun6压根没进,所以是短路& // 并且起始在and方法源码中给我们也可以看到 return (t) -> test(t) && other.test(t); 是短路& // negate 实际上等价于逻辑运算符中的!操作 // 我们直接取上面的值做例子,本来结果应该为false,取反后应该为true System.out.println("negate结果:"+fun5.and(fun6).negate().test("test")); /** * 最终结果: * negate结果:true */ // or 等价于逻辑运算符中的||操作 // 我们直接取上面的做例子,第一个为false,第二个为true、||的最终结果应该为true System.out.println("or结果:"+fun5.or(fun6).test("test")); /** * 最终结果: * or结果:true */ // isEqual 内部调用的是Object的equals方法,如果子类重写了equals则调起子类的equals方法 // 如我们常用的String就重写了Object的equals方法,我们以它做例子 Predicate fun7 = Predicate.isEqual("Hello"); System.out.println("isEquals第一次结果:"+ fun7.test("Hello")); /** * 最终结果: * isEquals第一次结果:true */ Predicate fun8 = Predicate.isEqual("World"); System.out.println("isEquals第二次结果:"+ fun8.test("Hello")); // 自定义的对象类型也是可以比较的,但需要重写equals和hashCode,这里就不写示例了,可以自己玩玩 // 以上就是Predicate的相关API的介绍 } }
学习案例
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 | public class Main{ /** * Predicate * boolean test(T t): 抽象方法实现,用于返回传入的参数逻辑运算后布尔类型结果 * Predicate * Predicate * Predicate * Predicate */ public static void main(String[] args) { // 判断给定字符串是否纯数字并且小于10 可以使用and进行链式调用 Predicate "[0-9]{1,}" )).and(x->Integer.valueOf(x) < 10 ); System.out.println( "使用and方式进行链式调用:" +pre1.test( "9" )); // 需要注意的是:要使用这种方式,第一个Predicate要进行链式调用必须要强行再指定为(Predicate)类型,后续的接口才能够调用方法 // 实际上这种方式用的比较少,因为比较麻烦,所以一般都会直接使用&&进行判定 Predicate "[0-9]{1,}" ) && Integer.valueOf(s) < 10 ; System.out.println( "使用&&方式调用:" +pre2.test( "10" )); /** * 最终结果: * 使用and方式进行链式调用:true * 使用&&方式调用:false */ } } |
以上就是Jdk8提供的基础的四大函数(除了这四个之外,还有许多函数式接口,当然我们也可以自定义函数式接口)的基本使用方式和一些简单案例,具体该怎么做怎么写则需要根据项目实际需求进行,通常函数式接口都会搭配Stream成套使用,目前也有很多框架支持函数式接口的方式、如MyBatis-plus等社区活跃度较高的框架。