Jdk8提供的函数式接口都在java.util.function包下,Jdk8的函数式类型的接口都有@FunctionInterface注解所标注,但实际上即使没有该注解标注的有且只有一个抽象方法的接口,都可以算是函数式接口。
在JDK8中内置的四大核心函数式接口如下:

函数式接口 接口类型 参数类型 返回类型 作用 Stream流中的应用场景
Consumer<T> 消费型接口 T void 对类型为T的对象进行操作,包含方法为accpet(T t) 如forEach、peek等方法的函数式接口都是Consumer类型
Supplier<T> 供给型接口 T 返回类型为T的对象,包含方法为T get() 如collect等方法的某些方法重载就是用的Supplier类型
Function<T,R> 函数型接口 T R 对类型为T的对象进行操作,返回结果为 R类型的对象,包含方法为R apply(T t) 如map,flatMap等方法的函数式接口都是Function类型
Predicate<T> 断言型接口 T boolean 确定类型为T的对象是否满足约束,并返回约束结果,包含方法为boolean test(T t) 如filter等方法的函数式接口都是Predicate类型

Consumer<T>

Consumer<T>消费型接口,顾名思义就是消费并处理参数,且不反馈调用环境

基本使用

public class Main {
/**
* Consumer<T>
* 消费型接口:顾名思义主要用于消费参数,不反馈调用环境(没有返回值)
* accept: 抽象方法实现,用于调用方法。
* andThen: 默认实现方法,内部允许我们链式调用
*/
public static void main(String[] args) {
// 给定字符串转为大写并输出到控制台,匿名内部类的方式实现
Consumer<String> con1 = new Consumer<String>() {
@Override
public void accept(String str) {
System.out.println("通过匿名内部类的方式:"+ str.toUpperCase());
}
};
// 执行该方法的时候,我们传入了给定参数字符串,它会去执行我们上述实现的accept方法并传入参数,最后执行我们给定的代码逻辑
con1.accept("abc");
// 给定字符串转为大写并输出到控制台,通过Lambda表达式实现
Consumer<String> con2 = (text)-> System.out.println("通过Lambda表达式的方式:"+ text.toUpperCase());
con2.accept("goods");
/**
* 最终结果:
* 通过匿名内部类的方式:ABC
* 通过Lambda表达式的方式:GOOD
* 使用lambda表达式,我们只需要记住参数列表和执行逻辑即可,其他的我们无需关注。
*/
}}

学习案例

public class Main {

