探索Java8:(二)Function接口的使用
Java8 添加了一个新的特性Function,顾名思义这一定是一个函数式的操作。我们知道Java8的最大特性就是函数式接口。所有标注了@FunctionalInterface注解的接口都是函数式接口,具体来说,所有标注了该注解的接口都将能用在lambda表达式上。
标注了@FunctionalInterface的接口有很多,但此篇我们主要讲Function,了解了Function其他的操作也就很容易理解了。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
/**
* @return a composed function that first applies the {@code before}
* function and then applies this function
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* @return a composed function that first applies this function and then
* applies the {@code after} function
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
}
为了方便地阅读源码,我们需要了解一些泛型的知识,如果你对泛型已经很熟悉了,那你可以跳过这段 。
泛型是JDK1.5引入的特性,通过泛型编程可以使编写的代码被很多不同的类型所共享,这可以很好的提高代码的重用性。因为本篇重点不是介绍泛型,所以我们只关注上述Function源码需要用到的泛型含义。
1. 泛型类
泛型类使用<T>来表示该类为泛型类,其内部成员变量和函数的返回值都可以为泛型<T> ,Function源码的标识为<T,R>,也就是两个泛型参数,此处不再赘述,具体泛型类可以看网上的文章。
2. 泛型方法和通配符
在方法修饰符的后面加一个<T>表明该方法为泛型方法,如Function 的源码里的compose方法的<V>。通配符也很好理解,还是compose的例子,我们可以看到compose的参数为一个Function类型,其中Functin的参数指定了其第一个参数必须是V的父类,第二个参数必须继承T,也就是T的子类。
源码解析
1.apply
讲完了上面这些就可以开始研究源码了。
首先我们已经知道了Function是一个泛型类,其中定义了两个泛型参数T和R,在Function中,T代表输入参数,R代表返回的结果。也许你很好奇,为什么跟别的java源码不一样,Function 的源码中并没有具体的逻辑呢?
其实这很容易理解,Function 就是一个函数,其作用类似于数学中函数的定义 ,(x,y)跟<T,R>的作用几乎一致。
\]
所以Function中没有具体的操作,具体的操作需要我们去为它指定,因此apply具体返回的结果取决于传入的lambda表达式。
R apply(T t);
举个例子:
public void test(){
Function<Integer,Integer> test=i->i+1;
test.apply(5);
}
/** print:6*/
我们用lambda表达式定义了一个行为使得i自增1,我们使用参数5执行apply,最后返回6。这跟我们以前看待Java的眼光已经不同了,在函数式编程之前我们定义一组操作首先想到的是定义一个方法,然后指定传入参数,返回我们需要的结果。函数式编程的思想是先不去考虑具体的行为,而是先去考虑参数,具体的方法我们可以后续再设置。
再举个例子:
public void test(){
Function<Integer,Integer> test1=i->i+1;
Function<Integer,Integer> test2=i->i*i;
System.out.println(calculate(test1,5));
System.out.println(calculate(test2,5));
}
public static Integer calculate(Function<Integer,Integer> test,Integer number){
return test.apply(number);
}
/** print:6*/
/** print:25*/
我们通过传入不同的Function,实现了在同一个方法中实现不同的操作。在实际开发中这样可以大大减少很多重复的代码,比如我在实际项目中有个新增用户的功能,但是用户分为VIP和普通用户,且有两种不同的新增逻辑。那么此时我们就可以先写两种不同的逻辑。除此之外,这样还让逻辑与数据分离开来,我们可以实现逻辑的复用。
当然实际开发中的逻辑可能很复杂,比如两个方法F1,F2都需要两个个逻辑AB,但是F1需要A->B,F2方法需要B->A。这样的我们用刚才的方法也可以实现,源码如下:
public void test(){
Function<Integer,Integer> A=i->i+1;
Function<Integer,Integer> B=i->i*i;
System.out.println("F1:"+B.apply(A.apply(5)));
System.out.println("F2:"+A.apply(B.apply(5)));
}
/** F1:36 */
/** F2:26 */
也很简单呢,但是这还不够复杂,假如我们F1,F2需要四个逻辑ABCD,那我们还这样写就会变得很麻烦了。
2.compose和andThen
compose和andThen可以解决我们的问题。先看compose的源码
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后使用当前Function的apply。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
andThen跟compose正相反,先执行当前的逻辑,再执行传入的逻辑。
这样说可能不够直观,我可以换个说法给你看看
compose等价于B.apply(A.apply(5)),而andThen等价于A.apply(B.apply(5))。
public void test(){
Function<Integer,Integer> A=i->i+1;
Function<Integer,Integer> B=i->i*i;
System.out.println("F1:"+B.apply(A.apply(5)));
System.out.println("F1:"+B.compose(A).apply(5));
System.out.println("F2:"+A.apply(B.apply(5)));
System.out.println("F2:"+B.andThen(A).apply(5));
}
/** F1:36 */
/** F1:36 */
/** F2:26 */
/** F2:26 */
我们可以看到上述两个方法的返回值都是一个Function,这样我们就可以使用建造者模式的操作来使用。
B.compose(A).compose(A).andThen(A).apply(5);
这个操作很简单,你可以自己试试。
探索Java8:(二)Function接口的使用的更多相关文章
- Function接口 – Java8中java.util.function包下的函数式接口
Introduction to Functional Interfaces – A concept recreated in Java 8 Any java developer around the ...
- java8学习之Lambda表达式继续探讨&Function接口详解
对于上次[http://www.cnblogs.com/webor2006/p/8186039.html]已经初步引入的Java8中Stream流的概念,其中使用了map的操作,它需要接受一个Func ...
- java 1.8新特性(二) 关于 function 接口的使用
需求1:从user集合中 找出age=15的用户 传统方法 就不说了 举例明一下 java1.8 使用Predicate接口解决该需求: @FunctionalInterface public in ...
- 探索Java8:(三)Predicate接口的使用
上一篇学习了下Function接口的使用,本篇我们学习下另一个实用的函数式接口Predicate. Predicate的源码跟Function的很像,我们可以对比这两个来分析下.直接上Predicat ...
- Java8新特性:Function接口和Lambda表达式参考
Lambda基本:https://blog.csdn.net/wargon/article/details/80656575 https://www.cnblogs.com/hyyq/p/742566 ...
- JAVA8之函数式接口
由于JDK8已经发布一段时间了,也开始逐渐稳定,未来使用JAVA语言开发的系统会逐渐升级到JDK8,因为为了以后工作需要,我们有必要了解JAVA8的一些新的特性.JAVA8相对JAVA7最重要的一个突 ...
- 乐字节-Java8新特性-接口默认方法
总概 JAVA8 已经发布很久,而且毫无疑问,java8是自java5(2004年发布)之后的最重要的版本.其中包括语言.编译器.库.工具和JVM等诸多方面的新特性. Java8 新特性列表如下: 接 ...
- java8新特性——接口中的静态方法与默认方法
以前我们知道,接口中的方法必须时抽象方法,而从 java8 开始接口中也可以有方法的实现了,叫做默认方法. 一 .默认方法(default修饰) 在 java8 中,因为存在函数式接口,一个接口中只能 ...
- 关于 Unity WebGL 的探索(二)
关于 Unity WebGL 的探索(二) 上一篇博客记录了关于 WebGL 移植的第一步:部分 C/C++ 插件的编译,目前项目中的部分插件使用该方法通过,接下来比较大的一部分工作量是网络模块 We ...
随机推荐
- SQLSERVER 函数大全
转自:http://blog.csdn.net/travylee/article/details/7216481 SQL2008 表达式:是常量.变量.列或函数等与运算符的任意组合. 1. 字符串函数 ...
- Spring Data JPA(官方文档翻译)
关于本书 介绍 关于这本指南 第一章 前言 第二章 新增及注意点 第三章 项目依赖 第四章 使用Spring Data Repositories 4.1 核心概念 4.2 查询方法 4.3 定义rep ...
- GA安装
- 2018/03/26 每日一个Linux命令 之 du
昨天使用 df 查看了磁盘空间中所有的容量. 今天学习 du 查看目录或者文件的大小. du会显示指定的目录或文件所占用的磁盘空间. -- du命令用来查看目录或文件所占用磁盘空间的大小.常用选项组合 ...
- es match、match_phrase、query_string和term的区别
(一)text字段和keyword字段的区别 以下给出一个例子: 首先建立一个索引和类型,引入一个keywork的字段: PUT my_index { "mappings": { ...
- 牛客练习赛16D K进制 数论(待理解QAQ)
正解:数论 解题报告: 行吧那就让我一点点推出来趴QAQ
- json数据处理:读取文件中的json字符串,转为python字典
方法1: 读取文件中的json字符串, 再用json.loads转为python字典 import json str_file = './960x540/config.json' with open( ...
- SqlAlchemy个人学习笔记完整汇总-转载
使用 sqlalchemy 有3种方式: 方式1, 使用raw sql; 方式2, 使用SqlAlchemy的sql expression; 方式3, 使用ORM. 前两种方式可以统称为 core ...
- 机器学习理论基础学习18---高斯过程回归(GPR)
一.高斯(分布)过程(随机过程)是什么? 一维高斯分布 多维高斯分布 无限维高斯分布 高斯网络 高斯过程 简单的说,就是一系列关于连续域(时间或空间)的随机变量的联合,而且针对每一个时间或是空间点 ...
- OO第四次阶段性总结
测试与正确性论证的效果差异及优缺点 测试实际上就是对程序进行的一种黑箱测试,利用各种各样的测试样例去检验程序是否能够给出正确的结果.其中的单元测试则是将整体的测试拆分成单元来进行,但其仍然躲不开黑箱测 ...