详解 java8 stream流的开发妙用

简介

Java 8 的 Stream API 是一种新的抽象,它允许以声明式的方式处理数据。这种抽象具有多种优势,包括:代码简洁、可读性强、以及能够轻松地利用多核架构。下面是关于 Java 8 Stream 的一些详细解释

解释

创建 Stream

  • 从集合创建: Collection.stream()Collection.parallelStream()
  • 从数组创建: Arrays.stream(array)
  • 直接创建: Stream.of(val1, val2, val3)
  • 空 Stream: Stream.empty()
  • 无限 Stream: Stream.iterateStream.generate

中间操作 (Intermediate Operations)

filter: 过滤

我们通过一个简单的例子来展示使用filter和不使用filter的区别。

import java.util.*;
import java.util.stream.*;

public class FilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用filter过滤出偶数
        List<Integer> evensWithFilter = numbers.stream()
                                               .filter(n -> n % 2 == 0)
                                               .collect(Collectors.toList());
        System.out.println(evensWithFilter);  // 输出: [2, 4, 6, 8, 10]

        // 不使用filter,通过传统循环过滤出偶数
        List<Integer> evensWithoutFilter = new ArrayList<>();
        for (Integer n : numbers) {
            if (n % 2 == 0) {
                evensWithoutFilter.add(n);
            }
        }
        System.out.println(evensWithoutFilter);  // 输出: [2, 4, 6, 8, 10]
    }
}

优点对比:

  1. 代码简洁性:

    • 使用filter可以使代码更简洁、更易读。通过链式调用,我们可以在一行代码中完成过滤操作。
    • 不使用filter则需要多行代码,并且需要明确的循环和条件判断。
  2. 函数式风格:

    • filter方法允许我们采用函数式编程风格,这种风格可以减少错误并提高代码的可维护性。
    • 传统的循环方式则是命令式编程风格,可能会引入更多错误,特别是在复杂的过滤逻辑中。
  3. 不变性:

    • 使用filter方法不会修改原始的数据源,而是创建一个新的Stream。这种不变性是函数式编程的重要特点,有助于减少bug和侧效应。
    • 传统的循环方式则可能会修改原始的数据,增加了出错的可能性。
  4. 优化性能:

    • filter操作是惰性的,只有在需要时才会执行。而且它可以与其他Stream操作如mapsorted等一起优化,提高性能。
    • 传统的循环则是立即执行的,可能会导致不必要的计算,特别是在大数据集上。

 

map: 映射,将每个元素映射到另一个对象上。

让我们通过一个示例来比较使用 map 和不使用 map 的效果:

import java.util.*;
import java.util.stream.*;

public class MapExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 使用 map 将每个单词转换为大写
        List<String> uppercasedWordsWithMap = words.stream()
                                                   .map(String::toUpperCase)
                                                   .collect(Collectors.toList());
        System.out.println(uppercasedWordsWithMap);  // 输出: [APPLE, BANANA, CHERRY]

        // 不使用 map,通过传统循环将每个单词转换为大写
        List<String> uppercasedWordsWithoutMap = new ArrayList<>();
        for (String word : words) {
            uppercasedWordsWithoutMap.add(word.toUpperCase());
        }
        System.out.println(uppercasedWordsWithoutMap);  // 输出: [APPLE, BANANA, CHERRY]
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 map 可以在一行代码中完成映射操作,使代码更为简洁、易读。
    • 不使用 map 则需要多行代码,并且需要明确的循环和转换操作。
  2. 函数式风格:

    • map 方法允许我们采用函数式编程风格,这种风格可以减少错误并提高代码的可维护性。
    • 传统的循环方式是命令式编程风格,可能会引入更多错误,特别是在复杂的映射逻辑中。
  3. 不变性:

    • 使用 map 方法不会修改原始的数据源,而是创建一个新的 Stream。这种不变性是函数式编程的重要特点,有助于减少 bug 和侧效应。
    • 传统的循环方式则可能会修改原始的数据,增加了出错的可能性。
  4. 链式调用:

    • map 方法可以与其他 Stream 操作如 filtersorted 等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。
    • 传统的循环则难以与其他操作组合,可能需要额外的代码来实现同样的功能。

flatMap: 扁平映射,可以将多个 Stream 合并为一个 Stream。

我们通过一个示例来比较使用flatMap和不使用flatMap的效果:

