3

【java8新特性】:常见的函数式接口

 1 year ago
source link: https://blog.51cto.com/u_15773567/5692610
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【java8新特性】:常见的函数式接口

精选 原创

小二上酒8 2022-09-20 15:49:51 ©著作权

文章标签 函数式接口 lambda表达式 泛型 文章分类 Java 编程语言 阅读数157

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

在JDK8中内置的四大核心函数式接口如下:

【java8新特性】:常见的函数式接口_泛型
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供给型函数式接口,顾名思义就是供给数据给调用环境,不接收参数传递

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等社区活跃度较高的框架。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK