Lambda表达式与Stream流的使用
来源:
https://www.yuque.com/pig4cloud/pig/yewg8z stream流
https://www.cnblogs.com/haixiang/p/11029639.html lambda表达式
目录
Lambda表达式
命令式和函数式
命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。
对于函数式编程的理解,不关注结果,将过程定义好,参数之间使用函数进行传递,忽略掉结果的细节处理 可以得到很多好处实现如下:
行为参数化:
把算法的策略(行为)作为一个参数传递给函数。
特点:
● 匿名:它不像普通的方法那样有一个明确的名称:写得少而想得多!
● 函数:Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
● 传递:Lambda表达式可以作为参数传递给方法或存储在变量中。
● 简洁:无需像匿名类那样写很多模板代码。
● 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
● 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
● 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
● 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
● 虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有``一个需要被实现的``方法,不是规定接口中只能有一个方法
语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。
简写规则:
1. 可以不写参数类型 但必须要么都不写,要么都写,不能出现一个写一个不写的情况
2. 当只有一条语句的时候可以省略掉大括号
3. 只有返回值return一条语句的时候,可以省略掉return和大括号 直接()-> a a是要return的结果
4. 当参数只有一个的时候,可以省略掉小括号 a -> {}
lambda表达式引用
语法形式为: 方法归属者::方法名
## 静态方法的归属为类 实例方法归属为对象
可以快速为其指向一个实现:
分为
1. lambda 表达式引用方法
2. lambda 构造方法的引用
3. lambda 表达式创建线程
测试代码:
public static void main(String[] args) {
/*List<SysDept> userList = new ArrayList<>();
for(int i = 0; i<100;i++){
SysDept sysDept = new SysDept();
sysDept.setSortOrder(i);
userList.add(sysDept);
}
long sum = userList.stream().mapToInt(SysDept::getSortOrder).count();
System.out.println(sum);
*/
/*List<SysDept> list2 = new ArrayList<>();
for(int i = 0; i<100;i++){
SysDept sysDept = new SysDept();
sysDept.setDelFlag(i%2==0?"aa":"bb");
list2.add(sysDept);
}
Map<String, List<SysDept>> collect = list2.stream().collect(Collectors.groupingBy(SysDept::getDelFlag));
System.out.println(collect);*/
NoReturnNoParam noReturnNoParam = () -> System.out.println("noReturnNoParam");;
noReturnNoParam.method();
NoReturnOneParam noReturnOneParam = a -> System.out.println(a+"noReturnOneParam");;
noReturnOneParam.method(1);
//多个参数无返回
NoReturnMultiParam noReturnMultiParam = (a,b) ->System.out.println("NoReturnMultiParam param:" + "{" + a +"," + + b +"}");
noReturnMultiParam.method(6, 8);
//无参有返回值
ReturnNoParam returnNoParam = () -> {
System.out.print("ReturnNoParam");
return 1;
};
int res = returnNoParam.method();
System.out.println("return:" + res);
//一个参数有返回值
ReturnOneParam returnOneParam = (int a) -> a;
int res2 = returnOneParam.method(6);
System.out.println("returnOne:" + res2);
//多个参数有返回值
ReturnMultiParam returnMultiParam = (int a, int b) -> {
System.out.println("ReturnMultiParam param:" + "{" + a + "," + b +"}");
return 1;
};
int res3 = returnMultiParam.method(6, 8);
System.out.println("return:" + res3);
// 方法引用
ReturnOneParam lambda1= SysUserServiceImpl::doubleNum;
System.out.println(lambda1.method(3));
}
/**
* 要求
* 1.参数数量和类型要与接口中定义的一致
* 2.返回值类型要与接口中定义的一致
*/
public static int doubleNum(int a) {
return a * 2;
}
public int addTwo(int a) {
return a + 2;
}
// 实现构造方法
interface ItemCreatorBlankConstruct {
Item getItem();
}
interface ItemCreatorParamContruct {
Item getItem(int id, String name, double price);
}
public class Exe2 {
public static void main(String[] args) {
ItemCreatorBlankConstruct creator = () -> new Item();
Item item = creator.getItem();
ItemCreatorBlankConstruct creator2 = Item::new;
Item item2 = creator2.getItem();
ItemCreatorParamContruct creator3 = Item::new;
Item item3 = creator3.getItem(112, "鼠标", 135.99);
}
}
/**
* 附加方法等等
*/
// 遍历集合
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//....
}
Copy
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
//lambda表达式 方法引用
list.forEach(System.out::println);
list.forEach(element -> {
if (element % 2 == 0) {
System.out.println(element);
}
});
// 排序
ArrayList<Item> list = new ArrayList<>();
list.add(new Item(13, "背心", 7.80));
list.add(new Item(11, "半袖", 37.80));
list.add(new Item(14, "风衣", 139.80));
list.add(new Item(12, "秋裤", 55.33));
/*
list.sort(new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2) {
return o1.getId() - o2.getId();
}
});
*/
list.sort((o1, o2) -> o1.getId() - o2.getId());
System.out.println(list);
Java 8中的常用函数式接口
函数式接口 | 函数描述符 | 原始类型特化 | |
---|---|---|---|
Predicate<T> | T->boolean | IntPredicate,LongPredicate, DoublePredicate | |
Consumer<T> | T->void | IntConsumer,LongConsumer, DoubleConsumer | |
Function<T,R> | T->R | IntFunction<R>, IntToDoubleFunction,``IntToLongFunction, LongFunction<R>,``LongToDoubleFunction, LongToIntFunction,``DoubleFunction<R>, ToIntFunction<T>,``ToDoubleFunction<T>, ToLongFunction<T> | |
Supplier<T> | ()->T | BooleanSupplier,IntSupplier, LongSupplier, DoubleSupplier | |
UnaryOperator<T> | T->T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator | |
BinaryOperator<T> | (T,T)->T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator | |
BiPredicate<L,R> | (L,R)->boolean | ||
BiConsumer<T,U> | (T,U)->void | ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T> | |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U> |
Stream流使用
概念
流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。
流就像是一条流水线,对流操作则相当于是对流水线进行加工,像流水线一样串行执行操作
特点
只能遍历一次,-> 流水线
由外部迭代输入结果 内部迭代进行操作处理
操作种类
流的操作分为两种,分别为中间操作和终端操作。
1. 中间操作
当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。
中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线。
2. 终端操作
当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终端操作。
终端操作将返回一个执行结果,这就是你想要的数据。
使用
在使用流之前,首先需要拥有一个数据源的概念
创建流
使用api方法
List<Person> list = new ArrayList<Person>();
Stream<Person> stream = list.stream();
使用Arrays.stream()获取数组
String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
使用值定义
Stream<String> stream = Stream.of("chaimm","peter","john");
迭代器
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
筛选filter
filter 函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给filter并筛选出执行结果为true的元素,即只需要关心如何能使结果为true,对于程序员来说
filter功能很强大,能通过思路转换做其他的很多操作
List<Person> result = list.stream() //获取数据源
.filter(Person::isStudent) //为true的条件
.collect(toList()); //转化为集合
去重
distinct()是Stream接口的方法。distinct()使用hashCode()和equals()方法来获取不同的元素.而且方法本身不提供按照属性的过滤,所以必须需要自行去实现hashCode()和equals()方法来确保过滤的准确性.
如果distinct()正在处理有序流,那么对于重复元素,将保留重复的第一个元素,并且以这种方式选择不同元素是稳定的.
在无序流的情况下,不同元素的选择不一定是稳定的,是可以改变的。distinct()执行有状态的中间操作。在有序流的并行流的情况下,保持distinct()的稳定性是需要很高的代价的,因为它需要大量的缓冲开销。如果我们不需要保持遭遇顺序的一致性,那么我们应该可以使用通过**BaseStream.unordered()**方法实现的无序流。
/**
* 去掉重复的结果
*/
List<Person> result = list.stream() //获取数据源
.distinct() // 调用方法
.collect(toList()); //转化为集合
截取
List<Person> result = list.stream() //获取数据源
.limit(3) //截取前3个数
.collect(toList()); //转化为集合
映射
对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
map方法返回是一个object map将流中当前元素替换为此返回值 处理并替换以原样返回
flatMap方法返回的是一个stream, 将流中的当前元素替换为此返回流拆解的流元素 处理并以结果替换流
如,获取每个人的姓名(实则是将Perosn类型转换成String类型)
List<String> result = list.stream() //获取数据源
.map(Person::getName) // 源数据源 转化为String 的映射
.collect(toList()); // 以String 为对象转化为List
合并多个流
例:列出List中各不相同的单词,List集合如下:
List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");
/**
*list.stream() //数据源
* .map(line->line.split(" ")); //映射出一个由多个line对象,值为迭代里的每个对象的split(" ");
* .map(Arrays::stream) // 转化为steam的小流 大流包小流
*/
list.stream()
.map(line->line.split(" "))
.flatMap(Arrays::stream) //源数据源 转化为切隔后的对象,并以此为结果替换源数据
.distinct()
.collect(toList());
是否匹配任一元素: anyMatch
/**anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执*行结果为boolean类型。
*如,判断list中是否有学生:
*/
boolean result = list.stream()
.anyMatch(Person::isStudent);
是否匹配所有元素: allMatch
//判断流中所有元素是否都满足指定条件
boolean result = list.stream().allMatch(Person::isStudent);
是否都不匹配所有元素: noneMatch
//是否都不满足
boolean result = list.stream().noneMatch(Person::isStudent);
获取任一元素findAny
findAny能够从流中随便选一个元素,返回一个Optional类型元素
Optional<Person> person = list.stream().findAny();
获取第一个元素findFirst
Optional<Person> person = list.stream().findFirst();
归约
归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。 在流中,reduce函数能实现归约。 reduce函数接收两个参数: 1. 初始值 reduce的第一个参数表示初试值为0; 2. 进行归约操作的Lambda表达式 reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。
/**
* 例:计算所有人的年龄总和
* 0 初始值 (person1,person2)->person1.getAge()+person2.getAge() 归约操作
*/
int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());
数值流的使用
采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。
当流操作为纯数值操作时,使用数值流能获得较高的效率
转换流
=>
IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong
/**
* 如,将Person中的age转换成数值流:
*/
IntStream stream = list.stream().mapToInt(Person::getAge);
/**
* 上述计算所有人年龄 如果为Integer
*/
int sum = list.stream().mapToInt(Person::getAge).reduce(0,(i1,i2)->i1+i2)
引申出:
OptionalInt、OptionalDouble、OptionalLong
由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理
Collector 收集
收集器用来将经过筛选、映射的流进行最后的整理,可以使得最后的结果以不同的形式展现。collect 方法即为收集器,它接收
Collector
接口的实现作为具体收集器的收集方法。Collector
接口提供了很多默认实现的方法,我们可以直接使用它们格式化流的结果;也可以自定义Collector
接口的实现,从而定制自己的收集器
中间操作和收集操作
操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream<T> | Predicate<T> | T -> boolean |
distinct | 中间 | Stream<T> | ||
skip | 中间 | Stream<T> | long | |
map | 中间 | Stream<R> | Function<T, R> | T -> R |
flatMap | 中间 | Stream<R> | Function<T, Stream<R>> | T -> Stream<R> |
limit | 中间 | Stream<T> | long | |
sorted | 中间 | Stream<T> | Comparator<T> | (T, T) -> int |
anyMatch | 终端 | boolean | Predicate<T> | T -> boolean |
noneMatch | 终端 | boolean | Predicate<T> | T -> boolean |
allMatch | 终端 | boolean | Predicate<T> | T -> boolean |
findAny | 终端 | Optional<T> | ||
findFirst | 终端 | Optional<T> | ||
forEach | 终端 | void | Consumer<T> | T -> void |
collect | 终端 | R | Collector<T, A, R> | |
reduce | 终端 | Optional<T> | BinaryOperator<T> | (T, T) -> T |
count | 终端 | long |
Collectors 类的静态工厂方法
使用时建议使用Collectors.静态方法
工厂方法 | 返回类型 | 用途 | 示例 |
---|---|---|---|
toList | List<T> | 把流中所有项目收集到一个 List | List<Project> projects = projectStream.collect(toList()); |
toSet | Set<T> | 把流中所有项目收集到一个 Set,删除重复项 | Set<Project> projects = projectStream.collect(toSet()); |
toCollection | Collection<T> | 把流中所有项目收集到给定的供应源创建的集合 | Collection<Project> projects = projectStream.collect(toCollection(), ArrayList::new); |
counting | Long | 计算流中元素的个数 | long howManyProjects = projectStream.collect(counting()); |
summingInt | Integer | 对流中项目的一个整数属性求和 | int totalStars = projectStream.collect(summingInt(Project::getStars)); |
averagingInt | Double | 计算流中项目 Integer 属性的平均值 | double avgStars = projectStream.collect(averagingInt(Project::getStars)); |
summarizingInt | IntSummaryStatistics | 收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值 | IntSummaryStatistics projectStatistics = projectStream.collect(summarizingInt(Project::getStars)); |
joining | String | 连接对流中每个项目调用 toString 方法所生成的字符串 | String shortProject = projectStream.map(Project::getName).collect(joining(", ")); |
maxBy | Optional<T> | 按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty() | Optional<Project> fattest = projectStream.collect(maxBy(comparingInt(Project::getStars))); |
minBy | Optional<T> | 按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty() | Optional<Project> fattest = projectStream.collect(minBy(comparingInt(Project::getStars))); |
reducing | 归约操作产生的类型 | 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值 | int totalStars = projectStream.collect(reducing(0, Project::getStars, Integer::sum)); |
collectingAndThen | 转换函数返回的类型 | 包含另一个收集器,对其结果应用转换函数 | int howManyProjects = projectStream.collect(collectingAndThen(toList(), List::size)); |
groupingBy | Map<K, List<T>> | 根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键 | Map<String,List<Project>> projectByLanguage = projectStream.collect(groupingBy(Project::getLanguage)); |
partitioningBy | Map<Boolean,List<T>> | 根据对流中每个项目应用断言的结果来对项目进行分区 | Map<Boolean,List<Project>> vegetarianDishes = projectStream.collect(partitioningBy(Project::isVegetarian)); |
并发流
并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。
将一个顺序执行的流转变成一个并发的流只要调用 parallel() 方法
将一个并发流转成顺序的流只要调用 sequential() 方法
并发流使用的默认线程数等于你机器的处理器核心数。
通过这个方法可以修改这个值,这是全局属性。
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");
并非使用多线程并行流处理数据的性能一定高于单线程顺序流的性能,因为性能受到多种因素的影响。
如何高效使用并发流的一些建议:
- 如果不确定, 就自己测试。
- 尽量使用基本类型的流 IntStream, LongStream, DoubleStream
- 有些操作使用并发流的性能会比顺序流的性能更差,比如limit,findFirst,依赖元素顺序的操作在并发流中是极其消耗性能的。findAny的性能就会好很多,应为不依赖顺序。
- 考虑流中计算的性能(Q)和操作的性能(N)的对比, Q表示单个处理所需的时间,N表示需要处理的数量,如果Q的值越大, 使用并发流的性能就会越高。
- 数据量不大时使用并发流,性能得不到提升。
- 考虑数据结构:并发流需要对数据进行分解,不同的数据结构被分解的性能时不一样的。
流的数据源和可分解性
源 | 可分解性 |
---|---|
ArrayList | 非常好 |
LinkedList | 差 |
IntStream.range | 非常好 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |
流的特性以及中间操作对流的修改都会对数据对分解性能造成影响。 比如固定大小的流在任务分解的时候就可以平均分配,但是如果有filter操作,那么流就不能预先知道在这个操作后还会剩余多少元素。
考虑终端操作的性能:如果终端操作在合并并发流的计算结果时的性能消耗太大,那么使用并发流提升的性能就会得不偿失。