import java.util.*;
import java.util.stream.*;

public class FlatMapExample {
    public static void main(String[] args) {
        List<List<Integer>> listOfLists = Arrays.asList(
                Arrays.asList(1, 2, 3),
                Arrays.asList(4, 5, 6),
                Arrays.asList(7, 8, 9)
        );

        // 使用flatMap将多个List合并成一个List
        List<Integer> flatListWithFlatMap = listOfLists.stream()
                                                       .flatMap(List::stream)
                                                       .collect(Collectors.toList());
        System.out.println(flatListWithFlatMap);  // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

        // 不使用flatMap,通过传统循环将多个List合并成一个List
        List<Integer> flatListWithoutFlatMap = new ArrayList<>();
        for (List<Integer> list : listOfLists) {
            flatListWithoutFlatMap.addAll(list);
        }
        System.out.println(flatListWithoutFlatMap);  // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 flatMap 可以在一行代码中完成多个集合的合并,使代码更为简洁、易读。
    • 不使用 flatMap 则需要多行代码,并且需要明确的循环和合并操作。
  2. 函数式风格:

    • flatMap 方法允许我们采用函数式编程风格,有助于减少错误并提高代码的可维护性。
    • 传统的循环方式是命令式编程风格,可能会引入更多错误。
  3. 不变性:

    • 使用 flatMap 方法不会修改原始的数据源,而是创建一个新的 Stream。这种不变性是函数式编程的重要特点。
    • 传统的循环方式可能会修改原始的数据,增加了出错的可能性。
  4. 链式调用:

    • flatMap 方法可以与其他 Stream 操作如 filtermap 等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。
    • 传统的循环方式难以与其他操作组合,可能需要额外的代码来实现同样的功能。

 

distinct: 去重,通过元素的 hashCode() 和 equals() 去除重复元素。

下面通过一个示例来展示使用 distinct 和不使用 distinct 的效果:

import java.util.*;
import java.util.stream.*;

public class DistinctExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);

        // 使用 distinct 去除重复元素
        List<Integer> uniqueNumbersWithDistinct = numbers.stream()
                                                         .distinct()
                                                         .collect(Collectors.toList());
        System.out.println(uniqueNumbersWithDistinct);  // 输出: [1, 2, 3, 4]

        // 不使用 distinct,通过 HashSet 去除重复元素
        Set<Integer> uniqueNumbersWithoutDistinct = new HashSet<>(numbers);
        System.out.println(uniqueNumbersWithoutDistinct);  // 输出: [1, 2, 3, 4]
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 distinct 可以在一行代码中完成去重操作,使代码更为简洁、易读。
    • 不使用 distinct 则需要创建一个新的 HashSet 对象,并将原始数据复制到该 HashSet 中进行去重。
  2. 函数式风格:

    • distinct 方法允许我们以函数式的风格处理数据,减少错误并提高代码的可维护性。
    • 使用 HashSet 进行去重是一种命令式的编程风格,可能会引入更多错误。
  3. 不变性:

    • 使用 distinct 方法不会修改原始的数据源,而是创建一个新的 Stream,保持了函数式编程的不变性特点。
    • 使用 HashSet 进行去重可能会修改原始数据,增加了出错的可能性。
  4. 链式调用:

    • distinct 方法可以与其他 Stream 操作如 filtermap 等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。
    • 使用 HashSet 进行去重则难以与其他操作组合,可能需要额外的代码来实现同样的功能。

 

sorted: 排序,可以提供一个 Comparator 进行自定义排序。

下面通过一个示例来展示使用 sorted 和不使用 sorted 的效果:

import java.util.*;
import java.util.stream.*;

public class SortedExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date");

        // 使用 sorted 进行自然排序
        List<String> sortedWordsWithSorted = words.stream()
                                                  .sorted()
                                                  .collect(Collectors.toList());
        System.out.println(sortedWordsWithSorted);  // 输出: [apple, banana, cherry, date]

        // 不使用 sorted,通过 Collections.sort 进行排序
        List<String> sortedWordsWithoutSorted = new ArrayList<>(words);
        Collections.sort(sortedWordsWithoutSorted);
        System.out.println(sortedWordsWithoutSorted);  // 输出: [apple, banana, cherry, date]
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 sorted 可以在一行代码中完成排序操作,使代码更为简洁、易读。
    • 不使用 sorted 则需要复制原始数据到一个新的列表中,并使用 Collections.sort 进行排序。
  2. 函数式风格:

