巧用 Java8 的 Stream 来优化代码,提高代码可视性

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));

返回值的键仍然是布尔类型,但是它的分类是根据范围进行分类的,分区比较适合处理根据范围进行分类

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 好的,我来帮助你使用 Java 8 的新特性来优化这段代码。 首先,我们可以使用 Java 8 中的 Stream API 和 Lambda 表达式来优化这段代码。 使用 Stream API 和 Lambda 表达式可以帮助我们简化代码,并提高代码的可读性。 例如,我们可以使用 Stream API 来代替 for 循环,并使用 Lambda 表达式来简化 if 语句。 例如,下面是使用 Stream API 和 Lambda 表达式优化后的代码: ``` List<String> result = list.stream() .filter(s -> s.length() > 3) .map(String::toUpperCase) .collect(Collectors.toList()); ``` 另外,我们还可以使用 Java 8 中的 Optional 类来优化这段代码。Optional 类是一个可以为 null 的容器对象,它可以帮助我们避免空指针异常。 例如,我们可以使用 Optional 类来代替 if 语句,从而使代码更加简洁。 例如,下面是使用 Optional 类优化后的代码: ``` Optional<String> result = list.stream() .filter(s -> s.length() > 3) .map(String::toUpperCase) .findFirst(); ``` 希望这些建议能帮助你使用 Java 8 的新特性优化代码。 ### 回答2: 使用Java 8的新特性可以优化这段代码。有几个可以改进的方面: 1. 使用Lambda表达式来替代匿名内部类:Lambda表达式可以简化代码提高可读性。比如,在使用线程时可以使用Lambda表达式来创建匿名线程。 2. 使用流操作来替代循环:Java 8引入了Stream API,可以通过流操作来处理集合数据,简化了循环的使用。在处理集合数据时,可以使用流操作来替代传统的循环。 3. 使用Optional类来避免NullPointerException:Optional类是Java 8中引入的用于解决NullPointerException的问题。可以使用Optional类来替代传统的空指针检查,提高代码的健壮性。 4. 使用新的日期和时间API:Java 8引入了新的日期和时间API,提供了一种更加简单和方便的方式来处理日期和时间。可以使用新的日期和时间API来替代旧的Date和Calendar类,使代码更加清晰和易于维护。 通过使用Java8的新特性,能够简化代码提高可读性和性能。这些优化将使代码更加清晰,易于理解和维护。 ### 回答3: 使用Java 8的新特性可以通过Lambda表达式和流(stream)来优化代码。考虑以下示例代码: ```java List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); // 原始代码 List<String> result = new ArrayList<>(); for (String name : names) { if (name.startsWith("A")) { result.add(name.toUpperCase()); } } ``` 我们可以使用Java 8提供的Lambda表达式和流来优化上面的代码,使其更加简洁和易读。 ```java List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); // 使用Lambda表达式和流优化后的代码 List<String> result = names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .collect(Collectors.toList()); ``` 通过使用`stream()`方法将集合转换为流,我们可以使用`filter()`方法过滤出以"A"开头的姓名,然后使用`map()`方法将名称转换为大写,最后使用`collect()`方法将结果收集到一个新的列表中。 这样的优化代码不但更简洁,同时也更易于理解和维护。Lambda表达式和流的引入使代码更加函数式和声明式,减少了冗余代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

几行代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值