这篇文章主要介绍“如何使用java8新特性Stream”,在日常操作中,相信很多人在如何使用java8新特性Stream问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用java8新特性Stream”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
首先先定义一个菜品类:
Dish
public class Dish { private final String name; private final boolean vegetarian; private final int calories; private final Type type; public boolean isVegetarian() { return vegetarian; } //省略set,get,toString方法}
然后创建一个静态方法,并且设置成类的成员变量,以供测试。
public static List<Dish> getDishes() { return 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("pizza", true, 550, Dish.Type.OTHER), new Dish("prawns", false, 300, Dish.Type.FISH), new Dish("salmon", false, 450, Dish.Type.FISH) ); }
XNBqZ
好了,现在有个需求,找出菜品中所有小于400卡路里的食物,并且按照卡路里的大小进行排序。
在java8之前,甚至有些人在java8之后,都会想着借助一个中间变量保符合要求的菜品,然后排序。
public static List<String> beforeJava8() { List<Dish> lowCaloricDishes = new ArrayList<>(); for (Dish dish : dishes) { if (dish.getCalories() < 400) { lowCaloricDishes.add(dish); } } lowCaloricDishes.sort(Comparator.comparingInt(Dish::getCalories));// lowCaloricDishes.sort((d1, d2) -> Integer.compare(d1.getCalories(), d2.getCalories())); List<String> res = new ArrayList<>(); for (Dish dish : lowCaloricDishes) { res.add(dish.getName()); } return res; }
由于前一篇文章讲过了方法引用,所以这里就直接用,不过下面一行也有普通的Lambda表达式的书写。
上述写法有什么问题吗,可以发现lowCaloricDishes
只使用了一次,真就一个临时变量。那能不能跳过创建变量的过程,你直接把数据给我,我经过过滤排序后得到想要的呢,就和流水线一样。
public static List<String> afterJava8() { return dishes.stream() .filter(dish -> dish.getCalories() < 400) .sorted(Comparator.comparing(Dish::getCalories)) .map(Dish::getName) .collect(Collectors.toList()); }
从支持数据处理操作的源生成的元素序列
流和集合有点类似,集合是数据结构,主要的目的是存储和访问元素,而流的主要目的是为了对元素进行一系列的操作。
通俗入门地来讲,集合就相当于你一部电影下载,流就相当于在线观看。其实只需要把流想成高级的集合即可。流有两个重要的特点:
流水线: 很多流本身会返回一个流,这样多个流就能链接起来和流水线一般。
内部迭代: 内部迭代也就是把迭代封装起来,如collect(Collectors.toList)
,与之相对应的外部迭代则是for-each
。
值得注意的是,和迭代器类似,流只能遍历一次 ,遍历完就可以说这个流消费掉了。
流常用的构建方式有4种,其实要么是借助Stream
类的静态方法,要么是借助别人的类的静态方法。
由值创建流
由数组创建流
由文件生成流
由函数生成流
public static void buildStream() throws IOException { Stream<String> byValue = Stream.of("java8", "c++", "go"); Stream<Object> empty = Stream.empty(); int[] nums = {1, 2, 3, 4, 5}; IntStream byInts = Arrays.stream(nums); Stream<String> byFiles = Files.lines(Paths.get("")); Stream<Integer> byFunction1 = Stream.iterate(0, n -> n * 2); Stream<Double> byFunction2 = Stream.generate(Math::random); Stream<String> java = Stream.of("java"); }
可以连接起来的流操作称为中间操作,关闭流的操作称为终端操作
通俗地讲,返回结果是流的操作称为中间操作,放回的不是流的操作称为终端操作。
image-20210414155605342
通过查找java8接口可以得知到哪些接口是中间操作,哪些接口时终端操作。由于那些接口描述得太过官方,估计我贴了也没啥人会仔细看,所以想看的直接去官方查阅即可。
就按照官网上的java API顺序来讲述,小插一句,之前我一直没有学流是主要是因为感觉接口会很多,怎么可能记得了这么多,其实这几天看才发现真的很少,基本上不用记。
img
首先构建好一个数字列表:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7);
中间操作有去重、过滤、截断、查看、跳过、排序 ,这些相信大家都能够明白是什么意思。
public static void midOperation() { numbers.stream() .distinct() .forEach(System.out::println); List<Integer> filter = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); numbers.stream() .limit(3) .forEach(System.out::println); numbers.stream() .peek(integer -> System.out.println("consume operation:" + integer)) .forEach(System.out::println); List<Integer> skip = numbers.stream() .skip(2) .collect(Collectors.toList()); numbers.stream() .sorted() .forEach(System.out::println); }
需要单独拎出来说的是映射(map) 和扁平化映射(flatMap) ,注意,这里的map并不是hashmap的那个map,而是说把什么映射或者说转化成了什么。
public static void midOperation() { List<String> map = numbers.stream() .map(Object::toString) //这里就是把int映射成了string .collect(Collectors.toList()); }
而对于扁平化映射,现在又有一个需求,现在有个单词列表如{"hello", "world"},返回里面各不相同的字符,也就是要求返回List<String>
。
这还不简单,把单词映射成一个个字母,再去重就好了。
public static void flatMapDemoNoral() { List<String> words = Arrays.asList("hello", "world"); List<String[]> normal = words.stream() .map(str -> str.split("")) .distinct() .collect(Collectors.toList()); }
img
虽然确实也能达到效果,但是注意映射所用的函数是split()
,返回的是String[]
,因此整个返回的是List<String[]>
那我映射完后再把每个String[]
数组映射成流
public static void flatMapDemoMap() { List<String> words = Arrays.asList("hello", "world"); List<Stream<String>> usingMap = words.stream() .map(str -> str.split("")) .map(Arrays::stream) .distinct() .collect(Collectors.toList()); }
虽然摘掉了数组的帽子,但是返回的却是List<Stream<String>>
。
flatMap
正是为了解决这种情况的
public static void flatMapDemoFlatMap() { List<String> words = Arrays.asList("hello", "world"); List<String> usingFlatMap = words.stream() .map(str -> str.split("")) .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList()); }
可以简单的理解,map是把每个元素映射成了独立的流,而扁平化map是把元素保存了下来,最后映射成了一个流 。
终端操作除了上述写例子的时候常用的collect()
和forEach()
还有查找和规约两种大的方向。
因为没啥好说的,直接上代码就完了:
public static void endOperationFindAndMatch() { if (dishes.stream().noneMatch(Dish::isVegetarian)) { System.out.println("所有的菜品都是非素食"); } if (dishes.stream().allMatch(Dish::isVegetarian)) { System.out.println("所有的菜品都是素食"); } if (dishes.stream().anyMatch(Dish::isVegetarian)) { System.out.println("菜品中至少有一道菜是素食"); } Optional<Dish> any = dishes.stream() .filter(meal -> meal.getCalories() <= 1000) .findAny(); Optional<Dish> first = dishes.stream() .filter(meal -> meal.getCalories() <= 1000) .findFirst(); }
对流的规约操作的话,一般有普通操作也就是能直接调用接口的,还有一种就是借助reduce()
。
对于普通操作来说,像求和,最大值,最小值这些都是有接口对应的。
public static void endOperationCalculate() { long count = dishes.stream() .filter(meal -> meal.getCalories() <= 1000) .count(); Optional<Dish> max = dishes.stream() .max(Comparator.comparingInt(Dish::getCalories)); Optional<Dish> min = dishes.stream() .min(Comparator.comparing(Dish::getName)); }
但是如果说要求对元素求和,就要使用reduce()
image-20210417114209123
一般使用的是可以接受2个参数,一个是初始值,一个是BinaryOprator<T>
来将两个元素结合起来产生的新值。
public static void reduceDemo() { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7); Integer sum = numbers.stream().reduce(0, Integer::sum); //所有元素相乘也是比较简单 Integer multi = numbers.stream().reduce(0, (a, b) -> a * b); //还有求最大值 Optional<Integer> max = numbers.stream().reduce(Integer::max); }
上面一直出现有返回值是Optional<T>
,它是一个容器类,代表一个值存在或者不存在,比如一开始的findAny()
,可能找不到符合条件的菜品。Java8引入的目的主要是/为了不要返回容易出现问题的null了。
就说几个比较常用的api就好了至于其它的可以上网看下官方API,今天说的API已经够多了
isPresent()
将在Optional
包含值的时候返回true,否则返回false
ifPresent(Consumer<T> block)
存在值的时候会执行给定的代码块
get()
存在值就返回值,否则抛出NoSuchElement
异常
orElse()
存在值就返回,否则就返回一个默认值
public static void optionalDemo() { //ifPresent dishes.stream() .filter(Dish::isVegetarian) .findAny() .ifPresent(dish -> System.out.println(dish.getName())); //isPresent boolean isLowCalories= dishes.stream() .filter(dish -> dish.getCalories() <= 1000) .findAny() .isPresent(); //get Optional<Dish> optional = dishes.stream() .filter(Dish::isVegetarian) .findAny(); if (optional.isPresent()) { Dish dish = optional.get(); } //orElse Dish dishNormal = dishes.stream() .filter(Dish::isVegetarian) .findAny() .orElse(new Dish("java", false, 10000, Dish.Type.OTHER)); }
到此,关于“如何使用java8新特性Stream”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。