    • sorted 方法允许我们以函数式的风格处理数据,减少错误并提高代码的可维护性。
    • 使用 Collections.sort 进行排序是一种命令式的编程风格,可能会引入更多错误。
  3. 不变性:

    • 使用 sorted 方法不会修改原始的数据源,而是创建一个新的 Stream,保持了函数式编程的不变性特点。
    • 使用 Collections.sort 进行排序会修改原始数据,增加了出错的可能性。
  4. 链式调用:

    • sorted 方法可以与其他 Stream 操作如 filtermap 等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。
    • 使用 Collections.sort 进行排序则难以与其他操作组合,可能需要额外的代码来实现同样的功能。

 

peek: 用于 debug,每个元素经过这个操作时都会执行给定的消费函数。

让我们通过一个示例来展示使用 peek 和不使用 peek 的效果:

import java.util.*;
import java.util.stream.*;

public class PeekExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 使用 peek 查看流中的元素
        List<String> uppercasedWordsWithPeek = words.stream()
                                                    .peek(System.out::println)
                                                    .map(String::toUpperCase)
                                                    .collect(Collectors.toList());
        // 输出:
        // apple
        // banana
        // cherry

        // 不使用 peek,直接进行映射操作
        List<String> uppercasedWordsWithoutPeek = words.stream()
                                                       .map(String::toUpperCase)
                                                       .collect(Collectors.toList());
        // 无输出
    }
}

优点对比:

  1. 调试方便:

    • 使用 peek 可以在不改变流的情况下查看流中的元素,对于调试非常方便。
    • 不使用 peek 则无法在流操作过程中查看流中的元素,可能需要额外的代码来进行调试。
  2. 代码简洁性:

    • peek 提供了一种简洁的方式来查看和调试流操作。
    • 不使用 peek 可能需要额外的代码来查看流中的元素,可能会使代码变得更加复杂。
  3. 函数式风格:

    • peek 方法允许我们以函数式的风格查看流中的元素,使代码保持了函数式的风格。
    • 不使用 peek 可能需要使用命令式的代码来查看流中的元素,可能会打破函数式编程的风格。
  4. 链式调用:

    • peek 方法可以与其他 Stream 操作如 filtermap 等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。
    • 不使用 peek 可能需要断开链式调用来查看流中的元素,可能会使代码变得更加复杂。

 

limitskip: 用于缩减 stream 的大小。

让我们通过一个示例来展示使用 limitskip 的效果:

import java.util.*;
import java.util.stream.*;

public class LimitSkipExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用 limit 缩减流的大小
        List<Integer> limitedNumbers = numbers.stream()
                                              .limit(5)
                                              .collect(Collectors.toList());
        System.out.println(limitedNumbers);  // 输出: [1, 2, 3, 4, 5]

        // 使用 skip 跳过流中的元素
        List<Integer> skippedNumbers = numbers.stream()
                                              .skip(5)
                                              .collect(Collectors.toList());
        System.out.println(skippedNumbers);  // 输出: [6, 7, 8, 9, 10]
    }
}

优点解析:

  1. 代码简洁性:

    • limitskip 提供了简洁的方式来缩减或跳过流中的元素,无需额外的循环或条件判断。
    • 传统的方式可能需要使用循环和条件判断来实现相同的功能,代码可能会更加冗长。
  2. 函数式风格:

    • limitskip 允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 不变性:

    • limitskip 不会修改原始数据源,而是创建一个新的 Stream,保持了函数式编程的不变性特点。
    • 传统的方式可能会修改原始数据,增加了出错的可能性。
  4. 链式调用:

    • limitskip 可以与其他 Stream 操作如 filtermap 等链式调用,提供了高度的灵活性和可组合性。
    • 传统的方式可能需要断开链式调用来实现相同的功能,可能会使代码变得更加复杂。

 

终止操作 (Terminal Operations)

forEach: 对每个元素执行操作

让我们通过一个示例来展示使用 forEach 和不使用 forEach 的效果:

import java.util.*;
import java.util.stream.*;

