1.函数式编程思想
1.1概念
面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就是类似于我们数学中的函数,它主要关注的是对数据进行了什么操作。
1.2函数式编程的思想
- 代码简洁,提高开发效率
- 接近自然语法,易于理解
- 易于“并发编程”
2.Lambda表达式
2.1概述
Lambda表达式是JDK8中一个语法糖,它可以对某些匿名内部类的写法进行简化。==它是函数式编程思想的一个重要体现,让我们不用关注是什么对象,而是关注我们对数据进行了什么操作。==
2.2核心原则
==可推导可省略==
2.3基本格式
(参数列表) -> {方法体}
2.4Lambda表达式练习
例一:
我们在创建线程并启动时可以使用匿名内部类的写法
==Lambda表达式只关注有什么参数和方法体==
例二:
==Lambda表达式优化==
例三:
==Lambda表达式优化==
例四:
==Lambda表达式优化==
总结:==使用Lambda表达式替换匿名内部类,只需要保留匿名内部类的方法参数和方法体,然后在中间加一个箭头即可。==
2.5省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时,小括号可以省略
- 以上规则都可以省略不记,使用idea的快捷键 alt+enter
对于例四的最简省略方式:
3.Stream流
3.1概述
Java8的Stream使用的是函数式编程模式,==它可以被用来对集合或数组进行链状流式的操作,可以更方便的让我们对集合或数组操作。==
3.2案例准备
Book类:
Author类:
StreamDemol类:
作者添加书籍
3.3Steam流操作案例
3.3.1需求
获取到作家的集合,打印出年龄小于18的作家的名字,并且去重
3.3.2实现
dug启动,查看stream的执行过程
- 将集合转换为Stream流
- 去重操作
- 过滤年龄小于18的对象
3.4Stream常用操作
3.4.1创建stream流方式
- 单列集合
集合对象.stream()
- 数组
Arrays.stream(数组)
Stream.of
双列集合
==将双列集合转换为单列集合==
Map<String, String> map = new HashMap<>();
Stream<Map.Entry<String, String>> stream = map.entrySet().stream();
3.4.2中间操作
3.4.2.1filter
==对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。==
3.4.2.2map
==对流中的元素进行计算或转换==
3.4.2.3distinct
==除去流中的重复元素==
distinct方法是依赖Object的equals方法来判断是否是相同对象。
3.4.2.4sorted
==对流中的元素进行排序==
可以看当方法参数有两种:
- 无参
- Comparator接口
一:空参的情况
==如果是空参的,对当前流中的元素进行排序,但是如果此时流中的对象不能排序将会报类型转换异常==
==对于以上解决方案,需要对应的实体类实现Comparable
接口,实现其中的方法,重写方法。==
实现类:
当当前对象减去传入的对象时为升序
**二:携带参数**,不需要实现接口,而是实现匿名内部类,再使用Lambda表达式优化
升序排列:
降序排列:
3.4.2.5limit
==设置流的最大长度,超出的部分将会被舍去==
如下设置流的最大长度为2
3.4.2.6skip
==跳过流中的前几个元素,返回剩下的元素==
如下跳过第一个元素
3.4.2.7flatMap
map:map方法返回的是一个object,map将流中的当前元素替换为此返回值;
flatMap:==flatMap方法返回的是一个stream,flatMap将流中的当前元素替换为此返回流拆解的流元素;==
案例一:打印所有书籍的名字,要求对重复的元素进行去重。
map方式:Author对象的books属性是集合类型,使用原来map转换对象,要使用嵌套循环进行打印。
flatMap方式:
案例二:打印所有书籍分类的名字,要求对重复的元素进行去重。
3.4.3终结操作
3.4.3.1forEach
==对流中的元素进行遍历操作==
3.4.3.2count
==可以获取当前流当中的元素个数==
案例:打印所有书籍的数目
3.4.3.3max&min
==获取到流中的最值==
案例:分别获取作家的所有书籍的最高分和最低分
3.4.3.4collect
==将当前流转换为一个集合==
例子:
获取一个存放所有作者名字的list集合
获取一个存放所有书名的set集合
获取一个Map集合,map的key为作者名,value为List
3.4.4终结操作—查找与匹配
3.4.4.1anyMatch
==用来判断是否有任意符合条件的元素,结果为boolean类型==
案例:判断存在年龄在29岁之上的作家
3.4.4.2allMatch
==用来判断是否所有元素都符合条件,结果为boolean类型==
案例:判断所有的作家都是成年人
3.4.4.3noneMatch
==判断流中的元素是否都不符合匹配条件,如果都不符合结果为true,否则结果为false==
案例:判断所有作家的年龄是否都超过20岁
没有年龄超过20岁的作家,所以结果为true
3.4.4.4findAny
==获取流中的任意一个元素==
3.4.4.5findFirst
==获取流中的第一个元素==
3.4.5终结操作—reduce归并
==对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)==
reduce的作用是把stream中的元素给组合起来。我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。
它内部的计算方式如下:
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
案例1:使用reduce求所有作者年龄的和
这里的0就是相当于identity,也就是result的初始化值
案例2:使用reduce求所有作者中年龄的最大值
reduce有个重载形式,不需要设置初始化值。内部代码如下:
其就是将第一个元素作为初始化值
实现方式:
3.5Stream流的注意事项
- 惰性求值:如果没有终结操作,中间操作是不会得到执行的。
- 流是一次性的:一旦一个流对象经过一个终结操作后,这个流就不能再被使用了,只能重新创建流对象再使用。
- 不会影响原数据:我们在流中可以对数据做很多处理,但正常情况下是不会影响原来集合中的元素的。
4.Optional
4.1Optional概述
我们在编写代码的时候出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空的判断。尤其是对象中的属性还是一个对象的情况下,这种判断会更多。
==过多的判断语句会让我们的代码显得臃肿,所以在JDK8中引入了Optional,养成使用Optional的习惯后,你可以写出更优雅的代码来避免空指针异常。==
4.2Optional的使用
4.2.1创建Optional对象(ofNullable)
==Optional就好像是包装类,可以把我们的具体数据封装到Optional对象内部,然后我们去使用Optional中封装好的方法 操作封装进去的数据,就可以非常优雅的避免空指针异常。==
我们一般使用Optional的静态方法ofNullable来把数据封装成一个Optional对象,无论传入的参数是否为null都不会出现问题。
你可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下getAuthor方法,让其的返回值就是封装好的Optional的话,我们在使用时就会方便很多。
==而且在实际开发中我们的数据很多是从数据库获取的。Mybatis从3.5版本可以也已经支持Optional了. 我们可以直接把dao方法的返回值类型定义成Optional类型, MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。==
如果你可以确定一个对象不为空,则可以使用Optional的静态方法of来把数据封装成Optional对象。
如果传入of()方法的对象为空,也会报空指针异常。
观察ofNullable()方法的源码也可以发现,它是调用了of()方法的,只是对对象做了非空的判断。
基于此,可以直接实现empty()方法和Opional.of()方法
4.2.2安全的消费值(ifPresent)
我们获取到一个Optional对象后,肯定需要对其中的数据进行使用,这时候我们可以使用其ifPresent方法来消费其中的值。
==这个方法会判断其内部封装的数据是否为空,不为空时才会执行具体的消费代码,这样使用起来就更加安全。==
例如:下面的方式就避免了空指针异常
4.2.3安全的获取值(orElseGet/orElseThrow)
如果我们期望安全获取值,不推荐使用get()方法获取,而是用如下方法:
- orElseGet
==获取数据并且设置数据为空时的默认值。如果数据不为空则获取到该数据,如果为空则返回传入的参数来创建对象。==
- orElseThrow
==获取数据,如果数据不为空则获取数据,如果为空则根据传入的参数来创建异常抛出。==
4.2.4过滤(filter)
==我们可以使用filter方法对数据进行过滤。如果原本是有数据的,但不符合判断条件,也会变成一个无数据的Optional对象。==
4.2.5判断(isPresent)
我们可以使用isPresent方法进行判断是否存在数据的判断。如果为空,返回值为false,如果不为空,返回值为true。但这种方式不能体现Optional的好处,更推荐使用ifPresent方法。
4.2.6数据转换(map)
==Optional还提供了map可以让我们的数据进行转换,并且转换得到的数据也还是Optional包装好的,保证了我们的使用安全。==
5.函数式接口
5.1概述
==只有一个抽象方法的接口我们称为函数接口。==
JDK的函数式接口都加上了@FunctionalInterface注解进行标识。但是无论是否加上该注解,只要接口中只有一个抽象方法,就是函数式接口。
5.2常见的函数式接口
- Consumer接口
==根据其中抽象方法的参数列表和返回值类型,可以在方法中对传入的参数进行消费。==
- Function接口
==根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回。==
- Predicate 判断接口
==根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果。==
- Supplier 生产型接口
==根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回。==
5.3常用的默认方法
这些方法主要是在自定义函数式编程使用。
- and
==我们在使用Predicate接口的时候可能需要进行判断条件的拼接,而and方法相当于是使用&&来拼接两个判断条件。==
例1:
- or
==我们在使用Predicate接口的时候可能需要进行判断条件的拼接。而or方法相当于是使用 || 来拼接两个判断条件。==
- negate
==Predicate接口中的方法。negate方法相当于是在判断添加前面加了个 !,表示取反==
下面就表示取出年龄大于等于18的
6.方法引用
6.1概述
==我们在使用lambda时,如果方法体中只有一个方法的调用的话,我们可以用方法引用进一步简化代码。==
6.2推荐用法
推荐用法:我们在使用lambda时不需要考虑什么时候用方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且在方法的调用时使用快捷键尝试是否能转换成方法引用即可。
6.3基本格式
类名/对象名::方法名
例子:
使用方法引用优化Lambda表达式如下:
6.4语法详解
6.4.1引用类的静态方法
格式:类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
优化
6.4.2引用对象的实例方法
格式:对象名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法。
优化
6.4.3引用类的实例方法
格式:类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入这个成员方法中,这个时候我们就可以引用类的实例方法。
优化
public class MethodDemo {
interface UserString {
String use(String str, int start, int length);
}
6.4.4构造器引用
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
格式:类名::new
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
优化
7.高级用法
7.1基本数据类型优化
我们之前用到的很多Stream的方法由于都使用了泛型,所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的数字类型,但实际使用的是它们的包装类。自动装箱和拆箱是需要消耗时间的,在数据量大的情况下,不断重复装箱拆箱的时候,就不能无视这个时间的损耗了。
Stream提供了很多专门针对基本数据类型的方法,对这部分的时间消耗进行优化。
例如: mapTolnt、mapToLong、mapToDouble、flatMapTolng、flatMapToDouble等。
优化前:匿名内部类中转换的是包装类型,也就意味着后面的操作都要进行装箱和拆箱。
优化后返回的就是Int类型,避免了自动装箱和拆箱:
7.2并行流
当流中有大量元素时,我们可以使用并行流去提高操作的效率。
==并行流就是把任务分配给多个线程去执行。==如果我们自己使用代码去实现会很复杂,并且要求你对并发编程有足够的理解和认识。
如果使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流帮我们实现,从而提高效率。
parallel方法可以把串行流转换成并行流。
当使用穿行流
当使用并行流:
也可以通过parallelStream
直接获取并行流对象。