java8新特性

java8新特性 - stream流

一、流的创建方式

集合创建流

  • Stream stream() : 返回一个顺序流

  • Stream parallelStream() : 返回一个并行流

    new ArrayList<>().stream();
    

数组创建流

返回一个流重载形式,能够处理对应基本类型的数组

Arrays.stream(new int[]{1,2,3})

值创建流

public static Stream of(T… values) : 返回一个流

Stream<Integer> integerStream = Stream.of(1);
Stream<String> stringStream = Stream.of("1");

方法创建流 : 创建无限流

可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流

  • 迭代流 : public static Stream iterate(final T seed, final UnaryOperator f)

    //初值为1的无限等比数列
    Stream.iterate(1, n -> n * 2);
    
  • 生成流 : public static Stream generate(Supplier s)

    //无限随机数流
    Stream.generate(Math::random)
    
  • 使用IntStream、LongStream、DoubleStream的static方法创建有限流

    IntStream.of(new int[]{1, 2, 3});
    IntStream.range(1, 3);
    IntStream.rangeClosed(1, 3);
    
  • 使用随机数类的ints()方法创建无限数值流

    Random random = new Random();
    IntStream ints = random.ints();
    

文件中获取流

使用BufferedReader的lines方法从文件中获得行的流

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")));
Stream<String> lines = bufferedReader.lines();

其他类提供的创建流

  • BitSet数值流

     IntStream stream = new BitSet().stream();
    
  • Pattern 将字符串分隔成流

    Pattern pattern = compile(",");
    Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
    stringStream.forEach(System.out::println);
    
  • JarFile 读取jar文件流

    Stream<JarEntry> stream = new JarFile("").stream();
    

二、流的中间操作

distinct 唯一

distinct保证输出的流中包含唯一的元素,它是通过**Object.equals(Object)**来检查是否包含相同的元素。

List<String> l = Stream.of("a","b","c","b")
.distinct()
.collect(Collectors.toList());
System.out.println(l); //[a, b, c]

filter 过滤

filter返回的流中只包含满足断言(predicate)的数据。

下面的代码返回流中的偶数集合。

List<Integer> l = IntStream.range(1,10)
.filter( i -> i % 2 == 0)
.boxed()
.collect(Collectors.toList());
System.out.println(l); //[2, 4, 6, 8]

map 映射

map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。

下面的代码中将字符元素映射成它的哈希码(ASCII值)。

List<Integer> l = Stream.of('a','b','c')
.map( c -> c.hashCode())
.collect(Collectors.toList());
System.out.println(l); //[97, 98, 99]

flatmap 映射汇总

flatmap方法混合了map + flattern的功能,同时扩展flatMapToDouble、flatMapToInt、flatMapToLong提供了转换成特定流的方法。它将映射后的流的元素全部放入到一个新的流中。它的方法定义如下:

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

flatmap适用于多对多或者一对多的映射关系,mapper函数会将每一个元素转换成一个流对象,而flatMap方法返回的一个流包含所有mapper转换后的元素。

下面举个例子来详细说明:

给定一个列表{“aaa”,“bbb”,“ddd”,“eee”,“ccc”}。需要在控制台直接输出aaabbbdddeeeccc字样

采用map来做

 List<String> list = Arrays.asList("aaa", "bbb", "ddd", "eee", "ccc");
//这里采用了两次forEach循环进行输出,显然不太优雅
list.stream().map(x -> {
    List<Character> characterList = new ArrayList<>();
    char[] chars = x.toCharArray();
    for (char c : chars) {
        characterList.add(c);
    }
    return characterList.stream();
}).forEach(xStream -> xStream.forEach(System.out::print)); //aaabbbdddeeeccc

采用flatMap来做

 List<String> list = Arrays.asList("aaa", "bbb", "ddd", "eee", "ccc");
//采用flatMap来做  体会一下flatMap的魅力吧
list.stream().flatMap(x -> {
    List<Character> characterList = new ArrayList<>();
    char[] chars = x.toCharArray();
    for (char c : chars) {
        characterList.add(c);
    }
    return characterList.stream();
}).forEach(System.out::print); //aaabbbdddeeeccc

limit 截断

limit方法指定数量的元素的流。对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能

List<Integer> l = IntStream.range(1,100).limit(5)
.boxed()
.collect(Collectors.toList());
System.out.println(l);//[1, 2, 3, 4, 5]

peek 观察者

生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;这里所说的消费函数有点类似于钩子,每个元素被消费时都会执行这个钩子

String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
.peek(System.out::println) //a,b,c,d
.count();

sorted 排序

sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。

sorted(Comparator<? super T> comparator)可以指定排序的方式。

对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。

String[] arr = new String[]{"b_123","c+342","b#632","d_123"};
List<String> l  = Arrays.stream(arr)
    .sorted((s1,s2) -> {
        if (s1.charAt(0) == s2.charAt(0)) {
            return s1.substring(2).compareTo(s2.substring(2));
        } else {
            return s1.charAt(0) - s2.charAt(0);
        }
    })
    .collect(Collectors.toList());