public class ForEachExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 使用 forEach 输出每个单词
        words.stream().forEach(System.out::println);
        // 输出:
        // apple
        // banana
        // cherry

        // 不使用 forEach,通过传统循环输出每个单词
        for (String word : words) {
            System.out.println(word);
        }
        // 输出:
        // apple
        // banana
        // cherry
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 forEach 可以在一行代码中完成对每个元素的操作,使代码更为简洁、易读。
    • 不使用 forEach 则需要明确的循环结构,代码可能会显得更加冗长。
  2. 函数式风格:

    • forEach 方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 方法引用:

    • forEach 可以与方法引用一起使用,提供了一种简洁的方式来执行操作。
    • 传统的循环则难以与方法引用结合使用,可能需要额外的代码来实现相同的功能。
  4. 链式调用:

    • 虽然 forEach 是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。
    • 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。

 

toArray: 将 Stream 转换为数组

让我们通过一个示例来展示使用 toArray 和不使用 toArray 的效果:

import java.util.*;
import java.util.stream.*;

public class ToArrayExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 使用 toArray 将流转换为数组
        String[] arrayWithToArray = words.stream().toArray(String[]::new);
        System.out.println(Arrays.toString(arrayWithToArray));  // 输出: [apple, banana, cherry]

        // 不使用 toArray,通过循环将流转换为数组
        String[] arrayWithoutToArray = new String[words.size()];
        for (int i = 0; i < words.size(); i++) {
            arrayWithoutToArray[i] = words.get(i);
        }
        System.out.println(Arrays.toString(arrayWithoutToArray));  // 输出: [apple, banana, cherry]
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 toArray 可以简洁地将流转换为数组,无需额外的循环或数组操作。
    • 不使用 toArray 则需要显式的循环和数组赋值,代码可能会显得更加冗长。
  2. 函数式风格:

    • toArray 方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 类型安全:

    • toArray 提供了一种类型安全的方式来创建数组,我们可以提供一个生成器函数来创建指定类型的数组。
    • 传统的循环方式也可以创建类型安全的数组,但可能需要更多的代码来实现。
  4. 链式调用:

    • toArray 是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。
    • 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。

 

reduce: 将 Stream 缩减成单个值

让我们通过一个示例来展示使用 reduce 和不使用 reduce 的效果:

import java.util.*;
import java.util.stream.*;

public class ReduceExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用 reduce 求和
        Optional<Integer> sumWithReduce = numbers.stream()
                                                 .reduce(Integer::sum);
        sumWithReduce.ifPresent(sum -> System.out.println("Sum with reduce: " + sum));
        // 输出: Sum with reduce: 15

        // 不使用 reduce,通过循环求和
        int sumWithoutReduce = 0;
        for (int number : numbers) {
            sumWithoutReduce += number;
        }
        System.out.println("Sum without reduce: " + sumWithoutReduce);
        // 输出: Sum without reduce: 15
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 reduce 可以在一行代码中完成聚合操作,使代码更为简洁、易读。
    • 不使用 reduce 则需要明确的循环结构和累加操作,代码可能会显得更加冗长。
  2. 函数式风格:

    • reduce 方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 不变性:

    • reduce 是一个纯函数,不会修改流中的元素或产生任何副作用,符合函数式编程的不变性原则。
    • 传统的循环方式可能会引入变量的副作用,增加了出错的可能性。
  4. 链式调用:

    • reduce 是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。
    • 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。

 

collect: 将 Stream 转换成其他形式,如 List、Set 或 Map

让我们通过一个示例来展示使用 collect 和不使用 collect 的效果:

import java.util.*;
import java.util.stream.*;

public class CollectExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 使用 collect 将流元素收集到 List
        List<String> listWithCollect = words.stream().collect(Collectors.toList());
        System.out.println(listWithCollect);  // 输出: [apple, banana, cherry]

        // 不使用 collect,通过循环将流元素收集到 List
        List<String> listWithoutCollect = new ArrayList<>();
        for (String word : words) {
            listWithoutCollect.add(word);
        }
        System.out.println(listWithoutCollect);  // 输出: [apple, banana, cherry]
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 collect 可以在一行代码中将流元素收集到集合,使代码更为简洁、易读。
    • 不使用 collect 则需要明确的循环结构和集合操作,代码可能会显得更加冗长。
  2. 函数式风格:

    • collect 方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 灵活性:

    • collect 方法提供了高度的灵活性,我们可以使用不同的 Collector 实现来收集数据到不同类型的集合。
    • 传统的循环方式可能需要更多的代码来实现不同类型的集合的创建和填充。
  4. 链式调用:

    • collect 是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。
    • 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。

 

