JDK8-05.Stream

之前已经提到过流,这里进行详细的说明。其实流通俗来时就是一个更加高级的迭代器,只是在迭代的过程中,做了我们想做的事。之前的写法是一个foreach然后在括号里些一堆我们的逻辑,比如找最大值,最小值,合并等等等操作,而流就是声明式的。

示例:

	// JDK8 之前
    List<Dish> lowCaloricDishes = new ArrayList<>();
    // 1 遍历 筛选卡路里低于400的的数据进行合并
	for(Dish d: menu){
	    if(d.getCalories() < 400){
	        lowCaloricDishes.add(d);
	    }
	}
	// 2 再进行卡路里排序
	Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
	    public int compare(Dish d1, Dish d2){
	        return Integer.compare(d1.getCalories(), d2.getCalories());
	    }
	});
	// 3 提取排序的各个食物的名称
	List<String> lowCaloricDishesName = new ArrayList<>(); 
	for(Dish d: lowCaloricDishes){
	        lowCaloricDishesName.add(d.getName());
	    }
	}

这10多行代码实际进行了多次遍历,并且lowCaloricDishes这个集合实际没有多大作用,在这里仅仅是作为中间变量使用,因为我们其实就想知道“食物中低于400卡路里的名称的排序”,在JDK8中,流的操作会让你瞬间兴奋。

示例:

import static java.util.Comparator.comparing; 
import static java.util.stream.Collectors.toList;
    List<String> lowCaloricDishesName = 
    	menu.stream()
            .filter(d -> d.getCalories() < 400)
            .sorted(comparing(Dish::getCalories))
            .map(Dish::getName)
            .collect(toList());

静态导入不熟悉的可以移步Think in java。
我们来看看这段代码说了什么?

  1. 拿到menu的流
  2. 过滤卡路里小于400的菜
  3. 卡路里排序
  4. 提取菜名
  5. 生成集合List

流的优势:

  1. 声明-语义间接明了
  2. 复合-可以自由搭配
  3. 并行-不用考虑线程之间的问题,性能更好

如果使用多核操作只需要把stream()换成parallelStream()。
在之后会一直使用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.FISH) ); 

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 } 
}

在流中同样存在拆装箱操作。比如:

int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);

这里的Integer::sum暗含拆箱,需要把卡路里全部拆箱成int进行求和。所以在JDK8中引入了原始类型流特化接口:IntStream,DoubleStream,LongStream,他们主要是解决拆装箱的性能问题,不解决流的复杂性。
转换到数值流:stream().mapToInt,mapToDouble,mapToLong.,这些方法就会得到对应的数值流。当需要将特化流转换问对象流,只需要调用boxed()即可。

数值流应用(勾股数)

勾股定理:a² + b² = c²

  1. 三元数,这里使用int[] = new int[]{3,4,5}
  2. 筛选成立的组合,也就是假设知道a,b需要筛选出他们的平方根是不是整数,如果不是则他们不是勾股数。filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
  3. 生产三元组,筛选后就需要生成第三个数即:map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})
  4. 生成b值。IntStream.rangeClosed(1, 100).filter(b -> Math.sqrt(a*a + b*b) % 1 == 0).boxed().map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
  5. 生成值,假设我们给定一个a值。flatMap是将生成的数据流转换成扁平流。
Stream<int[]> pythagoreanTriples = 
			// 随机一个a
		   IntStream.rangeClosed(1, 100)
		   			.boxed()
		   			.flatMap(a -> 
		   				// a-100 来生成b和c ,如果使用1-100会出现重复
		   				IntStream.rangeClosed(a, 100)
		   						 .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0) 
 								 .mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)}) 
 );

到这里已经能得到1-100的所有的三元数,但是这里我们求了两次平方根,所以可以改变下思路,先求平方根,再筛选,这样代码会更紧凑。

Stream<double[]> pythagoreanTriples2 = 
 	IntStream.rangeClosed(1, 100)
 			.boxed() 
 			.flatMap(a -> 
 				IntStream.rangeClosed(a, 100) 
 						 .mapToObj(b -> new double[]{a, b, Math.sqrt(a*a + b*b)}) 
 						 .filter(t -> t[2] % 1 == 0));

关键就在最后这一句,只要c是整数,那这一定是三元数。

一些简单的实用的方法
  1. 值创建流。Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
  2. 数组创建流。int sum = Arrays.stream(numbers).sum();
  3. 文件生成流。
long uniqueWords = 0; 
/*
* 把获取流的代码放在try里,会自动关闭,不需要在finally里手动关闭。
* Files.lines会得到一个流,其中每个元素都是文件中的一行。
* 这段代码的意思就是:计算data.txt文件中一共出现了多少个不同的单词。
*/
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ 
	uniqueWords = lines.flatMap(line -> 
					  // line.split 是在拆分每个字
				  	  Arrays.stream(line.split(" ")))
 							.distinct() // 去重
 							.count();  // 计算总和
} 
catch(IOException e){ 
 
}
  1. 无限流。在JDK8中有两个函数可以生成无限流:Stream.iterate和Stream.generate。使用无限流一定要使用limit限制,否则内存一定溢出。比如斐波拉契数列(数列中开始的两个数字是0和1,后续的每个数字都是前两个数字之和):
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0]+t[1]}) 
      .limit(20) 
      .forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));

如有错误欢迎指正,转载请注明出处,文章内容总结于《JDK8+实战》。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值