Java8-Stream流编程

什么是流

在java中,集合是一个非常基本的结构,几乎每个Java应用程序都会制造和处理集合。而对集合的操作中,有很多是类似于数据库操作的。在Java程序中,如果要处理集合中的数据,我们就需要一遍一遍的遍历集合,从而对集合中的数据进行处理。我们也知道,像这种标准的数据结构,类似于数据库中的数据,使用sql这种语法会很方便也很直观。在Java8之前没有专门针对集合的操作api,但在Java8之后,我们可以将集合序列化为Stream,使用Stream中的函数来对集合进行操作。

如何使用流
//Java8之前的写法
List<Dish> lowCaloricDishes = new ArrayList<>(); //有一个盘子列表
for(Dish d: menu){ //筛选出价格比较低的盘子
	if(d.getCalories() < 400){
		lowCaloricDishes.add(d);
	}
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
		public int compare(Dish d1, Dish d2){  //对价格比较低的盘子进行排序
			return Integer.compare(d1.getCalories(), d2.getCalories());
		}
});
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish d: lowCaloricDishes){ //盘子名称的列表
	lowCaloricDishesName.add(d.getName());
}

上面的写法,总共对盘子进行了两次的遍历,而且是单线程进行遍历的,在数据量比较大的时候,非常的占用内存和cpu时间。下面我们看看Java8的写法:

Java8的写法:

List<String> lowCaloricDishesName = menu.stream()
								.filter(d -> d.getCalories() < 400)
								.sorted(comparing(Dish::getCalories))
								.map(Dish::getName)
								.collect(toList());
//如果要使用多线程方式,直接将stream()改为parallelStream()即可
List<String> lowCaloricDishesName = menu.parallelStream()
								.filter(d -> d.getCalories() < 400)
								.sorted(comparing(Dishes::getCalories))
								.map(Dish::getName)
								.collect(toList());

可以看到,使用Java8语法我们可以非常简单的实现以上功能,而且可以简单的使用并行来处理该问题。

下面我们分别看看使用流的几种中间操作和终端操作。

筛选和切片
  1. 用谓词筛选
    //用谓词筛选流
    menu.stream()
            .filter(Dish::isVegetarian)
            .collect(toList())
            .forEach(System.out::println);
    
  2. 筛选各异的元素
    List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
    numbers.stream()
            .filter(i -> i % 2 == 0)
            .distinct()
            .forEach(System.out::println);
    
  3. 截断流
    //截断流
    menu.stream()
            .filter(d -> d.getCalories()>300)
            .limit(3)
            .collect(Collectors.toList())
            .forEach(System.out::println);
    
  4. 跳过元素(和limit互补)
    //跳过元素
    menu.stream()
             .filter(d -> d.getCalories()>300)
             .skip(3)
             .collect(toList())
             .forEach(System.out::println);
    
映射
  1. 对流中的每个元素应用函数
      menu.stream()
             .map(Dish::getName)
             .collect(toList())
             .forEach(System.out::println);
    
  2. 流的扁平化
    //要找到一串文字中的所有不重复的字符
    //错误示范1
    List<String> words = Arrays.asList("我是一个程序员", "我喜欢写程序");
    words.stream()
            .map(word -> word.split(""))
            .distinct()
            .collect(toList())
            .forEach(System.out::println);
    //此处输出:[Ljava.lang.String;@1e80bfe8
    //[Ljava.lang.String;@66a29884
    //错误示范2
    words.stream()
            .map(word -> word.split(""))
            .map(Arrays::stream)
            .distinct()
            .collect(toList())
            .forEach(System.out::println);
    //输出:java.util.stream.ReferencePipeline$Head@79fc0f2f
    //java.util.stream.ReferencePipeline$Head@50040f0c
    //正确示范
    words.stream()
            .map(word -> word.split(""))
            .flatMap(Arrays::stream)
            .distinct()
            .collect(toList())
            .forEach(System.out::print);
    //输出:我是一个程序员喜欢写
    