anyMatch, allMatch, noneMatch: 返回一个 boolean 值,表示 Stream 中的元素是否满足给定条件

下面是这三个方法的简单描述和示例:

anyMatch:

  • anyMatch 方法测试流中的任何元素是否满足指定的条件。
  • import java.util.*;
    import java.util.stream.*;
    
    public class MatchExample {
        public static void main(String[] args) {
            List<String> words = Arrays.asList("apple", "banana", "cherry");
    
            // 使用 anyMatch 测试是否有单词以 "a" 开头
            boolean anyMatch = words.stream().anyMatch(word -> word.startsWith("a"));
            System.out.println(anyMatch);  // 输出: true
        }
    }
    

allMatch:

  • allMatch 方法测试流中的所有元素是否满足指定的条件。
  • // 使用 allMatch 测试是否所有单词的长度大于 4
    boolean allMatch = words.stream().allMatch(word -> word.length() > 4);
    System.out.println(allMatch);  // 输出: false
    

noneMatch:

  • noneMatch 方法测试流中的所有元素是否都不满足指定的条件。
  • // 使用 noneMatch 测试是否没有单词以 "z" 开头
    boolean noneMatch = words.stream().noneMatch(word -> word.startsWith("z"));
    System.out.println(noneMatch);  // 输出: true
    

优点对比(以 anyMatch 为例,与传统循环对比):

  1. 代码简洁性:

    • 使用 anyMatch 可以在一行代码中完成条件测试,使代码更为简洁、易读。
    • 传统的循环方式可能需要明确的循环结构和条件判断,代码可能会显得更加冗长。
  2. 函数式风格:

    • anyMatchallMatchnoneMatch 方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 链式调用:

    • 这些方法可以与其他流操作链式调用,提供了高度的灵活性和可组合性。
    • 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。

 

 

 

findFirst, findAny: 返回 Stream 中的第一个或任意元素

findFirstfindAny 是 Java 8 Stream API 中的终端操作,它们用于从流中检索元素。这两个方法返回一个 Optional 对象,该对象可能包含找到的元素,或者如果流为空或没有元素满足条件,则为 Optional.empty

findFirst:

  • findFirst 方法返回流中的第一个元素。它常用于有序流,其中元素的顺序有意义。
  • import java.util.*;
    import java.util.stream.*;
    
    public class FindExample {
        public static void main(String[] args) {
            List<String> words = Arrays.asList("apple", "banana", "cherry");
    
            // 使用 findFirst 获取第一个单词
            Optional<String> firstWord = words.stream().findFirst();
            firstWord.ifPresent(System.out::println);  // 输出: apple
        }
    }
    

findAny:

  • findAny 方法返回流中的任何元素。在并行流处理时,它可能会返回任何合适的元素,使其对于并行处理更为友好。
  • // 使用 findAny 获取任何一个单词
    Optional<String> anyWord = words.stream().findAny();
    anyWord.ifPresent(System.out::println);  // 输出可能是: apple 或 banana 或 cherry
    

优点对比:

  1. 代码简洁性:

    • 使用 findFirstfindAny 可以在一行代码中获取流中的元素,使代码更为简洁、易读。
    • 不使用这些方法可能需要明确的循环结构和条件判断,代码可能会显得更加冗长。
  2. 函数式风格:

    • findFirstfindAny 方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 并行友好:

    • findAny 是并行友好的,它可以在并行流处理时提供更好的性能。
    • findFirst 在并行流中可能会受到顺序保证的限制,性能可能不如 findAny
  4. Optional 处理:

    • findFirstfindAny 返回一个 Optional 对象,它提供了一种更安全、更现代的方式来处理可能的 null 值。
    • 传统的方式可能需要手动检查 null 值,增加了出错的可能性。

 

 

count: 返回 Stream 中的元素数量

让我们通过一个示例来展示使用 count 和不使用 count 的效果:

import java.util.*;
import java.util.stream.*;

public class CountExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry");

        // 使用 count 计算单词数量
        long countWithCount = words.stream().count();
        System.out.println(countWithCount);  // 输出: 3

        // 不使用 count,通过循环计算单词数量
        int countWithoutCount = 0;
        for (String word : words) {
            countWithoutCount++;
        }
        System.out.println(countWithoutCount);  // 输出: 3
    }
}

