本文主要介绍Java 8
的 Stream
的简单使用
简介 流与集合的区别 计算的时机
是否全部载入内存
能否添加或删除元素
类似于
集合
是
能
DVD
流
否,按需计算
不能
网络流媒体
消费一次 流只能遍历一次
,遍历后即被消费,类似于网络流
相关代码托管在java8_demo
1 2 3 4 5 List<String> strs = Arrays.asList("zhong" , "ming" , "mao" ); Stream<String> stream = strs.stream(); stream.forEach(s -> System.out.println(s)); stream.forEach(System.out::println);
迭代方式
迭代方式
编程方式
并行
目的
集合
外部迭代
自行选择数据表示
自行实现并行
以特定时间/空间复杂度存储和访问元素
流
内部迭代
声明式编程
几乎免费的并行
计算
流操作 流操作类似于流水线
操作
中间操作
返回一个流(Stream
)的操作
中间操作不会执行任何处理
,直到触发了一个终端操作
中间操作一般是可以进行合并
的,这会在终端操作进行处理
常用中间操作
操作
出参
入参
函数描述符(入参)
filter
Stream<T>
Predicate<T>
T -> boolean
distinct
Stream<T>
skip
Stream<T>
long
limit
Stream<T>
long
map
Stream<R>
Function<T,R>
T -> R
flatMap
Stream<R>
Function<T,Stream<R>>
T -> Stream<R>
sorted
Stream<T>
Comparator<T>
(T,T) -> int
终端操作
关闭流
的操作
从流的流水线生成结果
(List、Integer、void等)
常用终端操作
操作
出参
入参
函数描述符(入参)
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>
count
long
reduce
Optional<T>
BinaryOperator<T>
(T,T) -> T
筛选与切片 谓词筛选 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Data @AllArgsConstructor @NoArgsConstructor public class User { public enum TYPE { OLD, YOUNG; } private String name; private int age; private boolean isStudent; public User (int age, boolean isStudent) { this .age = age; this .isStudent = isStudent; } }
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void filterCountTest () { List<User> users = Arrays.asList(new User (10 , true ), new User (20 , true ), new User (30 , false ), new User (40 , false ), new User (50 , false ), new User (60 , false ), new User (70 , false )); assertEquals(2 , users.stream() .filter(User::isStudent) .count()); }
筛选各异元素 1 2 3 4 5 6 7 8 @Test public void filterDistinctTest () { List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 3 , 2 , 1 ); assertEquals(2 , numbers.stream() .filter(n -> n % 2 == 0 ) .distinct() .count()); }
截断流 1 2 3 4 5 6 7 8 @Test public void filterLimitTest () { Predicate<User> isStudent = User::isStudent; assertEquals(3 , users.stream() .filter(isStudent.negate()) .limit(3 ) .count()); }
跳过N个元素 1 2 3 4 5 6 7 8 @Test public void filterSkipTest () { Predicate<User> isStudent = User::isStudent; assertEquals(4 , users.stream() .filter(isStudent.negate()) .skip(1 ) .count()); }
映射 map 1 2 3 4 5 6 7 8 @Test public void mapTest () { List<String> words = Arrays.asList("zhong" , "ming" , "mao" ); assertEquals(3 , words.stream() .map(String::length) .distinct() .count()); }
flatMap 1 2 3 4 5 6 7 8 9 10 11 12 @Test public void flatMapTest () { List<String> words = Arrays.asList("zhong" , "ming" , "mao" ); Stream<String[]> stream = words.stream() .map(s -> s.split("" )); assertEquals(8 , stream.flatMap(Arrays::stream) .distinct() .collect(toList()) .size()); }
flatMap 从定义上理解有点晦涩,做简单解释
flatMap
方法定义可简单理解为Stream<R> (Function<T, Stream<R>> mapper)
,mapper
的函数描述符简单理解为T -> Stream<R>
Lambda表达式Arrays::stream
的签名为T[] -> Stream<T>
stream
为Stream<String[]>
,元素类型为String[]
,通过Arrays::stream
会变成String[] -> Stream<String>
,依据flatMap
方法的定义,将返回Stream<String>
,流被扁平化
下面是 flatmap
的 另一个实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 List<Integer> numbers1 = Arrays.asList(1 , 2 , 3 ); List<Integer> numbers2 = Arrays.asList(3 , 4 ); Stream<Stream<Integer[]>> mapStream = numbers1.stream() .map(i -> numbers2.stream() .map(j -> new Integer []{i, j}) ); Stream<Integer[]> flatMapStream = numbers1.stream() .flatMap(i -> numbers2.stream() .map(j -> new Integer []{i, j}) ); assertEquals(2 , numbers1.stream() .flatMap(i -> numbers2.stream() .map(j -> new Integer []{i, j})) .filter(ints -> (ints[0 ] + ints[1 ]) % 3 == 0 ) .count());
查找与匹配 查找 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test(expected = IllegalArgumentException.class) public void findTest () { List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 ); Optional<Integer> first = numbers.stream() .map(x -> x * x + 2 ) .filter(x -> x % 3 == 0 ) .findFirst(); first.orElseThrow(() -> new RuntimeException ("error" )); Optional<Integer> any = numbers.stream() .map(x -> x * x) .filter(x -> x % 7 == 0 ) .findAny(); any.orElseThrow(() -> new IllegalArgumentException ("error" )); }
匹配 1 2 3 4 5 6 7 8 9 10 @Test public void matchTest () { List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertFalse(numbers.stream() .anyMatch(x -> x > 10 )); assertFalse(numbers.stream() .noneMatch(x -> x < 10 )); assertTrue(numbers.stream() .allMatch(x -> x < 10 )); }
归约 求和 1 2 3 4 5 6 7 8 9 10 11 12 13 @Test(expected = RuntimeException.class) public void sumTest () { List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(Integer.valueOf(15 ), numbers.stream() .reduce(0 , (x, y) -> x + y)); assertEquals(Integer.valueOf(15 ), numbers.stream().reduce(0 , Integer::sum)); numbers.clear(); Optional<Integer> sum = numbers.stream() .reduce((x, y) -> x + y); sum.orElseThrow(() -> new RuntimeException ("no init value" )); }
最大值和最小值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void maxMinTest () { List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(Integer.valueOf(5 ), numbers.stream().reduce((x, y) -> Integer.max(x, y)).get()); assertEquals(Integer.valueOf(5 ), numbers.stream() .reduce(Integer::max) .get()); assertEquals(Integer.valueOf(1 ), numbers.stream().reduce(Integer::min).get()); assertEquals(Integer.valueOf(5 ), numbers.stream().max((x, y) -> x.compareTo(y)).get()); assertEquals(Integer.valueOf(5 ), numbers.stream() .max(Integer::compareTo) .get()); assertEquals(Integer.valueOf(1 ), numbers.stream().min(Integer::compareTo).get()); }
总数 1 2 3 4 5 6 7 8 9 10 @Test public void countTest () { List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(Integer.valueOf(5 ), numbers.stream() .map(integer -> 1 ) .reduce(0 , Integer::sum)); assertEquals(5 , numbers.stream().count()); }
数值流
前面涉及到Stream<String>
、Stream<Integer>
的都是对象流
,隐含装箱
和拆箱
成本
数值流
是对象流的原始类型特化
,如 IntStream
,没有装箱
和拆箱
成本
1 2 3 4 5 6 7 8 9 List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 ); Stream<Integer> objStream = numbers.stream(); Optional<Integer> reduce = objStream.reduce(Integer::max); assertEquals(Integer.valueOf(5 ), reduce.get()); IntStream intStream = numbers.stream().mapToInt(Integer::intValue);OptionalInt max = intStream.max();assertEquals(5 , max.getAsInt()); Stream<Integer> boxed = numbers.stream().mapToInt(Integer::intValue).boxed();
构建流 有限流 值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void buildFromValueTest () { Stream<String> stringStream = Stream.of("zhong" , "ming" , "mao" ); assertEquals(3 , stringStream.count()); Stream<Object> objectStream = Stream.empty(); assertEquals(0 , objectStream.count()); IntStream intStream = IntStream.of(1 , 2 , 3 ); assertEquals(3 , intStream.max().getAsInt()); }
数组 1 2 3 4 5 6 7 8 9 10 11 12 @Test public void buildFromArrayTest () { int [] intArray = {1 , 2 , 3 , 4 , 5 , 6 , 7 }; IntStream intStream = Arrays.stream(intArray); assertEquals(3 , intStream.filter(x -> x % 2 == 0 ).count()); Integer[] integerArray = {1 , 2 , 3 , 4 , 5 , 6 , 7 }; Stream<Integer> integerStream = Arrays.stream(integerArray); assertEquals(4 , integerStream.filter(x -> x % 2 == 1 ).count()); }
文件 1 2 3 4 5 6 7 8 9 10 @Test public void buildFromFileTest () throws IOException { String relativePath = "/tmp.txt" ; String absPath = this .getClass().getResource(relativePath).getPath(); Stream<String> stringStream = Files.lines(Paths.get(absPath), Charset.defaultCharset()); assertEquals(9 , stringStream.flatMap(s -> Arrays.stream(s.split("\\s+" ))) .distinct() .count()); }
range + rangeClosed 1 2 3 4 5 6 7 8 @Test public void buildFromRangeTest () { IntStream intStream = IntStream.range(0 , 10 ); assertEquals(5 , intStream.filter(value -> value % 2 == 0 ).count()); intStream = IntStream.rangeClosed(0 , 10 ); assertEquals(6 , intStream.filter(value -> value % 2 == 0 ).count()); }
无限流 iterate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void buildFromIterateTest () { Stream<Integer> integerStream = Stream.iterate(0 , x -> x + 2 ); assertEquals(20 , integerStream.limit(5 ).mapToInt(Integer::intValue).sum()); IntStream intStream = IntStream.iterate(0 , x -> x + 2 ); assertEquals(20 , intStream.limit(5 ).sum()); Stream<int []> intArrayStream = Stream.iterate(new int []{0 , 1 }, fibArray -> new int []{fibArray[1 ], fibArray[0 ] + fibArray[1 ]}); assertEquals(5 , intArrayStream.mapToInt(fib -> fib[1 ]).limit(5 ).max().getAsInt()); }
generate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Test public void buildFromGenerateTest () { Stream<Integer> integerStream = Stream.generate(() -> (int ) (Math.random() * 1000 )); IntStream intStream = IntStream.generate(() -> (int ) (Math.random() * 1000 )); integerStream = Stream.generate(new Supplier <Integer>() { private int pre = 0 ; private int cur = 1 ; @Override public Integer get () { int next = pre + cur; pre = cur; cur = next; return pre; } }); assertEquals(5 , integerStream.mapToInt(Integer::intValue).limit(5 ).max().getAsInt()); intStream = IntStream.generate(new IntSupplier () { private int pre = 0 ; private int cur = 1 ; @Override public int getAsInt () { int next = pre + cur; pre = cur; cur = next; return pre; } }); assertEquals(5 , intStream.limit(5 ).max().getAsInt()); }
收集 收集器
(Collector
)会对流中的元素
应用一个转换函数
,并将结果累积
在一个数据结构中
汇总 求和 1 2 3 4 5 6 7 8 @Test public void sumTest () { List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(15 , integerList.stream() .collect(Collectors.summingInt(Integer::intValue)) .intValue()); }
<R, A> R collect(Collector<? super T, A, R> collector)
<T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper)
总数 1 2 3 4 5 6 7 8 @Test public void countTest () { List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(3 , integerList.stream().filter(n -> n % 2 == 1 ) .collect(Collectors.counting()) .intValue()); }
最大值最小值 1 2 3 4 5 6 7 8 9 10 11 12 @Test public void maxMinTest () { List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(Integer.valueOf(5 ), integerList.stream().filter(n -> n % 2 == 1 ) .collect(Collectors.maxBy(Comparator.comparingInt(Integer::intValue))) .get()); assertEquals(Integer.valueOf(1 ), integerList.stream().filter(n -> n % 2 == 1 ) .collect(Collectors.minBy(Comparator.comparing(Integer::intValue))) .get()); }
平均数 1 2 3 4 5 6 7 @Test public void avgTest () { List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(Double.valueOf("3.0" ), integerList.stream() .collect(Collectors.averagingInt(Integer::intValue))); }
统计数 1 2 3 4 5 6 7 8 9 10 11 @Test public void statisticsTest () { List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 5 ); IntSummaryStatistics statistics = integerList.stream() .collect(Collectors.summarizingInt(Integer::intValue)); assertEquals(5 , statistics.getCount()); assertEquals(5 , statistics.getMax()); assertEquals(1 , statistics.getMin()); assertEquals(15 , statistics.getSum()); assertEquals(3 , (int ) statistics.getAverage()); }
连接字符串 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void joinStrTest () { List<String> stringList = Arrays.asList("zhong" , "ming" , "mao" ); String delimiter = "," ; String expectedStr = "" ; for (int i = 0 , len = stringList.size(); i < len; i++) { expectedStr += stringList.get(i); if (i != len - 1 ) { expectedStr += delimiter; } } assertEquals(expectedStr, stringList.stream() .collect(Collectors.joining("," ))); }
广义归约 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 5 ); assertEquals(Integer.valueOf(15 ), integerList.stream() .collect(Collectors.reducing(Integer::sum)) .get()); assertEquals(Integer.valueOf(25 ), integerList.stream() .collect(Collectors.reducing(10 , Integer::sum))); assertEquals(Integer.valueOf(50 ), integerList.stream() .collect(Collectors.reducing(20 , n -> n * 2 , Integer::sum)));
分组 单级分组 1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void singleGroupTest () { List<User> users = Arrays.asList(new User ("a" , 10 , true ), new User ("b" , 20 , true ), new User ("c" , 30 , false ), new User ("d" , 40 , false ), new User ("e" , 50 , false ), new User ("f" , 60 , false ), new User ("g" , 70 , false )); Map<Boolean, List<User>> listMap = users.stream() .collect(Collectors.groupingBy(User::isStudent)); assertEquals(2 , listMap.size()); assertEquals(5 , listMap.get(false ).size()); assertEquals(2 , listMap.get(true ).size()); }
多级分组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void multiGroupTest () { List<User> users = Arrays.asList(new User ("a" , 10 , true ), new User ("b" , 20 , true ), new User ("c" , 30 , false ), new User ("d" , 40 , false ), new User ("e" , 50 , false ), new User ("f" , 60 , false ), new User ("g" , 70 , false )); Map<User.TYPE, Map<Boolean, List<User>>> mapMap = users.stream().collect( Collectors.groupingBy(user -> user.getAge() > 50 ? User.TYPE.OLD : User.TYPE.YOUNG, Collectors.groupingBy(User::isStudent))); assertEquals(2 , mapMap.size()); assertEquals(1 , mapMap.get(User.TYPE.OLD).size()); assertEquals(2 , mapMap.get(User.TYPE.YOUNG).size()); assertFalse(mapMap.get(User.TYPE.OLD).containsKey(Boolean.TRUE)); assertEquals(2 , mapMap.get(User.TYPE.OLD).get(Boolean.FALSE).size()); assertEquals(2 , mapMap.get(User.TYPE.YOUNG).get(Boolean.TRUE).size()); assertEquals(3 , mapMap.get(User.TYPE.YOUNG).get(Boolean.FALSE).size()); }
按子组收集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void groupCollectTest () { Map<User.TYPE, Long> typeLongMap = users.stream().collect( Collectors.groupingBy(user -> user.getAge() > 50 ? User.TYPE.OLD : User.TYPE.YOUNG, Collectors.counting())); assertEquals(2 , typeLongMap.size()); assertEquals(2 , typeLongMap.get(User.TYPE.OLD).intValue()); assertEquals(5 , typeLongMap.get(User.TYPE.YOUNG).intValue()); Map<User.TYPE, Double> typeDoubleMap = users.stream().collect( Collectors.groupingBy(user -> user.getAge() > 50 ? User.TYPE.OLD : User.TYPE.YOUNG, Collectors.averagingInt(User::getAge))); assertEquals(2 , typeDoubleMap.size()); assertEquals(1 , typeDoubleMap.get(User.TYPE.OLD).compareTo(typeDoubleMap.get(User.TYPE.YOUNG))); }
分区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void partitioningTest () { Map<Boolean, List<User>> listMap = users.stream().collect( Collectors.partitioningBy(User::isStudent)); assertEquals(2 , listMap.size()); assertEquals(2 , listMap.get(Boolean.TRUE).size()); assertEquals(5 , listMap.get(Boolean.FALSE).size()); Map<Boolean, Map<Boolean, List<User>>> map = users.stream().collect( Collectors.partitioningBy(user -> user.getAge() > 50 ? true : false , Collectors.partitioningBy(User::isStudent))); assertEquals(2 , map.size()); assertEquals(2 , map.get(Boolean.TRUE).size()); assertEquals(2 , map.get(Boolean.FALSE).size()); assertEquals(2 , map.get(Boolean.FALSE).get(Boolean.TRUE).size()); assertEquals(3 , map.get(Boolean.FALSE).get(Boolean.FALSE).size()); }
收集器 接口定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface Collector <T, A, R> { Supplier<A> supplier () ; BiConsumer<A, T> accumulator () ; BinaryOperator<A> combiner () ; Function<A, R> finisher () ; Set<Characteristics> characteristics () ; enum Characteristics { CONCURRENT, UNORDERED, IDENTITY_FINISH } }
自定义收集器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class CustomCollector <T> implements Collector <T, List<T>, Set<T>> { @Override public Supplier<List<T>> supplier () { return ArrayList::new ; } @Override public BiConsumer<List<T>, T> accumulator () { return List::add; } @Override public BinaryOperator<List<T>> combiner () { return (list1, list2) -> { list1.addAll(list2); return list1; }; } @Override public Function<List<T>, Set<T>> finisher () { return list -> { Set<T> finalSet = new HashSet <>(); finalSet.addAll(list); return finalSet; }; } @Override public Set<Characteristics> characteristics () { return Collections.unmodifiableSet(EnumSet.of(Characteristics.CONCURRENT)); } }
1 2 3 4 5 6 @Test public void customCollectorTest () { List<Integer> integerList = Arrays.asList(1 , 2 , 3 , 4 , 3 , 2 , 1 ); Set<Integer> set = integerList.stream().collect(new CustomCollector <>()); assertEquals(4 , set.size()); }
并行流 这里仅简单介绍并行流的使用
,平时一般不使用(因为数据量往往不够大,发挥并行优势有比较多的限制条件,顺序流
基本能应付日常开发的需求)
简单使用 1 2 3 4 5 6 7 8 9 public static long sum (int m) { return Stream.iterate(1 , n -> n + 1 ).limit(m) .parallel() .filter(n -> n % 2 == 0 ) .sequential() .map(integer -> integer * 3 ) .parallel() .reduce(0 , Integer::sum); }
性能比较 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public static int iterativeSum (int n) { int result = 0 ; for (int i = 0 ; i < n; i++) { result += i; } return result; } public static int sequentialSum (int m) { return Stream.iterate(0 , n -> n + 1 ).limit(m).reduce(0 , Integer::sum); } public static int iterativeParallelSum (int m) { return Stream.iterate(0 , n -> n + 1 ).limit(m).parallel().reduce(0 , Integer::sum); } public static int rangesequentialSum (int m) { return IntStream.range(0 , m).reduce(0 , Integer::sum); } public static int rangeParallelSum (int m) { return IntStream.range(0 , m).parallel().reduce(0 , Integer::sum); }