9102年了,你还在用for循环操做集合?

本文首发于cdream的我的博客,点击得到更好的阅读体验!html

欢迎转载,转载请注明出处。java

前段时间公司书架多了一本《Java8 实战》,毕竟久闻lambda的大名,因而借来一阅。这一看,简直是惊为天人啊,lambda,stream,java8里简直是满脑子骚操做,看个人一愣一愣的。我甚至是第一次感受到了什么叫<font color="purple">优雅</font>。python

image-20190224175258572

本文主要介绍java8中的流处理,看看java8是怎么愉快的玩耍集合的,让咱们来一块儿感觉java8的魅力吧!git

<!--more-->github

我就随便举个例子,看看Stream有多优雅。sql

// 对苹果按颜色汇总并绩数量
Map<String, Long> appleCount = apples.stream()
    .collect(groupingBy(Apple::getColor, counting()));
// 过滤掉颜色为黑色的苹果,并汇总好苹果的总金额
Double sum = apples.stream()
    .filter(i->"black".equals(i.getColor()))
    .collect(toList);

1、lambda表达式

虽然本文重点是stream,可是stream中须要传递lambda表达式,因此简单介绍一下lambda表达式。lambda表达式其实就是匿名函数(anonymous function),是指一类无需定义标识符的函数或子程序。数组

java中匿名函数的表现形式,只留下入参和方法体中的内容app

// 普通函数
public void run(String s){
    System.out.print(s+"哈哈");
}
// 我不要名字啦!!!
(s)->System.out.print(s+"哈哈")

诶,过去咱们都用对象调方法的,你弄这个没名的东西啥时候用啊?框架

java中咱们经过函数式接口来使用这种匿名函数。dom

<div class="info note">函数式接口<br>1.java中只包含一个未实现方法的接口。其中能够有与Object中同名的方法和默认方法(java8中接口方法能够有默认实现)。<br>2.java中函数式接口使用@FunctionalInterface进行注解。Runnable、Comparator都是函数式接口。<br>3.java.util.function包下为咱们提供不少经常使用的函数式接口,例如Function<T,R>等。</div>

用法举例:

// 实现Runnable中的run方法,替代匿名内部类。
Runnable r = ()->System.out.print("哈哈");
// 做为参数传递。
new Thread(()-> System.out.println("haha")).start();

ArrayList<Apple> list = new ArrayList<>();
list.forEach(i-> System.out.println(i.getWeight()));


// 简化策略模式
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
    List<Apple>  apples = new ArrayList<>();
    for(Apple apple : inventory){
        if(p.test(apple)){
            apples.add(apple);
        }
    }
    return apples;
}
public class BigApple implement ApplePredicate{
    @Override
    public boolean test(Apple a){
        if(a.getWeight>10){
            return a
        }
    }
}
// 这是个简单的策略模式,根据用户的须要,建立不一样的接口ApplePredicate实现类,调用时传入不一样的实现类就能够,但问题是若是需求过多,建立的实现类也会不少,过于臃肿不方便管理。
xx.filterApple(inventory,new BigApple);
// 使用lambda表达式,不在须要建立BigApple类
xx.filterApple(inventory,i->(i.getWeight>10));

使用lambda表达式能够简化大量的模板代码,而且能够向方法直接传递代码。

总之

方法出参入参来自函数式接口
//入参s,返回void
(s)->System.out.println(s);
//入参空,返回void
()->System.out.print("haha");
//入参i,返回i+1
i->i+1
//后面写代码块
apple->{if(apple.getWeiht>5) return "BIG";
        else return "small";
       }

好了,很少啰嗦了,若是感兴趣推荐下面的文章或《Java8实战》的前三章。

<div class="info note">1.<a href="https://www.zhihu.com/question/20125256/answer/324121308">Lambda表达式有何用处?如何使用?</a><br>2.<a href="http://product.dangdang.com/23952002.html">java8实战</a></div>

2、Stream

流是什么?

Java API的新成员,它容许你使用声明式方式处理数据集合(相似sql,经过查询语句表达,而不是临时编写一个实现)。

若是有人说lambda表达式不易于理解,那还勉强能够接受(其实过于复杂的lambda缺失很差阅读,但一般lambda不会作太复杂的实现),但流真的很是的易懂易用。这个语法糖真的是甜死了。

<div class="warning note">注意事项:<br>1.流只能使用一次,遍历结束就表明这个流被消耗掉了<br>2.流对集合的操做属于内部迭代,是流帮助咱们操做,而不是外部迭代<br>3.流操做包含:数据源,中间操做链,终端操做三个部分。</div>

基础流操做

List<Double> collect = list.stream()
        // 过滤掉黑色的苹果
        .filter(i -> "black".equals(i.getColor()))
        // 让苹果按照重量个价格排序
        .sorted(Comparator.comparing(Apple::getWeight)
                .thenComparing(i->i.getPrice()))
        // 筛选掉重复的数据
        .distinct()
        // 只要苹果的价格
        .map(Apple::getPrice)
        // 只留下前两条数据
        .limit(2)
        // 以集合的形式返回
        .collect(toList());
// 循环打印列表中元素
list.forEach(i->System.out.print(i));

Apple::getPrince<=>i -> i.getPrince()能够看作是仅涉及单一方法的语法糖,效果与lambda表达式相同,但可读性更好。

同理

下面列表为常见操做

中间

操做 类型 做用 函数描述 函数
filter 中间 过滤 T -> boolean Predicate<T>
sorted 中间 排序 (T,T)->int Comparator<T>
map 中间 映射 T->R Function<T,R>
limit 中间 截断
distinct 中间 去重,根据equals方法
skip 中间 跳过前n个元素

终端

操做 类型 做用
forEach 终端 消费流中的每一个元素,使用lambda进行操做
count 终端 返回元素个数,long
collect 终端 将流归约成一个集合,如List,Map甚至是Integer

筛选与切片

List<String> strings = Arrays.asList("Hello", "World");
List<String> collect1 = strings.stream()
    // String映射成String[]
    .map(i -> i.split(""))
    // Arrays::Stream 数据数组,返回一个流String[]->Stream<String>
    // flatMap各数组并不分别映射成一个流,而是映射成流的内容 Stream<String>->Stream
    .flatMap(Arrays::stream)
    .collect(toList());
System.out.println(collect);
----->输出 [H, e, l, l, o, W, o, r, l, d]

归约操做reduce

List<Integer> integers = Arrays.asList(12, 3, 45, 3, 2,-1);
// 有初始值的叠加操做
Integer reduce = integers.stream().reduce(3, (i, j) -> i + j);
Integer reduce2 = integers.stream().reduce(5, (x, y) -> x < y ? x : y);
// 无初始值的叠加操做
Optional<Integer> reduce1 = integers.stream().reduce((i, j) -> i + j);
// 无初始值的最大值
Optional<Integer> reduce4 = integers.stream().reduce(Integer::min);
// 无初始值的最大值
Optional<Integer> reduce5 = integers.stream().reduce(Integer::max);
// 求和
Optional<Integer> reduce6 = integers.stream().reduce(Integer::sum);

reduce作的事情是取两个数进行操做,结果返回取下一个数操做,以次类推。

Optional是java8引入的新类,避免形成空指针异常,在集合为空时,结果会包在Optional中,能够用isPresent()方法来判断是否为空值。

无初始值的状况下可能为空,故返回Optional

中间

操做 类型 做用 函数描述 函数
flatmap 中间 使经过的流返回内容 T -> boolean Predicate<T>

终端

操做 类型 做用
anyMatch 终端 返回boolean,判断是否有符合条件内容
noneMatch 终端 返回boolean,判断是否无符合条件内容
allMatch 终端 返回boolean,判断是全为符合条件内容
findAny 终端 Optional<T>,随机找一个元素返回
findFirst 终端 Optional<T>,返回第一个元素
reduce 终端 Optional<T> (T,T)->T 归约操做

数值流

包装类型的各类操做都会有拆箱操做和装箱操做,严重影响性能。因此Java8为咱们提供了原始数值流。

// 数值流求平均值
OptionalDouble average = apples.stream()
        .mapToDouble(Apple::getPrice)
        .average();
// 数值流求和
OptionalDouble average = apples.stream()
    .mapToDouble(Apple::getPrice)
    .sum();
// 数值流求最大值,没有则返回2
double v = apples.stream()
    .mapToDouble(Apple::getPrice)
    .max().orElse(2);
// 生成随机数
IntStream s = IntStream.rangeClosed(1,100);

下面列表为常见数值流操做操做

中间

操做 类型 做用
rangeClosed(1,100) 中间 生成随机数(1,100]
range(1,100) 中间 生成随机数(1,100)
boxed() 中间 包装成通常流
mapToObj 中间 返回为对象流
mapToInt 中间 映射为数值流

终端,终端操做与List<Integer>通常流相似

构建流

  1. 值建立

    Stream<String> s = Stream.of("java","python");
  2. 数组建立

    int[]  i = {2,3,4,5};
    Stream<int> = Arrays.stream(i);
  3. 由文件生成,NIO API已经更新,以便利用Stream API

    Stream<String> s = Files.lines(Paths.get("data.txt"),Charset.defaultCharset());
  4. 由函数建立流:无限流

    // 迭代
    Stream.iterate(0,n->n+2)
    	.limit(10)
        .forEach(System.out::println);
    // 生成,须要传递实现Supplier<T>类型的Lambda提供的新值
    Stream.generate(Math.random)
        .limit(5)
        .forEach(System.out::println);

3、总结

至此,本文讲述了常见的流操做,目前排序、筛选、求和、归约等大多数操做咱们都能实现了。与过去相比,操做集合变的简单多了,代码也变的更加简练明了。

目前Vert.x,Spring新出的WebFlux都经过lambda表达式来简化代码,不久的未来,非阻塞式框架的大行其道时,lambda表达式必将变的更加剧要!

至于开篇见到的分组!!!下篇文章见~


参考资料:

  1. Java8 实战,Raoul-Gabriel Urma,Mario Fusco,Alan Mycroft
相关文章
相关标签/搜索