1、函数式编程
- 大数据量下处理集合效率更高(并行处理)
- 代码可读性高(减少很多嵌套),接近自然语言,易于理解
- 对并发编程比较友好
- 主要关注对数据进行了什么操作
2、函数式接口
只有一个抽象方法的接口,可以用**@FunctionalInterface**注解标注,不标注也可以是一个函数式接口
1.8之后也可以有default修饰的方法,静态的方法,Default方法
JDK8中引入了函数式编程,为了兼容之前的版本,在接口中定义获取Stream流的方法,这些方法是default修饰的,确保新加的方法不会影响之前的代码,提供了默认的实现,
函数式接口中的默认方法,常用于自定义函数式接口
- Consumer 消费型
- Supplier 生产型
- Function 计算型
- Predicate 断言型
3、lambda表达式
简化匿名内部类的写法
lambda表达式更节省内存空间,lambda表达式编译后比匿名内部类节省空间JDK1.8
可省略
- 参数类型可以省略
也就是说如果函数式接口中定义了类型,那么方法中的类型是确定的,因此可以省略
-
只有一个参数时,小括号可以省略
-
方法体中只有一句代码时,return关键字、大括号、分号都可以省略
可推导:方法引用
如果函数式接口中定义的方法的参数类型和lambda方法体中调用的方法的参数类型相匹配,就可以使用简化的写法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8j3dPDqF-1692548575740)(…\Pictures\blog\lambda_1.png)]
4、Stream操作
注意
- (惰性求职)流必须有终结操作,如果没有,并不会执行
- 流是一次性的,如果流对象经过了终结操作后,这个流就不能再使用了
- 流不会影响原始数据(正常情况下)
对基本类型数据的优化
在Stream中所有方法都使用了泛型,因此基本数据类型在流的操作中会进行装箱和拆箱的操作,但是当流中的数据量很大的情况下,这种自动拆装箱的操作会消耗一定时间,因此Stream中提供了很对针对基本数据类型的方法,比如mapToInt mapToLong mapToDouble flatMapToInt flatMapToDouble等,在IntStream中还提供了boxed、range、rangeClosed等
-
创建流
- 数组 Arrays.stream(arr)或者Stream.of()
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8}; int[] arr1 = new int[10]; int[] arr2 = new int[]{12,13,14,15,16,17,18}; IntStream stream = Arrays.stream(arr); IntStream stream1 = IntStream.of(arr);
- 单列集合 集合对象.stream()
List list = new ArrayList<Apple>(20); Stream<Apple> stream = list.stream();
- 双列集合 转换成单列集合后再创建流 map.entrySet().stream() map.keySet().stream()
HashMap<String,String> map = new HashMap<>(); map.put("hello","world"); Stream<Map.Entry<String,String>> stream1 = map.entrySet().stream(); Stream<String> stream2 = map.keySet().stream();
-
中间操作
- filter 按照某些条件对流中的元素进行过滤
- map 可以对流中的元素进行计算或者转换
- distinct 对流中的元素进行去重(根据对象的equals方法来判断是否相同)
- sorted 对流中的元素进行排序,可以传入一个比较器Comparator,空参的sorted方法,元素必须实现Comparable接口
- limit 设置流的最大长度,超出部分将被抛弃
- skip 跳过流中的前n个元素,返回剩下的元素(索引从1开始)
- flatMap 可以将一个对象转换为多个对象作为流中的元素(一个流变为多个流),而map方法只能将一个对象转换为另一个对象。比如可以将集合中对象的集合类型字段转换为流
-
终结操作
-
forEach 对流中的元素进行遍历操作,通过传入Consumer接口来指定具体的操作
-
count 获取当前流中元素的个数
-
min和max 求流中的最大值和最小值
-
collect 将流中的元素转换为一个集合 该方法的参数通常使用Collectors工具类中定义的方法比较方便
-
查找和匹配
- anyMatch 判断是否有任意符合匹配条件的元素
- allMatch 判断是否都符合匹配条件
- noneMatch 判断是否都不符合匹配条件
- findAny 获取流中的任意一个元素,但并不保证一定是第一个
- findFirst 获取流中的第一个元素
-
reduce 对流中的数据按照给定的计算方式计算出一个结果,通常会和map组合使用
-
Optional<T> reduce(BinaryOperator<T> accumulator); //相当于从流中找到第一个元素作为初始值 //底层执行逻辑 boolean foundAny = false; T result = null; for (T element : this stream) { if (!foundAny) { foundAny = true; result = element; }else result = accumulator.apply(result, element); } return foundAny ? Optional.of(result) : Optional.empty();
-
T reduce(T identity, BinaryOperator<T> accumulator); //identity相当于一个初始值 //底层执行逻辑 U result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result;
-
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); //用于并行计算
-
-
5、Optional
可以避免空指针异常,代码更优雅
本质上是将对象封装成了Optional中的属性,调用对象的方法时,通过Optional封装的方法可以做相关的一些检查
mybatis在3.5版本中已经支持Optional,这样就可以在DAO中将数据封装为Optional返回
-
创建Optional
- 在不知道对象是否为null的情况下,一般使用静态方法Optional.ofNullable(对象)方法将数据封装为Optional对象
- 在已经确定对象不为null的情况下,使用静态方法Optional.of(对象)方法将数据封装为Optional对象
- 如果对象为null,使用静态方法Optional.**empty()**将Null封装Optional对象
-
安全消费 optional.ifPresent(lambda)
-
获取值 get() 当元素存在时正常返回,当元素为空时抛出NoSuchElementExecption,因此不建议使用get方法获取值
-
安全获取值
-
orElseGet(lambda) 获取数据,如果数据为空,根据传入的参数创建对象作为默认值
Apple apple = appleOptional.orElseGet(()->new Apple())
-
orElseThrow(lambda) 获取数据,如果数据为空,根据传入的参数创建异常并抛出
try{ Apple apple = appleOptional.orElseThrow(()->new RuntimeExecption("apple is null")); }catch(Exception e){ e.printStackTrace(); }
-
-
过滤 filter(lambda)方法可以进行过滤,不符合判断时,将会返回一个无数据的新的Optional对象
-
判断 isPresent() 如果数据不为空返回true,如果为空返回false。更推荐使用ifPresent方法
-
数据转换 map(lambda),转换后的数据也是被Optional包装的对象
6、方法引用
方法引用的使用前提是在使用lambda时(而使用lambda的前提是函数式接口,也就是只有一个抽象方法的接口),如果lambda的方法体中只有一个方法的调用的话(包括构造方法),就可以使用方法引用进一步简化代码也就是看lambda表达式的参数和lambda表达式方法体中调用方法的参数的关系
格式:类名或对象名::方法名
-
引用类的静态方法 类名::静态方法名
lambda方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中(函数式接口中方法的形参按照顺序匹配上了静态方法的形参),就可以引用类的静态方法
-
引用对象的实例方法 对象名::普通方法名
lambda方法体只有一行、该行代码调用了某个对象的成员方法、lambda方法体中的方法的参数按照顺序传入了成员方法
-
引用类的实例方法 类名::普通方法名
lambda方法体中只有一行代码、该行代码调用了第一个参数的成员方法、lambda方法体中的方法中剩余的所有参数都按照顺序传入了这个成员方法中
改行代码的形式就像这样:参数1.成员方法(参数2,参数3…)
-
引用构造方法 类名::new
lambda方法体中只有一行代码、该行代码调用了类的构造方法、lambda方法体中的方法的参数按照顺序传入了构造方法中
lambda方法体中只有一行代码、该行代码调用了第一个参数的成员方法、lambda方法体中的方法中剩余的所有参数都按照顺序传入了这个成员方法中
改行代码的形式就像这样:参数1.成员方法(参数2,参数3…)
-
引用构造方法 类名::new
lambda方法体中只有一行代码、该行代码调用了类的构造方法、lambda方法体中的方法的参数按照顺序传入了构造方法中