查找和匹配
  1. 检查谓词是否至少匹配一个元素
    //检查集合中是否至少匹配一个元素
    if (menu.stream().anyMatch(Dish::isVegetarian)) {
         System.out.println("这个菜单里面有蔬菜");
     }
    
  2. 检查谓词是否匹配所有元素
    boolean isHealthy = menu.stream()
            .allMatch(d -> d.getCalories() < 1000);
    System.out.println(isHealthy);
    
  3. 查找元素
    menu.stream()
        .filter(Dish::isVegetarian)
         .findAny()
         .ifPresent(System.out::println);
    
  4. 查找第一个元素
    List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
    someNumbers.stream()
            .map(x -> x * x)
            .filter(x -> x % 3 == 0)
            .findFirst()
            .ifPresent(System.out::println); // 9
    
规约
  1. 元素求和

    numbers.stream()
           .reduce((a, b) -> (a + b))
           .ifPresent(System.out::println);
    
  2. 最大值和最小值

    numbers.stream().reduce(Integer::max).ifPresent(System.out::println);
    numbers.stream().reduce(Integer::min).ifPresent(System.out::println);
    

    归约方法的优势与并行化

    相比于前面写的逐步迭代求和,使用reduce的好处在于,这里的迭代被内部迭代抽象掉了,这让内部实现得以选择并行执行reduce操作。而迭代式求和例子要更新共享变量sum,这不是那么容易并行化的。如果你加入了同步,很可能会发现线程竞争抵消了并行本应带来的性能提升!这种计算的并行化需要另一种办法:将输入分块,分块求和,最后再合并起来。但这样的话代码看起来就完全不一样了。现在重要的是要认识到,可变的累加器模式对于并行化来说是死路一条。你需要一种新的模式,这正是reduce所提供的。使用流来对所有的元素并行求和时,你的代码几乎不用修改: stream()换成了parallelStream()。
    但要并行执行这段代码也要付一定代价: 传递给reduce的Lambda不能更改状态(如实例变量),而且操作必须满足结合律才可以按任意顺序执行。

    流操作:有状态和无状态

    诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态(假设用户提供的Lambda或方法引用没有内部可变状态)。
    但诸如reduce、 sum、 max等操作需要内部状态来累积结果。在上面的情况下,内部状态很小。不管流中有多少元素要处理,内部状态都是有界的。
    相反,诸如sort或distinct等操作一开始都和filter和map差不多——都是接受一个流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知道先前的历史。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操作的存储要求是无界的。要是流比较大或是无限的,就可能会有问题(把质数流倒序会做什么呢?它应当返回最大的质数,但数学告诉我们它不存在)。我们把这些操作叫作有状态操作。

在这里插入图片描述

