Java8新特性—四大内置函数式接口
Java8新特性——四大内置函数式接口
预备知识
背景
Lambda 的设计者们为了让现有的功能与 Lambda 表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。
什么是函数式接口?
函数式接口
指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式。
但是在实践中,函数式接口非常脆弱,只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface
,举个简单的函数式接口的定义:
@FunctionalInterface
public interface GreetingService {
void sayMessage(String message);
}
Java7 只能通过匿名内部类进行编程,例如:
GreetingService greetService = new GreetingService() {
@Override
public void sayMessage(String message) {
System.out.println("Hello " + message);
}
};
greetService.sayMessage("world");
Java8 可以采用 Lambda 表达方进行编程,例如:
GreetingService greetService = message -> System.out.println("Hello " + message);
greetService.sayMessage("world");
目前 Java 库中的所有相关接口都已经带有这个注解了,实践上java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子!
@FunctionalInterface注解
Java 8为函数式接口引入了一个新注解@FunctionalInterface
,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。
正确例子,没有报错:
/**
* @Description FunctionalInterface
* @Author vchicken
* @Date 2022/9/24 14:46
*/
@FunctionalInterface
public interface TestFunctionalInterface {
void sayMessage(String message);
}
错误例子,接口中包含了两个抽象方法,违反了函数式接口的定义,Eclipse报错提示其不是函数式接口。
提醒:加不加@FunctionalInterface对于接口是不是函数式接口没有影响,该注解知识提醒编译器去检查该接口是否仅包含一个抽象方法。
四大函数式接口
1.Function接口
什么是Function接口?
从Function接口的源代码,我们可以看出,JDK1.8之后才加入这个接口。Functional接口类中只有一个抽象方法待实现,符合函数式接口(指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口),因此Function接口可以用Lambda表达式——这个方法就是apply。
从源码可以看出,入参和出参类型,用泛型动态指定。apply的具体逻辑就相当于是入参转化为出参的具体逻辑。也就相当于是y = f(x)这个里面的,映射法则f。具体逻辑需要我们用匿名内部类或者Lambda,写方法体来实现。因此这个接口又叫函数型接口
。
下面我们来用代码举栗如何使用Function接口:
public class FunctionTest {
public static void main(String[] args) {
// 如果入参为null,则回参为0,否则返回入参的值作为出参
Function<Integer, Integer> function1 = s -> s == null ? 0 : s;
// 将入参的值+1后作为回参返回
Function<Integer, Integer> function2 = s -> s + 1;
// 如果入参为null,则回参为"",否则返回入参的值作为出参
Function<String, String> function3 = s -> s == null ? "空的" : s;
System.out.println(function1.apply(null));
System.out.println(function1.apply(100));
System.out.println(function2.apply(10));
System.out.println(function3.apply(null));
System.out.println(function3.apply("hello world!"));
// andThen是先执行前面的操作,然后执行andThen之后的操作
Function<Integer, Integer> first = x -> x * x;
Function<Integer, Integer> after = y -> y * 2;
System.out.println(first.apply(3));
System.out.println(after.apply(3));
int res = first.andThen(after).apply(4);
System.out.println(res);
}
}
执行结果
0
100
11
空的
hello world!
9
6
32
从上面的栗子我们可以看出:
Java把这些映射规则,也就是y = f(x)中的【f】抽象成了这个Function接口的apply逻辑。然后x和y,自变量和因变量,也就是入参出参,Java使用了扩展性更强的泛型参数类型,而不是固定Object入参出参。因为固定Object的话还要涉及到类型转换,还有可能报ClassCast异常,很麻烦
再看一个栗子:
/**
* @author vchicken
* @version 1.0
* @description FunctionTest
* @date 2022/11/10 12:10:06
*/
public class FunctionTest<T,R> {
public static void main(String[] args) {
FunctionTest<Integer, Integer> functionTest1 = new FunctionTest<>();
System.out.println(functionTest1.functionTest(null, s -> s == null ? 0 : s));
System.out.println(functionTest1.functionTest(100, s -> s == null ? 0 : s));
System.out.println(functionTest1.functionTest(10, s -> s + 1));
FunctionTest<String, String> functionTest2 = new FunctionTest<>();
System.out.println(functionTest2.functionTest(null, s -> s == null ? "空的" : s));
System.out.println(functionTest2.functionTest("hello world!", s -> s == null ? "空的" : s));
}
public R functionTest(T in,Function<T,R> function){
return function.apply(in);
}
}
执行结果:
0
100
11
空的
hello world!
结合两个栗子我们可以看出来:
我们可以使用Function接口来将同一个方法的处理逻辑抽象出来,在调用方法的时候,将处理逻辑以Lambda表达式的形式传入,实现同一个方法可以处理不同的代码逻辑,而且使用泛型来表示方法的出入参,可以避免不必要的类型转换和异常发生。@vchicken
Function接口在JDK中的应用
在JDK1.8中的新属性Stream中,就使用到了Function接口,看下面的源码:
通过一个例子来看看怎样使用map这个接口
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(-1,0,1,2,3,4);
Stream<Integer> stream1 = stream.map(integer -> integer + 1);
stream1.forEach(System.out::println);
}
执行结果:
0
1
2
3
4
5
案例:将数组中的数,依次平方,等到一个一个新的数组
List<Integer> myList = new ArrayList<>();
myList.add(-2);
myList.add(0);
myList.add(1);
myList.add(3);
myList.add(5);
myList.add(7);
List<Integer> collect = myList.stream().map(integer -> integer*integer).collect(Collectors.toList());
collect.forEach(System.out::println);
执行结果:
4
0
1
9
25
49
2.Consumer接口
什么是Consumer接口?
从源码可以看出,Consumer与Function类似,只有一个accept抽象方法待实现,只不过唯一的区别是,Consumer的accept使用void修饰,没有返回值,而Function有返回值。
Consumer接口又称为消费型接口
,顾名思义,入参传入后,被accept方法消费掉了,什么都没得到。
举个栗子:
public static void main(String[] args) {
Consumer<Object> consumer1 = new Consumer<Object>() {
@Override
public void accept(Object o) {
System.out.println("这次消费了:" + o.toString());
}
};
consumer1.accept("100w元在双十一!这下穷死了!");
Consumer<String> consumer2 = s -> System.out.println("这次消费了:" + s);
consumer2.accept("120w元在双十二!又穷死了!");
}
执行结果:
这次消费了:100w元在双十一!这下穷死了!
这次消费了:120w元在双十二!又穷死了!
同样的,我们可以提取公共方法为:
/**
* @author vchicken
* @version 1.0
* @description ComsumerTest
* @date 2022/11/10 17:14:11
*/
public class ConsumerTest<T> {
public static void main(String[] args) {
ConsumerTest<String> consumerTest = new ConsumerTest<>();
consumerTest.accept("100w元在双十一!这下穷死了!", s -> System.out.println("这次消费了:" + s));
consumerTest.accept("120w元在双十二!又穷死了!", s -> System.out.println("这次消费了:" + s));
}
public void accept(T in, Consumer<? super T> consumer) {
consumer.accept(in);
}
}
执行结果:
这次消费了:100w元在双十一!这下穷死了!
这次消费了:120w元在双十二!又穷死了!
Consumer接口在JDK中的应用
同样的,我们以Stream中的forEach为例,看下面的例子:
Stream<Integer> stream = Stream.of(-1,0,1,2,3,4);
stream.forEach(s-> System.out.println(s));
执行结果:
-1
0
1
2
3
4
同样的我们还可以使用方法引用来打印,效果一致
stream.forEach(System.out::println);
对于方法引用不了解的亲,也可以阅读这篇文章Java8 新特性 - 方法引用 @vchicken
3.Suppiler接口
什么是Suppiler接口?
既然我们上面说到了Consumer为消费型接口
,按照惯例,那肯定有生产型接口
或者也可以成为供给型接口
——Supplier接口,看下图源码:
由源码我们可以看出,Supplier接口只有一个待实的get方法,属于无参有返回值的抽象方法。下面看一个例子:
public static void main(String[] args) {
// 生成一个字符串
Supplier<String> supplier1 = () -> "abcde";
// 生成一个随机数
Supplier<Integer> supplier2 = () -> new Random().nextInt(10);
// 产生一个运行时异常
Supplier<RuntimeException> supplier3 = () -> new RuntimeException();
System.out.println(supplier1.get());
System.out.println(supplier2.get().intValue());
System.out.println(supplier3.get());
}
执行结果:
abcde
2
java.lang.RuntimeException
Supplier接口在JDK中的应用
generate
方法返回一个无限连续的无序流,其中每个元素由提供的供应商(Supplier
)生成。generate
方法用于生成常量流和随机元素流。看下面例子:
public static void main(String[] args) {
// 生成随机数
Stream.generate(() -> new Random().nextInt(10));
stream.forEach(System.out::println);
// 生成随机布尔流
Stream.generate(() -> new Random().nextBoolean())
.forEach(System.out::println);
// 生成常量流
Stream.generate(() -> "Hello World!")
.forEach(System.out::println);
}
执行结果:
2
5
1
--- #略
true
false
true
--- #略
Hello World!
Hello World!
Hello World!
--- #略
由于generate
返回无限连续流,为了限制流中元素的数量,我们可以使用Stream.limit
方法
public static void main(String[] args) {
Stream.generate(() -> new Random().nextInt(10)).limit(3)
.forEach(e -> System.out.println(e));
Stream.generate(() -> new Random().nextBoolean()).limit(3)
.forEach(e -> System.out.println(e));
Stream.generate(() -> "Hello World!").limit(3)
.forEach(e -> System.out.println(e));
}
执行结果:
3
6
3
true
false
false
Hello World!
Hello World!
Hello World!
4.Predicate接口
什么是Predicate接口?
Predicate接口又称为断言型接口
,test()方法有参但是返回值类型是固定的boolean,看下面例子:
public static void main(String[] args) {
Predicate<String> predicate = (s) -> s.length() > 0;
// 测试字符串的长度是否>0
System.out.println(predicate.test("hello"));
// 结果取反
System.out.println(predicate.negate().test("hello"));
System.out.println("=====or / and======");
System.out.println(predicate.test(""));
// 增加或判断,二者满足其一则为true
System.out.println(predicate.or(s -> s.equals("")).test(""));
// 增加与判断,二者都满足则为true
System.out.println(predicate.and(s -> s.equals("hello")).test(""));
System.out.println(predicate.and(s -> s.equals("hello")).test("hello"));
System.out.println("=====isEqual======");
// 判断是否相等
System.out.println(Predicate.isEqual("hello").test(""));
System.out.println(Predicate.isEqual("hello").test("hello"));
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
}
执行结果:
true
false
=====or / and======
false
true
false
true
=====isEqual======
false
true
Predicate接口在JDK中的应用
Stream中的filter方法,用来过滤不满足条件的元素,使用Predicate传入过滤条件。看下面例子:
Stream<Integer> stream = Stream.of(-1, 0, 1, 2, 3, 4);
stream.filter(s -> s > 0).forEach(System.out::println);
执行结果:
1
2
3
4
总结
关于JDK1.8为什么要新增这四大内置函数式接口,其实就是Java的开发者将常用于代码的一些普遍场景抽象出来成为接口,而我们可以根据实际业务需求,实现这些接口的具体逻辑。通过lambda表达式的方式,也可以使得代码更加简洁。JDK中的函数式接口还有很多,但基本都是在四大函数式接口的基础之上加以拓展,有兴趣的童鞋可以自行研究。
四大函数式接口的比较
函数式接口 | 对应程序逻辑的抽象 | 具体场景 |
---|---|---|
Function | 程序中映射逻辑的抽象 | 比如我们写得很多的函数:接收入参,返回出参,方法代码块就是一个映射的具体逻辑。 |
Predicate | 程序中判断逻辑的抽象 | 比如各种if判断,对于一个参数进行各种具体逻辑的判定,最后返回一个if else能使用的布尔值 |
Consumer | 程序中的消费型逻辑的抽象 | 就比如Collection体系的ForEach方法,将每一个元素取出,交给Consumer指定的消费逻辑进行消费 |
Suppiler | 程序中的生产逻辑的抽象 | 就比如最常用的,new对象,这就是一个很经典的生产者逻辑,至于new什么,怎么new,这就是Suppiler中具体逻辑的写法了 |
参考资料
Java8新特性—四大内置函数式接口的更多相关文章
- java8新特性—四大内置核心接口
java8新特性-四大内置核心接口 四大内置核心接口 //消费型接口 Consumer<T>:: vode accept(T t); //供给型接口 Supplier<T>:: ...
- java8新特性——四大内置核心函数式接口
在前面几篇简单介绍了一些Lambda表达式得好处与语法,我们知道使用Lambda表达式是需要使用函数式接口得,那么,岂不是在我们开发过程中需要定义许多函数式接口,其实不然,java8其实已经为我们定义 ...
- Java8新特性 -- 四大内置的核心函数式接口
可以把这些函数式接口作为方法的参数. 1.0 核心内置函数式接口一: 消费型接口@FunctionalInterfacepublic interface Consumer<T> { voi ...
- Java8新特性_lambda表达式和函数式接口最详细的介绍
Lambda表达式 在说Lambda表达式之前我们了解一下函数式编程思想,在数学中,函数就是有输入量.输出量的一套计算方案,也就是“拿什么东西做什么事情”. 相对而言,面向对象过分强调“必须通过对象的 ...
- Java8新特性 - Java内置的四大核心函数式接口
Java内置的四大核心函数式接口 Consumer:消费型接口 对类型为T的对象应用操作,包含方法:void accept(T t) public class TestLambda02 { publi ...
- java8新增特性(二)----函数式接口(Functional)
上一篇博客介绍了java8新增的Lambda表达式,这一节介绍一下java8的函数式编程,两者之间有什么联系呢?请往下看~~~ Lambda表达式怎样在java类型中表示的呢? 语言设计者投入了大量的 ...
- Java8新特性一点通 | 回顾功能接口Functional Interface
Functional Interface Functional Interface是什么? 功能接口是java 8中的新增功能,它们只允许一个抽象方法.这些接口也称为单抽象方法接口(SAM接口).这些 ...
- 图说jdk1.8新特性(1)--- 函数式接口
函数式接口 总结起来就以下几点: 如果一个接口要想成为函数接口(函数接口可以直接用lambda方式简化),则必须有且仅有一个抽象的方法(非default和static) 可以通过注解@Function ...
- 【java8新特性】01:函数式编程及Lambda入门
我们首先需要先了解什么是函数式编程.函数式编程是一种结构化编程范式.类似于数学函数.它关注的重点在于数据操作.或者说它所提倡的思想是做什么,而不是如何去做. 自Jdk8中开始.它也支持函数式编程.函数 ...
随机推荐
- 2-1 走进selenium新世界
走进Selenium新世界 浏览器 Firefox Setup 35.0.1 安装完成后设置菜单栏 关闭浏览器自动更新 插件配置(必备武器) FireBug Firebug是firefox下的一个扩展 ...
- dotnet 设计规范 · 抽象定义
严格来说,只有一个类被其他的类继承,那么这个类就是基类.在很多时候,基类的定义是提供足够的抽象和通用方法和属性.默认实现.在继承关系中,基类定义在上层抽象和底层自定义之间. 他们充当抽象实现的实现帮助 ...
- 项目实践2:(问卷)用html和css做一个网页
好家伙,又来写项目了 1.以下是考题,姑且把他理解为甲方吧. 2.以下是附带的题目素材 开干.
- haodoop概念总结
大数据部门组织结构 Hadoop的优势(4高) 高可靠性:Hadoop底层维护多个数据副本 高扩展性:在集群间分配任务数据,可方便的扩展 高效性:在MapReduce的思想下,Hadoop时并行工作的 ...
- Django ORM 实现数据的多表 增删改查
一.创建模型和表 假定下面这些概念.字段与关系: 作者模型:一个作者有姓名和年龄. 作者详细模型:把作者的详情放到详情表,手机号,家庭住址信息. 作者详情模型 和 作者模型之间是一对一的关系(one- ...
- 【项目实战】Kaggle电影评论情感分析
前言 这几天持续摆烂了几天,原因是我自己对于Kaggle电影评论情感分析的这个赛题敲出来的代码无论如何没办法运行,其中数据变换的维度我无法把握好,所以总是在函数中传错数据.今天痛定思痛,重新写了一遍代 ...
- Containerd教程
文档是从B站有关视频上对应找到的,具体视频地址是:https://www.bilibili.com/video/BV1XL4y1F7QB?p=21&spm_id_from=333.880.my ...
- Shell分析日志文件
文章转载自:https://mp.weixin.qq.com/s/o63aIM2p9rc2OjhxiC6wgA 1.查看有多少个IP访问: awk '{print $1}' log_file|sort ...
- ES重要配置解析
path.data和path.logs 如果您使用.zip或.tar.gz存档,则data和logs 目录是子文件夹$ES_HOME.如果这些重要文件夹保留在其默认位置,则在将Elasticsearc ...
- 解决nexus仓库只能拉取不能推送的问题
当时正在使用jenkins自动构造镜像推送到nexus上的docker镜像仓库,突然间就报错如下,没法推送,超过重试次数后也是没法推送: ERROR: Build step failed with e ...