Java8的新特性主要是 Lambda表达式 和 流,当流和Lambda表达式结合起来一起使用时,因为流申明式处理数据集合的特点,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
如何使用流来简化代码,示例如下:
现有一个需求,对某省施工工程情况进行筛选统计,条件如下:
1.筛选出工程总面积 大于 200 万㎡ 的工程(库里数值以 万㎡ 为单位)
2.并且工程总造价 大于 1亿 的工程(库里数值以 百万元 为单位)
3.对筛选出的工程按总造价倒序
4.获取排序后的工程名称
Java8 以前的实现方式
private List<String> getProjectNameList(List<Project> projectList) {
/** 按条件过滤排序后集合 */
List<Project> list = new ArrayList<>();
//1.筛选出工程总面积 大于 200 万㎡ 的工程(库里数值以 万㎡ 为单位)
//2.并且工程总造价 大于 1亿 的工程(库里数值以 百万元 为单位)
for (Project project : projectList) {
if (project.getArea() > 200 && project.getCost > 100) {
list.add(project);
}
}
//3.对筛选出的工程按总造价倒序
Collections.sort(list, new Comparator<Project>() {
@Override
public int compare(Project p1, Project p2) {
return p2.getCost().compareTo(p1.getCost());
}
});
//4.获取排序后的工程名称
List<String> projectNameList = new ArrayList<>();
for (Project project : list) {
projectNameList.add(project.getName());
}
return projectNameList;
}
Java8 的实现方式
private List<String> getProjectNameList(List<Project> projectList) {
//1.筛选出工程总面积 大于 200 万㎡ 的工程(库里数值以 万㎡ 为单位)
//2.并且工程总造价 大于 1亿 的工程(库里数值以 百万元 为单位)
//3.对筛选出的工程按总造价倒序
//4.获取排序后的工程名称
return projectList.stream()
.filter(i -> i.getArea() > 200 && i.getCost > 100)
.sorted(comparing(Project::getCost).reversed()).
.map(Project::getName)
.collect(Collectors.toList()); //转换为List
}
渥!轻轻松松几行代码,就是这么简单,清晰明了!
刚好这时候需求大爷又改了方案,市里的工程需要按区域划分导出。
好家伙!不要慌!(这放以前慌得一批,单想都知道代码一大堆,不过现在我有Java8)
实现方式
private Map<String,List<String>> getProjectNameList(List<Project> projectList) {
//1.筛选出工程总面积 大于 200 万㎡ 的工程(库里数值以 万㎡ 为单位)
//2.并且工程总造价 大于 1亿 的工程(库里数值以 百万元 为单位)
//3.对筛选出的工程按总造价倒序
//4.获取排序后的工程名称
//5.按区域划分
return projectList.stream()
.filter(i -> i.getArea() > 200 && i.getCost > 100)
.sorted(comparing(Project::getCost).reversed()).
.map(Project::getName)
.collect(groupingBy(Project::getAreaCode)); //按区域编码划分
}
又是一行代码轻松搞定,看完上面的例子接下来就进入我们的正题,一起来了解下流的生成和使用:
如何生成流
生成流的方式主要有五种
1.第一种通过Collection集合的 Stream() 方法(串行流)或者 parallelStream() 方法(并行流)创建Stream。应用中最常用的一种
List<String> list = Arrays.asList("1","2","3","4","5","6","0");
Stream<String> stream = list.stream();
Stream<String> stream1 = list.parallelStream();
2.通过数组中的静态方法stream() 获取数值流
IntStream stream = Arrays.stream(new int[]{1,2,3});
使用数值流可以避免计算过程中拆箱装箱,提高性能。
另外Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流(Stream)转换成对应的数值流,同时提供了boxed方法将数值流转换为对象流
3.通过Stream类中 of() 静态方法获取流
Stream<String> stream = Stream.of("a","b","c");
通过Stream的of方法生成流,通过Stream的empty方法可以生成一个空流
4.通过文件生成
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())
通过Files.line方法得到一个流,并且得到的每个流是给定文件中的一行
5.通过函数生成,提供了iterate和generate两个静态方法,从函数中创建无限流(迭代、生成)
//迭代(需要传入一个种子,也就是起始值,然后传入一个一元操作)
Stream<Integer> stream1 = Stream.iterate(2, (x) -> x * 2);
//生成(无限产生对象)
Stream<Double> stream2 = Stream.generate(() -> Math.random());
iterate方法接受两个参数,第一个为初始化值,第二个为进行的函数操作,因为iterator生成的流为无限流,通过limit方法对流进行了截断,只生成5个偶数
generate方法接受一个参数,方法参数类型为Supplier ,由它为流提供值。generate生成的流也是无限流,因此通过limit对流进行了截断
Stream<Integer> stream1 = Stream.iterate(2, (x) -> x * 2).limit(5);
Stream<Double> stream2 = Stream.generate(Math::random).limit(5);
流的使用
流的使用将分为终端操作和中间操作进行介绍
中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线终止操作,否则中间操作不会执行任何处理。
终端操作时一次性全部处理,称为“延迟加载”。
filter() 筛选
List<String> list = Arrays.asList("1","2","3","4","0","222","33");
Stream<String> stream = list.stream().filter((x) -> {
System.out.println(" api 中间操作。");
return x.equals("3");
});
//终止操作:只有执行终止操作才会执行全部。即:延迟加载
stream.forEach(System.out::println);
结果
api 中间操作。
api 中间操作。
api 中间操作。
3
api 中间操作。
api 中间操作。
api 中间操作。
api 中间操作。
通过使用filter方法进行条件筛选,filter的方法参数为一个条件
distinct() 去除重复元素
List<Integer> list = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream().distinct();
distinct 通过流所生成元素的hashCode() 和equals() 去除重复元素
limit() 返回指定流个数
List<Integer> list = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream().limit(2);
通过limit方法指定返回流的个数,limit的参数值必须>=0,否则将会抛出异常
skip(n) 跳过流中的元素
List<Integer> list = Arrays.asList(1, 1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream().skip(2);
skip(n)返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空,与limit(n)互补。skip的参数值必须>=0,否则将会抛出异常
map() 流映射
就是将接受的元素映射成另外一个元素
List<String> stringList = Arrays.asList("Java 8", "Lambdas", "In", "Action");
//装换成String的长度
Stream<Integer> stream = stringList.stream().map(String::length);
flatMap() 流转换
将一个流中的每个值都转换为另一个流
List<String> list = Arrays.asList("Hello", "World").stream()
.map(i -> i.split("l"))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
map(i -> i.split("l"))的返回值为Stream<String[]>,我们想获取Stream<String>,再通过flatMap方法完成转换
终端操作
三种元素匹配
1.allMatch() 匹配所有
public void test1(){
boolean allMatch = Arrays.asList(1, 2, 3, 4, 5).stream().allMatch(i -> i > 2);
System.out.println(allMatch);
}
2.anyMatch() 检查是否至少匹配一个元素
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
if (list.stream().anyMatch(i -> i > 2)) {
System.out.println("存在大于2的值");
}
等同于
for (Integer i : list) {
if (i > 2) {
System.out.println("存在大于2的值");
break;
}
}
3.noneMatch() 全部不匹配
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
if (list.stream().noneMatch(i -> i > 2)) {
System.out.println("值都小于2");
}
统计流中元素个数
1、通过count()
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Long result = list.stream().count();
2、通过counting()
public void test() {
Long collect = list.stream().map(Project::getName).collect(Collectors.counting());
System.out.println(collect);
}
这种统计元素个数的方法在与collect联合使用的时候特别有用
查找
1、findFirst查找第一个
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = list.stream().filter(i -> i > 3).findFirst();
通过findFirst方法查找到第一个大于三的元素并打印
2、findAny随机查找一个
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = list.stream().filter(i -> i > 3).findAny();
通过findAny方法查找到其中一个大于三的元素并打印,因为内部进行优化的原因,当找到第一个满足大于三的元素时就结束,该方法结果和findFirst方法结果一样。提供findAny方法是为了更好的利用并行流,findFirst方法在并行上限制更多。
reduce将流中的元素组合起来
对一个集合中的值进行求和
jdk8之前的实现方式:
int sum = 0;
for (int i : list) {
sum += i;
}
jdk8之后通过reduce进行处理
//(T identitty,BinaryOperator)首先,需要传一个起始值,然后,传入的是一个二元运算。
int sum = list.stream().reduce(0, (a, b) -> (a + b));
//或者引用方法简写成下面
int sum = list.stream().reduce(0, Integer::sum);
//或者reduce(BinaryOperator)此方法相对于上面方法来说,没有起始值,则有可能结果为空,所以返回的值会被封装到Optional中
int sum = list.stream().reduce(Integer::sum);
获取流中最小最大值
通过min/max获取最小最大值
//min获取流中最小值,max获取流中最大值,方法参数为Comparator<? super T> comparator
Optional<Integer> min = list.stream().map(Project::getCost).min(Integer::compareTo);
Optional<Integer> max = list.stream().map(Project::getCost).max(Integer::compareTo);
也可以写成:
//min获取流中最小值,max获取流中最大值,方法参数为Comparator<? super T> comparator
OptionalInt min = list.stream().mapToInt(Project::getCost).min();
OptionalInt max = list.stream().mapToInt(Project::getCost).max();
通过minBy/maxBy获取最小最大值
//minBy获取流中最小值,maxBy获取流中最大值,方法参数为Comparator<? super T> comparator
Optional<Integer> min = list.stream().map(Project::getCost).collect(minBy(Integer::compareTo));
Optional<Integer> max = list.stream().map(Project::getCost).collect(maxBy(Integer::compareTo));
通过reduce获取最小最大值
Optional<Integer> min = list.stream().map(Project::getCost).reduce(Integer::min);
Optional<Integer> max = list.stream().map(Project::getCost).reduce(Integer::max);
求和
通过summingInt
double sum = list.stream().collect(summingDouble(Project::getCost));
如果数据类型为int、long,则通过summingInt、summingLong方法进行求和
通过reduce
double sum = list.stream().map(Project::getCost).reduce(0, Double::sum);
通过sum
int sum = list.stream().mapToInt(Project::getArea).sum();
在上面求和、求最大值、最小值的时候,推荐使用min、max、sum方法。因为最简洁易读,同时通过mapToInt将对象流转换为数值流,避免了装箱和拆箱操作。
通过averagingInt求平均值
double avg = list.stream().collect(averagingDouble(Project::getCost));
如果数据类型为int、long,则通过averagingInt、averagingLong方法进行求平均
通过summarizingInt同时求总和、平均值、最大值、最小值
IntSummaryStatistics intSummaryStatistics = list.stream().collect(summarizingInt(Project::getArea));
//获取平均值
double average = intSummaryStatistics.getAverage();
//获取最小值
int min = intSummaryStatistics.getMin();
//获取最大值
int max = intSummaryStatistics.getMax();
//获取总和
long sum = intSummaryStatistics.getSum();
如果数据类型为double、long,则通过summarizingDouble、summarizingLong方法
通过foreach进行元素遍历
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream().forEach(System.out::println);
返回集合
List<String> projectNameList = list.stream().map(Project::getName).collect(toList());
Set<String> projectNameSet = list.stream().map(Project::getName).collect(toSet());
通过上面遍历和返回集合的使用发现流只是把原来的外部迭代放到了内部进行,这也是流的主要特点之一。
通过joining拼接流中的元素
String result = list.stream().map(Project::getName).collect(Collectors.joining(", "));
通过groupingBy进行分组
Map<Type, List<Dish>> result = list.stream().collect(groupingBy(Project::getAreaCode));
在collect方法中传入groupingBy进行分组,其中groupingBy的方法参数为分类函数。还可以通过嵌套使用groupingBy进行多级分类
Map<String, List<Project>> result = list.stream().collect(groupingBy(Project::getAreaCode,
groupingBy(project -> {
if (project.getCost() > 100) return '100'
else if (project.getCost() <= 700) return '700';
else return '0';
})));
进阶通过partitioningBy进行分区
分区是特殊的分组,它分类依据是true和false,所以返回的结果最多可以分为两组
Map<Boolean, List<Project>> result = list.stream().collect(partitioningBy(Project :: isCheck))
等同于
Map<Boolean, List<Project>> result = list.stream().collect(groupingBy(Project :: isCheck))
这个例子可能并不能看出分区和分类的区别,甚至觉得分区根本没有必要,换个明显一点的例子:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Map<Boolean, List<Integer>> result = list.stream().collect(partitioningBy(i -> i < 2));
返回值的键仍然是布尔类型,但是它的分类是根据范围进行分类的,分区比较适合处理根据范围进行分类