System.out.println(l); //[b_123, b#632, c+342, d_123]

skip 跳过

skip返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流。

String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
    .skip(2)
    .forEach(System.out::println);// c d

三、流的终点操作

match 断言

public boolean 	allMatch(Predicate<? super T> predicate)		//所有都满足    true
public boolean 	anyMatch(Predicate<? super T> predicate)		//任意一个满足  true
public boolean 	noneMatch(Predicate<? super T> predicate)		//没有一个满足  true

这一组方法用来检查流中的元素是否满足断言。

allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true

anyMatch只有在任意一个元素满足断言时就返回true,否则flase,

noneMatch只有在所有的元素都不满足断言时才返回true,否则flase

System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0)); //true
System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0)); //true

cout 计数

count方法返回流中的元素的数量。

String[] arr = new String[]{"a","b","c","d"};
long count = Arrays.stream(arr).count();

手动实现

String[] arr = new String[]{"a","b","c","d"};
long count = Arrays.stream(arr).mapToLong(x->1L).sum();

collect 收集

collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。辅助类Collectors提供了很多的collector收集器,可以满足我们日常的需求,你也可以创建新的collector实现特定的需求。它是一个值得关注的类,你需要熟悉这些特定的收集器,如聚合类averagingInt、最大最小值maxBy minBy、计数counting、分组groupingBy、字符串连接joining、分区partitioningBy、汇总summarizingInt、化简reducing、转换toXXX等。

Collectors里常用搜集器介绍:

方法返回类型作用
toList()List把流中元素收集到List
List result = list.stream().collect(Collectors.toList());
toSet()Set把流中元素收集到Set
Set result = list.stream().collect(Collectors.toSet());
toCollection()Collection把流中元素收集到集合
Collection result = lsit.stream().collect(Collectors.toCollection(ArrayListL::new));
counting()Long计算流中元素的个数
long count = lsit.stream().collect(Collectors.counting());
summingInt()Integer对流中元素的整数属性求和
int total = lsit.stream().collect(Collectors.counting());
averagingIntDouble计算元素Integer属性的均值
double avg = lsit.stream().collect(Collectors.averagingInt(Student::getAge));
summarizingIntIntSummaryStatistics收集元素Integer属性的统计值
IntSummaryStatistics result = list.stream().collect(Collectors.summarizingInt(Student::getAge));
joiningStream连接流中的每个字符串
String str = list.stream().map(Student::getName).collect(Collectors.joining());
maxByOptional根据比较器选择最大值
Opetional max = list.stream().collect(Collectors.maxBy(comparingInt(Student::getAge)))
minByOptional根据比较器选择最小值
Optional min= list.stream().collect(Collectors.minBy(comparingInt(Student::getAge)));
reducing规约产生的类型从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值
int total = list.stream().collect(Collectors.reducing(0, Student::getAge, Integer::sum));
collectingAndThen转换函数返回的类型包裹另一个收集器,对其结果转换
int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingByMap<K, List>根据某属性值对流分组,属性为K,结果为V
Map<Integer, List> map = list.stream().collect(Collectors.groupingBy(Student::getStatus));
partitioningByMap<Boolean, List>根据true或false进行分区
Map<Boolean, List> map = list.stream().collect(Collectors.partitioningBy(Student::getPass));

find 返回

  • **findAny()**返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
  • **findFirst()**返回第一个元素,如果流为空,返回空的Optional。

forEach、forEachOrdered 遍历

forEach遍历流的每一个元素,执行指定的action。它是一个终点操作,和peek方法不同。这个方法不担保按照流的encounter order顺序执行,如果对于有序流按照它的encounter order顺序执行,你可以使用forEachOrdered方法。

Stream.of(1,2,3,4,5).forEach(System.out::println);
PS: 嵌套遍历(不推荐

如果要对两个集合进行遍历操作,可以将流嵌套,但是这种遍历的性能跟跟foreach嵌套一样,而且不能进行更复杂的操作,不推荐。

ArrayList<String> list = Lists.newArrayList("1", "2");
ArrayList<String> list2 = Lists.newArrayList("一", "二");
list.stream().forEach(str1->{
    list2.stream().forEach(str2->{
        System.out.println(str1+str2);
    });
});

max、min 最大最小值

max返回流中的最大值,

min返回流中的最小值。

ArrayList<Integer> list = Lists.newArrayList(3,5,2,1);
Integer max = list.stream().max(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
}).get();

reduce 归约

reduce是常用的一个方法,事实上很多操作都是基于它实现的。

它有几个重载方法:

