Java8实战-总结14

Java8实战-总结14

引入流

集合是Java中使用最多的API。几乎每个Java应用程序都会制造和处理集合。集合对于很多编程任务来说都是非常基本的:它们可以让数据分组并加以处理。为了解释集合是怎么工作的,想象一下准备列出一系列菜,组成一张菜单,然后再遍历一遍,把每盘菜的热量加起来。可能想选出那些热量比较低的菜,组成一张健康的特殊菜单。尽管集合对于几乎任何一个Java应用都是不可或缺的,但集合操作却远远算不上完美。

  • 很多业务逻辑都涉及类似于数据库的操作,比如对几道菜按照类别进行分组(比如全素菜肴),或查找出最贵的菜。大部分数据库都允许声明式地指定这些操作。比如,以下SQL查询语句就可以选出热量较低的菜肴名称:SELECT name FROM dishes WHERE calorie < 400。你不需要实现如何根据菜肴的属性进行筛选(比如利用迭代器和累加器),只需要表达想要什么。这个基本的思路意味着,用不着担心怎么去显式地实现这些查询语句——都替你办好了,怎么到了集合这里就不能这样了呢?
  • 要是要处理大量元素又该怎么办呢?为了提高性能,需要并行处理,并利用多核架构。但写并行代码比用迭代器还要复杂,而且调试起来也够受的。

Java语言的设计者能做些什么,来帮助你节约宝贵的时间,让程序员活得轻松一点儿呢?答案就是流。

流是什么

流是Java API的新成员,它允许以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,无需写任何多线程代码。简单看看使用流的好处吧。下面两段代码都是用来返回低热量的菜肴名称的,并按照卡路里排序,一个是用Java 7写的,另一个是用Java 8的流写的。

之前(Java 7):

	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 ());
	}

在这段代码中,用了一个“垃圾变量”lowCaloricDishes。它唯一的作用就是作为一次性的中间容器。在Java 8中,实现的细节被放在它本该归属的库里了。之后(Java 8):

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List<String> lowCaloricDishesName =
//选出400卡路里以下的菜肴
				menu.stream()
					.filter(d -> d.getCalories() < 400) //选出400卡路里以下的菜肴
					.sorted(comparing(Dish::getcalories))//按照卡路排序
					.map(Dish::getName)//提取菜肴的名称
					.collect(toList ());//将所有名称保存在List中

为了利用多核架构并行执行这段代码,只需要把stream()换成parallelStream():

List<String> lowCaloricDishesName = menu.parallelstream()
									.filter(d -> d.getcalories() < 400)
									.sorted(comparing(Dishes::getCalories))
									.map(Dish::getName)
									.collect(toList());

在调用parallelStream方法的时候到底发生了什么。用了多少个线程?对性能有多大提升?后续详细讨论这些问题。现在,从软件工程师的角度来看,新的方法有几个显而易见的好处。

代码是以声明性方式写的:说明想要完成什么(筛选热量低的菜肴)而不是说明如何实现一个操作(利用循环和if条件等控制流语句)。这种方法加上行为参数化可以轻松应对变化的需求:很容易再创建一个代码版本,利用Lambda表达式来筛选高卡路里的菜肴,而用不着去复制粘贴代码。

可以把几个基础操作链接起来,来表达复杂的数据处理流水线(在filter后面接上sortedmapcollect操作,如下图所示),同时保持代码清晰可读。filter的结果被传给了sorted方法,再传给map方法,最后传给collect方法。

因为filtersortedmapcollect等操作是与具体线程模型无关的高层次构件,所以它们的内部实现可以是单线程的,也可能透明地充分利用多核架构。在实践中,这意味着用不着为了让某些数据处理任务并行而去操心线程和锁了,Stream API都替你做好了。
在这里插入图片描述
新的Stream API表达能力非常强。比如下面这样的代码:

Map<Dish.Type,List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

简单来说就是,按照Map里面的类别对菜肴进行分组。比如,Map可能包含下列结果:

{FISH = [prawns, salmon],
OTHER = [french fries, rice, season fruit, pizza], MEAT = [pork, beef, chicken]}
其他库:Guava、Apache和lambdaj

为了给Java程序员提供更好的库操作集合,前人已经做过了很多尝试。比如,Guava就是谷歌创建的一个很流行的库。它提供了multimaps和multisets等额外的容器类。
ApacheCommons Collections库也提供了类似的功能。最后,本书作者Mario Fusco编写的lambdaj受到函数式编程的启发,也提供了很多声明性操作集合的工具。

如今Java8自带了官方库,可以以更加声明性的方式操作集合了。

总结一下,Java8中的Stream API可以让你写出这样的代码:

  • 声明性——更简洁,更易读
  • 可复合——更灵活
  • 可并行——性能更好

接下来会用这样一个例子:一个menu,它只是一张菜肴列表。

	List<Dish> menu = Arrays.asList(
				new Dish("pork", false,800, Dish.Type.MEAT),
				new Dish("beef",false,700, Dish.Type.MEAT),
				new Dish("chicken",false,400, Dish.Type.MEAT),
				new Dish("french fries", true, 530, Dish.Type.OTHER),
				new Dish("rice", true,350, Dish.Type.OTHER),
				new Dish("season fruit", true,120, Dish.Type.OTHER),
				new Dish("pizza", true,550, Dish.Type.OTHER),
				new Dish("prawns",false,300, Dish.Type.FISH),
				new Dish("salmon", false, 450, Dish.Type.PISH));

Dish类的定义是:

	public class Dish {
		private final String name;
		private final boolean vegetarian;
		private final int calories;
		private final Type type;
		
		public Dish(String name, boolean vegetarian, int calories, Type type) {
			this.name = name;
			this.vegetarian = vegetarian;
			this.calories = calories;
			this.type = type;
		}
		
		public String getName() {
			return name;
		}
		
		public boolean isVegetarian() {
			return vegetarian;
		}
		
		public int getCalories() {
			return calories;
		}
		
		public Type getType() {
			return type;
		}
		
		@Override
		public String toString(){
			return name;
		}
		
		public enum Type { MEAT,FISH,OTHER }
	}

现在就来仔细探讨一下怎么使用Stream API。会用流与集合做类比,做点儿铺垫。之后会详细讨论可以用来表达复杂数据处理查询的流操作。会谈到很多模式,如筛选、切片、查找、匹配、映射和归约。

接下来,会讨论如何创建和操纵数字流,比如生成一个偶数流,或是勾股数流。最后,会讨论如何从不同的源(比如文件)创建流。还会讨论如何生成一个具有无穷多元素的流——这用集合肯定搞不定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值