- 函数式接口
- lambda
- 新增方法举类
- Stream
- 接口默认方法
- DATE API
优点:
1.代码简洁,开发快速: 函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快
2. 接近自然语言,易于理解
3. 更方便的代码管理: 函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同
4. 易于“并发编程”:因为它不修改变量,所以根本不存在"锁"线程的问题
Lambda 表达式
Lambda 表达式将函数当成参数传递给某个方法,或者把代码本身当作数据处理;
函数式接口
函数式接口在java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
如果自定义一个函数式接口尽量使用 @FunctionalInterface 函数式接口声明来说这就是一个函数式接口,别动了。
lambda语法格式
表达式的组成:
1.形参
2. 箭头符号 ->
3.方法体
自定义一个函数式接口,等下来用例子看一下它的四种语法
其中Interger::sum为方法引用,后面会讲。
注意事项
lambda 表达式只能引用标记了 final 的外层局部变量,不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。可以不使用final关键字,但是必须不可被后面的代码修改
编译器给的异常描述:
不允许声明一个与局部变量同名的参数或者局部变量
基本函数式接口
在实际使用中很少使用自定义函数式接口,java8中的util.function包中包含了大部分常用接口,
这些接口都是以下面四种基本函数式接口为基础的。
新方法使用
foreach
API
源码:
源码中十一用迭代器以及四大基本类型中Consumer类型
使用示例
removeIf
API
源码
源码中是使用迭代器,去匹配每个符合条件的地址,然后去除
例子
Map中新增方法示例
foreach
API
源码
源码中也是使用迭代器以及Biconsumer基本函数式接口来实现的
这个基本函数式接口的代码:
例子
有两个参数,第一个参数是map的key,第二个参数是map的value;
还有map中一些大家常用的方法也加入到了里面,
根据key在map中获取,获取到的值为空返回默认值;
如果map中存在该value就不覆盖
根据KEY和value来判断是否删除该值
如果该键值对存在,替换
根据KEY和value来判断是否替换
Stream
Stream类似一个高级的迭代器,单向,不可往复,数据只能遍历一遍,用户只要给出对其包涵的元素执行什么操作,Stream会隐式地在内部进行遍历做对应的操作。
特性
- 无存储。
stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
- 为函数式编程而生。
对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。
- 惰式执行。
stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
- 可消费性。
stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
中间操作(intermediate operations)和结束操作(terminal operations),
二者特点是:
中间操作总是会惰式执行
调用中间操作只会生成一个标记了该操作的新stream,仅此而已。
结束操作会触发实际计算
计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。
区分中间操作和结束操作最简单的方法,就是看方法的返回值,返回值为stream的大都是中间操作,否则是结束操作。
示例
list.stream.foreach等同于list.foreach
过滤和去重
收集器(Collector)
是为Stream.collect()方法量身打造的工具接口(类)。
考虑一下将一个Stream转换成一个容器(或者Map)需要做哪些工作?
1:目标容器是什么?是ArrayList还是HashSet,或者是个TreeMap。
2:新元素如何添加到容器中?是List.add()还是Map.put()。
3:如果并行的进行规约,还需要告诉collect() 多个部分结果如何合并成一个。
使用自带方法讲stream转为list,toList返回类型ArrayList,toSet返回类型为HashSet
映射
Stream背后依赖于某种数据源,数据源可以是数组、容器等,但不能是Map,
使用collect 生成Map方式
使用Collectors.toMap()生成的收集器,用户需要指定如何生成Map的key和value
使用Collectors.partitioningBy()生成的收集器,对元素进行二分区操作
使用Collectors.groupingBy()生成的收集器,对元素做group操作
Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如t -> t形式的Lambda表达式。使用partitioningBy()生成的收集器,这种情况适用于将Stream中的元素依据某个二值逻辑(满足条件,或不满足)
分成互补相交的两部分,比如男女性别、成绩及格与否等。下列代码展示将学生分成成绩及格或不及格的两部分。
map用法
flagMap示例
map与flagMap区别
在执行map操作以后,我们得到是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操作是基于在这些字符串数组之间的对比,所以达不到我们希望的目的,此时的输出为:
j, a, v, a, 8] [i, s] [e, a, s, y] [t, o] [u, s, e]
结果正确
flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam,从而能够达到我们的目的。
接口的默认方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:
Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。
Date API
Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样,下面的例子展示了这组新API里最重要的一些部分:
Clock 时钟
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
Timezones 时区
在新API中时区使用ZoneId来表示。时区可以很方便的使用静态方法of来获取到。 时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。
LocalTime 本地时间
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
LocalDate 本地日期
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
从字符串解析一个LocalDate类型和解析LocalTime一样简单:
LocalDateTime 本地日期时间
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。
只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。