    /**
* Consumer<T>
* 消费型接口:顾名思义主要用于消费参数,不反馈调用环境(没有返回值)
* accept: 抽象方法实现,用于调用方法。
* andThen: 默认实现方法,内部允许我们链式调用
*/
public static void main(String[] args) {
// 1.我们需要将集合进行排序后在输出到控制台
Consumer<List> con1 = list-> {
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<List> con2 = ((Consumer<List>) list -> {
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<Integer> list,Consumer<List> con1,Consumer<List> con2){
// 链式调用时会优先执行左边的接口实现,依次往右执行 我们的需求是先排序后输出,第一个Consumer是排序,第二个是输出。
con1.andThen(con2).accept(list);
}
}

总结

1.函数式接口的本质实际上就是将函数以参数的形式进行传递

2.Consumer是消费型的函数式接口,通常用于数据内部处理,没有返回值

3.除了Consumer之外,还有各种消费型的函数式接口,还有IntConsumer、LongConsumer等、如果需要传递两个参数则可以使用BIFunction、也可以根据自身需求进行自定义。

Supplier<T>

Consumer<T>供给型函数式接口,顾名思义就是供给数据给调用环境,不接收参数传递

基本使用

public class Main{
/**
* 供给型函数式接口顾名思义就是顾名思义就是供给数据给调用环境,不接收参数传递
* T get() : 返回泛型T类型的参数到调用环境
*/
public static void main(String[] args) {
// 返回一个0-100间的随机数
Supplier<Integer> sup1 = new Supplier<Integer>() {
@Override
public Integer get() {
int res = new Random().nextInt(100);
System.out.println("通过匿名内部类的方式获取到的随机数:"+ res);
return res;
}
};
// 执行该方法的时候,它会去执行我们上述实现的get方法。
sup1.get();
// 通过lambda表达式的方式进行实现
Supplier<Integer> 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 Map<String,String> redis = 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<String> supplier){
return supplier.get();
}
}

总结

1.函数式接口的本质实际上就是将函数以参数的形式进行传递

2.Supplier是供给型的函数式接口,通常用于构建某个对象处理后返回调用环境

3.除了Supplier之外,还有各种供给型的函数式接口,还有BooleanSupplier、IntSupplier等。

Function<T,R>

Function<T,R>函数型的函数式接口,泛型T是参数、泛型R则是返回值、主要应用场景做数据类型转换等。

基本使用

public class Main{

    /**
* Function<T,R>函数型的函数式接口,泛型T是参数、泛型R则是返回值、主要应用场景做数据类型转换等。
* R apply(T t): 抽象方法实现,用于调用方法并返回泛型R
* <V> Function<T, V> andThen: 默认实现方法,内部允许我们链式调用,与其他的andThen原理一致。
* <V> Function<V, R> compose: 默认实现方法,内部允许我们链式调用,调用方式与andThen一样,但执行顺序不一样,compose是先执行compose中的函数接口,再执行左边调用的函数接口,依次往左
* <T> Function<T, T> identity():返回当前执行的方法,从源码中我们也可以看到它返回的是当前的t
*/
public static void main(String[] args) { // 传入给定字符串,返回转换后的Integer类型
Function<String,Integer> fun1 = new Function<String, Integer>() {
@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<String,Integer> fun2 = s->{
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<String,String> fun3 = x-> {
System.out.println("我是fun3的方法");
return x;
};
Function<String,String> fun4 = y-> {
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<Object, Object> identity = Function.identity();
// Function.identity() 静态方法这里就不好演示了,这个通常在后面搭配Stream流转Map类型的时候用到,它返回本身
}
}

学习案例

public class Main{

    /**
* Function<T,R>函数型的函数式接口,泛型T是参数、泛型R则是返回值、主要应用场景做数据类型转换等。
* R apply(T t): 抽象方法实现,用于调用方法并返回泛型R
* <V> Function<T, V> andThen: 默认实现方法,内部允许我们链式调用,与其他的andThen原理一致。
* <V> Function<V, R> compose: 默认实现方法,内部允许我们链式调用,调用方式与andThen一样,但执行顺序不一样,compose是先执行compose中的函数接口,再执行左边调用的函数接口,依次往左
* <T> Function<T, T> identity():返回当前执行的方法,从源码中我们也可以看到它返回的是当前的t
*/
public static void main(String[] args) {
List<Person> persons = Arrays.asList(new Person(1,"张三"),new Person(2,"李四"));
// 给定一个person对象集、转换成姓名属性集合返回
Function<List<Person>,List<String>> fun1 = list-> {
List<String> arr = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
arr.add(list.get(i).getName());
}
return arr;
};
List<String> personNames = fun1.apply(persons);
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

Predicate<T> 断言型的函数式接口,泛型T是参数、返回结果类型为布尔类型的函数接口。

基本使用

public class Main{

    /**
* Predicate<T>断言型的函数式接口,泛型T是参数、返回结果类型为布尔类型的函数接口。
* boolean test(T t): 抽象方法实现,用于返回传入的参数逻辑运算后布尔类型结果
* Predicate<T> and: 默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现 类似于逻辑运算中的短路&操作。
* Predicate<T> negate: 默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现,用于将当前判定结果取反后返回,类似于逻辑运算中的!操作
* Predicate<T> or:默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现 类似于逻辑运算中的||操作。
* Predicate<T> isEqual:静态方法,内部允许我们链式调用,在保证参数不是空的情况下它内部实现逻辑实际上调用的是Object的equals,具体equals看子类有没有重写
*/
public static void main(String[] args) {
Predicate<String> pre1 = new Predicate<String>() {
@Override
public boolean test(String o) {
boolean bool = o.matches("[0-9]{1,}");
System.out.println("通过匿名内部类的方式获取到的值:"+ bool);
return bool;
}
};
// 执行该方法的时候,它会去执行我们上述实现的test方法。
pre1.test("10086");
Predicate<String> 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<String> fun3 = x->
{
System.out.println("先计算fun3");
return true;
};
Predicate<String> fun4 = x->
{
System.out.println("先计算fun4");
return false;
};
System.out.println("第一次and结果:"+fun3.and(fun4).test("test"));
/**
* 最终结果:
* 先计算fun3
* 先计算fun4
* 本次结果:false
*/
// 那么为什么我们知道它是短路&的操作 而不是&的操作呢?,我们只需要将第一个函数式接口返回false,看看它还会不会执行第二个函数式接口即可
Predicate<String> fun5 = x->
{
System.out.println("先计算fun5");
return false;
};
Predicate<String> 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<String> fun7 = Predicate.isEqual("Hello");
System.out.println("isEquals第一次结果:"+ fun7.test("Hello"));
/**
* 最终结果:
* isEquals第一次结果:true
*/
Predicate<String> fun8 = Predicate.isEqual("World");
System.out.println("isEquals第二次结果:"+ fun8.test("Hello"));
// 自定义的对象类型也是可以比较的,但需要重写equals和hashCode,这里就不写示例了,可以自己玩玩 // 以上就是Predicate的相关API的介绍
}
}

学习案例

public class Main{

    /**
* Predicate<T>断言型的函数式接口,泛型T是参数、返回结果类型为布尔类型的函数接口。
* boolean test(T t): 抽象方法实现,用于返回传入的参数逻辑运算后布尔类型结果
* Predicate<T> and: 默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现 类似于逻辑运算中的短路&操作。
* Predicate<T> negate: 默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现,用于将当前判定结果取反后返回,类似于逻辑运算中的!操作
* Predicate<T> or:默认实现方法,内部允许我们链式调用,用于同时判定多个Predicate函数接口的实现 类似于逻辑运算中的||操作。
* Predicate<T> isEqual:静态方法,内部允许我们链式调用,在保证参数不是空的情况下它内部实现逻辑实际上调用的是Object的equals,具体equals看子类有没有重写
*/
public static void main(String[] args) {
// 判断给定字符串是否纯数字并且小于10 可以使用and进行链式调用
Predicate<String> pre1 = ((Predicate<String>) s -> s.matches("[0-9]{1,}")).and(x->Integer.valueOf(x) <10);
System.out.println("使用and方式进行链式调用:"+pre1.test("9"));
// 需要注意的是:要使用这种方式,第一个Predicate要进行链式调用必须要强行再指定为(Predicate)类型,后续的接口才能够调用方法
// 实际上这种方式用的比较少,因为比较麻烦,所以一般都会直接使用&&进行判定
Predicate<String> pre2 = s-> s.matches("[0-9]{1,}") && Integer.valueOf(s) <10;
System.out.println("使用&&方式调用:"+pre2.test("10"));
/**
* 最终结果:
* 使用and方式进行链式调用:true
* 使用&&方式调用:false
*/
}
}

以上就是Jdk8提供的基础的四大函数(除了这四个大的分类还有许多函数式接口,也可以自定义函数式接口实现我们的需求)的基本使用方式和一些简单案例,具体该怎么做怎么写则需要根据项目实际需求进行,通常函数式接口都会搭配Stream成套使用,目前也有很多框架支持函数式接口的方式、如MyBatis-plus等社区活跃度较高的框架。

【java8新特性】02:常见的函数式接口的更多相关文章

  1. Java8新特性—四大内置函数式接口

    Java8新特性--四大内置函数式接口 预备知识 背景 Lambda 的设计者们为了让现有的功能与 Lambda 表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念. 什么是函数式接口? 函数 ...

  2. Java8新特性_lambda表达式和函数式接口最详细的介绍

    Lambda表达式 在说Lambda表达式之前我们了解一下函数式编程思想,在数学中,函数就是有输入量.输出量的一套计算方案,也就是“拿什么东西做什么事情”. 相对而言,面向对象过分强调“必须通过对象的 ...

  3. java8新特性—四大内置核心接口

    java8新特性-四大内置核心接口 四大内置核心接口 //消费型接口 Consumer<T>:: vode accept(T t); //供给型接口 Supplier<T>:: ...

  4. java8新增特性(二)----函数式接口(Functional)

    上一篇博客介绍了java8新增的Lambda表达式,这一节介绍一下java8的函数式编程,两者之间有什么联系呢?请往下看~~~ Lambda表达式怎样在java类型中表示的呢? 语言设计者投入了大量的 ...

  5. Java8新特性一点通 | 回顾功能接口Functional Interface

    Functional Interface Functional Interface是什么? 功能接口是java 8中的新增功能,它们只允许一个抽象方法.这些接口也称为单抽象方法接口(SAM接口).这些 ...

  6. 图说jdk1.8新特性(1)--- 函数式接口

    函数式接口 总结起来就以下几点: 如果一个接口要想成为函数接口(函数接口可以直接用lambda方式简化),则必须有且仅有一个抽象的方法(非default和static) 可以通过注解@Function ...

  7. 【java8新特性】01:函数式编程及Lambda入门

    我们首先需要先了解什么是函数式编程.函数式编程是一种结构化编程范式.类似于数学函数.它关注的重点在于数据操作.或者说它所提倡的思想是做什么,而不是如何去做. 自Jdk8中开始.它也支持函数式编程.函数 ...

  8. java8新特性--Stream的基本介绍和使用

    什么是Stream? Stream是一个来自数据源的元素队列并可以进行聚合操作. 数据源:流的来源. 可以是集合,数组,I/O channel, 产生器generator 等 聚合操作:类似SQL语句 ...

  9. Java8新特性(一)概览

    最近看了好几段Java代码和以往的风格很不一样,都有点不太适应了,后来一查原来是Java8的新特性. 为了保持对技术的敏感性(面试...),这里我们一起来学习下Java8的新特性. 如果从技术角度来看 ...

  10. 乐字节-Java8新特性之函数式接口

    上一篇小乐带大家学过 Java8新特性-Lambda表达式,那什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的.从Java8开始引入了函数式接口,其说明比较简单:函数式接口 ...

随机推荐

  1. 关于vm虚拟机的问题

    这几天搞虚拟机搞的头疼,真是一步一个坑,总结以下几个问题: 安装不了或用户不接受协议:原因应该是你之前装过vm,没有彻底清理,和本次安装形成了对抗,所以我们需要安装WindowsInstallerCl ...

  2. Tapdata 在线研讨会:如何快速上手 Tapdata Cloud?

    偶然接触到 Tapdata Cloud,据说不仅可以实现异构数据实时同步,还永久 100% 免费,但就是不知道怎么获取.怎么用? 打开相关文档逐渐陷入迷茫,术语."黑话"随处可见, ...

  3. Random生成指定范围的随机数和对象数组

    查看类 ~java.util.Random :该类需要 import导入使后使用. 查看构造方法 ~public Random() :创建一个新的随机数生成器. 查看成员方法 ~public int ...

  4. Assembly.GetManifestResourceStream为null

    想把某个项目的某个文件夹里面的ini文件生成的时候顺便生成为网站和服务文件夹项目 string _path = Path.Combine(AppDomain.CurrentDomain.BaseDir ...

  5. Java学习_常见异常

    JAVA常见异常 Java.io.NullPointerException null 空的,不存在的 NullPointer 空指针 空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象 ...

  6. 【Codeforces1706A】 Another String Minimization Problem

    官方标签 贪心.字符串 题目描述 输入 输出 样例输入 6 4 5 1 1 3 1 1 5 2 4 1 1 1 1 1 2 4 1 3 2 7 7 5 4 5 5 5 3 5 样例输出 ABABA B ...

  7. Java基础语法02

    回顾前面的章节,我们学习了(1.注释,2.标识符和关键字,3.数据类型)今天让我们继续加油. 四.变量,常量,作用域1.变量是什么:存数的(可以变化的量) Java是一种强类型语言,每个变量都必须声明 ...

  8. 第七天python3 函数、参数及参数解构(二)

    函数参数 参数规则: 参数列表参数一般顺序是:普通参数<--缺省参数<--可变位置参数<--keyword-only参数(可带缺省值)<--可变关键字参数 def fn(x,y ...

  9. Auto.js 特殊定位控件方法 不能在ui线程执行阻塞操作,请使用setTimeout代替

    本文所有教程及源码.软件仅为技术研究.不涉及计算机信息系统功能的删除.修改.增加.干扰,更不会影响计算机信息系统的正常运行.不得将代码用于非法用途,如侵立删! Auto.js 特殊定位控件方法 操作环 ...

  10. PHP 获取数组长度

    count()函数,默认是获取一维数组,参数为:COUNT_NORMAL,添加第二个参数:COUNT_RECURSIVE,则可以获取多维关联数组的长度(意思为递归获取),例如:count($arr, ...