付诸实践
  1. 交易员和交易
    (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
    (2) 交易员都在哪些不同的城市工作过?
    (3) 查找所有来自于剑桥的交易员,并按姓名排序。
    (4) 返回所有交易员的姓名字符串,按字母顺序排序。
    (5) 有没有交易员是在米兰工作的?
    (6) 打印生活在剑桥的交易员的所有交易额。
    (7) 所有交易中,最高的交易额是多少?
    (8) 找到交易额最小的交易。
    
    //以下是你要处理的领域,一个Traders和Transactions的列表:
    Trader raoul = new Trader("Raoul", "Cambridge");
    Trader mario = new Trader("Mario","Milan");
    Trader alan = new Trader("Alan","Cambridge");
    Trader brian = new Trader("Brian","Cambridge");
    List<Transaction> transactions = Arrays.asList(
    	new Transaction(brian, 2011, 300),
    	new Transaction(raoul, 2012, 1000),
    	new Transaction(raoul, 2011, 400),
    	new Transaction(mario, 2012, 710),
    	new Transaction(mario, 2012, 700),
    	new Transaction(alan, 2012, 950)
    );
    //Trader和Transaction类的定义如下:
    @Data
    public class Trader{
    	private final String name;
    	private final String city;
    }
    @Data
    public class Transaction{
    	private final Trader trader;
    	private final int year;
    	private final int value;
    }
    
  2. 解答
    package top.hengshare.interviewer.java8.stream;
    
    import lombok.Data;
    
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.List;
    import java.util.stream.Collectors;
    
    @Data
    public class Trader {
        private final String name;
        private final String city;
    }
    
    @Data
    class Transaction{
        private final Trader trader;
        private final int year;
        private final int value;
    
        public static void main(String[] args) {
            Trader raoul = new Trader("Raoul", "Cambridge");
            Trader mario = new Trader("Mario","Milan");
            Trader alan = new Trader("Alan","Cambridge");
            Trader brian = new Trader("Brian","Cambridge");
            List<Transaction> transactions = Arrays.asList(
                    new Transaction(brian, 2011, 300),
                    new Transaction(raoul, 2012, 1000),
                    new Transaction(raoul, 2011, 400),
                    new Transaction(mario, 2012, 710),
                    new Transaction(mario, 2012, 700),
                    new Transaction(alan, 2012, 950)
            );
    
            //(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
            transactions.stream()
                    .filter(t -> t.getYear()==2011)
                    .sorted(Comparator.comparing(Transaction::getValue))
                    .collect(Collectors.toList())
                    .forEach(System.out::println);
    
            //(2) 交易员都在哪些不同的城市工作过?
            transactions.stream()
                    .map(t -> t.getTrader().getCity())
                    .distinct()
                    .collect(Collectors.toList())
                    .forEach(System.out::println);
            //或者
            transactions.stream()
                    .map(t -> t.getTrader().getCity())
                    .collect(Collectors.toSet())
                    .forEach(System.out::println);
    
            //(3) 查找所有来自于剑桥的交易员,并按姓名排序。
            transactions.stream()
                    .map(Transaction::getTrader)
                    .filter(t -> "Cambridge".equals(t.getCity()))
                    .distinct()
                    .sorted(Comparator.comparing(Trader::getName))
                    .forEach(System.out::println);
    
            //(4) 返回所有交易员的姓名字符串,按字母顺序排序。
            String str = transactions.stream()
                    .map(t -> t.getTrader().getName())
                    .distinct()
                    .sorted()
                    .collect(Collectors.joining());
            System.out.println(str);
    
            //(5) 有没有交易员是在米兰工作的?
            boolean b = transactions.stream()
                    .anyMatch(t -> "Milan".equals(t.getTrader().getCity()));
            System.out.println(b);
    
            //(6) 打印生活在剑桥的交易员的所有交易额。
            transactions.stream()
                    .filter(t -> "Cambridge".equals(t.getTrader().getCity()))
                    .map(Transaction::getValue)
                    .forEach(System.out::println);
    
            //(7) 所有交易中,最高的交易额是多少?
            transactions.stream()
                    .map(Transaction::getValue)
                    .reduce(Integer::max)
                    .ifPresent(System.out::println);
    
            //(8) 找到交易额最小的交易。
            transactions.stream()
                    .min(Comparator.comparing(Transaction::getValue))
                    .ifPresent(System.out::println);
        }
    }
    
数值流
  1. 原始类型流特化
    使用reduce方法可以计算list中元素的总和,但是有时候却会暗藏一个装箱和拆箱的操作,比如下面这段代码:
    int calories = menu.stream()
    			.map(Dish::getCalories)
    			.reduce(0, Integer::sum);
    
    为了解决这个Integer拆箱转换为int的操作,我们可以直接将该stream<integer>转换为intstream
    //Stream<Integer>映射到数值流intstream
    int calories = menu.stream()
    			.mapToInt(Dish::getCalories)
    			.sum();
    //intstream转换成流对象stream<Integer>
    IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
    Stream<Integer> stream = intStream.boxed();
    //使用默认值OptionalInt
    OptionalInt maxCalories = menu.stream()
    					.mapToInt(Dish::getCalories)
    					.max();
    int max = maxCalories.orElse(1);
    
  2. 数值范围
  3. 数值流应用:勾股数
构建流
  1. 由值创建流
  2. 由数组创建流
  3. 由文件创建流
  4. 由函数生成流:创建无限流
使用流收集数据
收集器简介
规约和汇总
分组
分区
收集器接口
自己的收集器,来获得更好的性能
并行处理数据与性能
并行流
分支合并框架
Spliterator
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值