优点对比:

  1. 代码简洁性:

    • 使用 count 可以在一行代码中完成元素计数,使代码更为简洁、易读。
    • 不使用 count 则需要明确的循环结构和计数变量,代码可能会显得更加冗长。
  2. 函数式风格:

    • count 方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。
    • 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
  3. 链式调用:

    • count 是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。
    • 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。

 

数值流 (Numeric Streams)

IntStream, LongStream, DoubleStream: 为基本数据类型提供的特殊 Stream,可以避免装箱和拆箱操作。

下面是它们的主要用途和实际应用场景:

  1. 处理基本数据类型:

    • 当需要处理的数据是基本数据类型(int, long, double)时,使用这些特殊流可以提高效率和性能。
  2. 数值范围生成:

    • IntStream, LongStream 提供了 rangerangeClosed 方法来生成数值范围内的元素,可以用于迭代或循环的替代。
// 生成 1 到 10 的整数序列
IntStream.range(1, 11).forEach(System.out::println);

数值统计:

  • 这些特殊流提供了如 sum, average, max, min 等方法来进行基本的数值统计。
IntStream numbers = IntStream.of(1, 2, 3, 4, 5);
System.out.println(numbers.sum());  // 输出: 15

映射和过滤:

  • 与常规流一样,这些特殊流也提供了 map, filter 等方法来进行元素的映射和过滤。
IntStream numbers = IntStream.of(1, 2, 3, 4, 5);
IntStream evenNumbers = numbers.filter(n -> n % 2 == 0);
evenNumbers.forEach(System.out::println);  // 输出: 2 4

 并行处理:

  • 通过 parallel 方法,这些特殊流可以进行并行处理,以提高大数据集处理的效率。
LongStream.rangeClosed(1, 1000000)
          .parallel()
          .sum();

实际应用场景:

  • 在实际应用中,当我们需要处理大量的基本数据类型时,使用 IntStream, LongStream, DoubleStream 可以提高程序的性能和效率。例如,在数值计算、数据分析、统计和科学计算等领域,这些特殊流的使用非常普遍。

总的来说,IntStream, LongStream, 和 DoubleStream 提供了一种高效、简洁和函数式的方式来处理基本数据类型的集合,使得我们能够以更高的性能和更少的代码来完成相关的任务。

 

 

并行流 (Parallel Streams)

parallel: 将一个顺序流转换为并行流,可以使得操作在多个线程上并发执行,提高执行效率。

import java.util.*;
import java.util.stream.*;

public class ParallelExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用 parallel 创建一个并行流,并计算数字的总和
        long startTime = System.currentTimeMillis();
        long sumWithParallel = numbers.stream().parallel().mapToInt(Integer::intValue).sum();
        long endTime = System.currentTimeMillis();
        System.out.println("Sum with parallel: " + sumWithParallel + ", Time taken: " + (endTime - startTime) + " ms");

        // 不使用 parallel,创建一个顺序流,并计算数字的总和
        startTime = System.currentTimeMillis();
        long sumWithoutParallel = numbers.stream().mapToInt(Integer::intValue).sum();
        endTime = System.currentTimeMillis();
        System.out.println("Sum without parallel: " + sumWithoutParallel + ", Time taken: " + (endTime - startTime) + " ms");
    }
}

优点对比:

  1. 性能提升:

    • 使用 parallel 在多核处理器环境下通常可以显著提高代码的执行效率,因为它允许多个线程同时处理数据。
    • 不使用 parallel 则只能在单个线程上顺序处理数据,效率可能较低。
  2. 简洁性:

    • 使用 parallel 可以简单地将顺序流转换为并行流,无需额外的代码或线程管理。
    • 不使用 parallel 则可能需要手动创建和管理线程,代码可能会显得更为复杂。
  3. 线程安全:

    • 使用 parallel 时,需要注意线程安全问题,确保操作是线程安全的,以避免数据竞争或不一致的结果。
    • 不使用 parallel 则无需考虑线程安全问题,因为所有操作都在单个线程上执行。
  4. 任务分解:

    • parallel 会自动将任务分解为多个小任务,并在多个线程上并行执行。
    • 不使用 parallel 则所有任务都在单个线程上顺序执行,无法利用多核处理器的优势。
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值