方法描述
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值,返回 Optional
**reduce(**T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值,返回 T
**reduce(**U identity, BiFunction a, BinaryOperator combiner)可以将流中元素反复结合起来,得到一个值,返回 Optional

PS: BiaryOperator 函数式接口,也即Lambada表达式

reduce是很重要的一种变成思想。这里重点介绍一下。reduce的作用是把stream中的元素给组合起来。至于怎么组合起来:

  • 它需要我们首先提供一个起始种子,然后依照某种运算规则使其与stream的第一个元素发生关系产生一个新的种子,这个新的种子再紧接着与stream的第二个元素发生关系产生又一个新的种子,就这样依次递归执行,最后产生的结果就是reduce的最终产出,这就是reduce的算法最通俗的描述;

所以运用reduce我们可以做sum,min,max,average,所以这些我们称之为针对具体应用场景的reduce,这些常用的reduce,stream api已经为我们封装了对应的方法。

//求和 sum
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
// 没有起始值时返回为Optional类型
Optional<Integer> sumOptional = integers.stream().reduce(Integer::sum);
System.out.println(sumOptional.get()); //15

// 可以给一个起始种子值
Integer sumReduce = integers.stream().reduce(0, Integer::sum);
System.out.println(sumReduce); //15

//直接用sum方法
Integer sum = integers.stream().mapToInt(i -> i).sum();
System.out.println(sum); //15

前面两个方法比较简单,重点说说三个参数的reduce(U identity, BiFunction a, BinaryOperator combiner)

三个参数时是最难以理解的。 分析下它的三个参数:

  • identity: 一个初始化的值;这个初始化的值其类型是泛型U,与Reduce方法返回的类型一致;注意此时Stream中元素的类型是T,与U可以不一样也可以一样,这样的话操作空间就大了;不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;或者是String,又或者是一些集合类型ArrayList等;后面会说到这些用法。

  • accumulator: 其类型是BiFunction,输入是U与T两个类型的数据,而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,而输入的第二个参数类型与Stream中元素类型是一样的

  • combiner: 其类型是BinaryOperator,支持的是对U类型的对象进行操作

第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,它实际上是不生效的。

因此针对这个方法的分析需要分并行与非并行两个场景。

就是因为U和T不一样,所以给了我们更多的发挥。比如设U的类型是ArrayList,那么可以将Stream中所有元素添加到ArrayList中再返回了,如下示例:

ArrayList<String> result = Stream.of("aa", "ab", "c", "ad").reduce(new ArrayList<>(),
                (u, s) -> {
                    u.add(s);
                    return u;
                }, (strings, strings2) -> strings);
System.out.println(result); //[aa, ab, c, ad]

注意由于是非并行的,第三个参数实际上没有什么意义,可以指定r1或者r2为其返回值,甚至可以指定null为返回值。下面看看并行的情况:

当Stream是并行时,第三个参数就有意义了,它会将不同线程计算的结果调用combiner做汇总后返回。注意由于采用了并行计算,前两个参数与非并行时也有了差异! 看个例子:

Integer reduce = Stream.of(1, 2, 3).parallel().reduce(
                4,
               (integer, integer2) -> integer + integer2,
               (integer, integer2) -> integer + integer2);
       System.out.println(reduce); //18

输出:18

omg,结果竟然是18。显然串行的话结果是10;这个不太好理解,但是我下面写一个等价的方式,可以帮助很好的理解这个结果:

Optional<Integer> reduce = Stream.of(1, 2, 3).map(n -> n + 4).reduce((s1, s2) -> s1 + s2);
System.out.println(reduce.get()); //18

这种方式有助于理解并行三个参数时的场景,实际上就是第一步使用accumulator进行转换(它的两个输入参数一个是identity, 一个是序列中的每一个元素),由N个元素得到N个结果;第二步是使用combiner对第一步的N个结果做汇总。

好了,三个参数的reduce先介绍到这。下面继续看看reduce能为我们做什么?

//构造字符串流
List<String> strs = Arrays.asList("H", "E", "L", "L", "O");
// reduce
String concatReduce = strs.stream().reduce("", String::concat);
System.out.println(concatReduce); //HELLO
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
Integer minReduce = integerStream.reduce(Integer.MAX_VALUE, Integer::min);
System.out.println(minReduce); //1

toArray()

将流中的元素放入到一个数组中,默认为Object数组

他还有一个重载方法 A[] toArray(IntFunction<A[]> generator) 可以返回指定类型的数组

Object[] objects = Stream.of(1, 2, 3, 4, 5).toArray();
Integer[] integers = Stream.of(1, 2, 3, 4, 5).toArray(Integer[]::new);

concat 组合

concat(Stream a, Stream b)用来连接类型一样的两个流。

List<Integer> list1 = Arrays.asList(1,2,3);
List<Integer> list2 = Arrays.asList(4,3,2);
Stream.concat(list1.stream(),list2.stream()).forEach(System.out::print);
  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

啊森的代码园

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

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

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

打赏作者

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

抵扣说明:

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

余额充值