java8实战之使用流(完整的例子内容)
好了今天我们来学习怎么使用流.
回忆下之前我们正常数据迭代的话都是使用的是 外部迭代
List<Dish> list1 = new ArrayList<>() for(Dish dish:list){ if(dish.isVegetarain()){ list1.add(dish) } }
使用流迭代 : 内部迭代(好处,可以绝对使用并行你的代码)加快速度
List<Dish> list1 = list.stream() //filter 过滤 .filter(d->d.isVegetarain()) //转存到list .collect(toList());
筛选和切片
用谓词筛选(返回一个boolean值的函数),筛选出各不同的元素,忽略头中的几个元素,或将流截短到指定长度
用谓词筛选
Stream接口支持filter方法 ,该操作会接收一个谓词作为参数,并返回一个包括所有符合谓词的元素的流.
筛选各异的元素
distinct() :过滤掉相同的元素. 它会返回一个元素各异(根据流生成元素hashcode()和equels()实现)的流
/** * @author clx * 过滤相同的元素,返回一组不同的元素流 * 根据hashcode和equels方法实现 */ public class DistinctDemo { public static void main(String[] args) { List<Integer> integers = Arrays.asList(1, 2, 1, 3, 4, 5, 6, 4); List<Integer> collect = integers.stream().distinct().collect(Collectors.toList()); System.out.println(collect); -------------------------------- //如果是过滤字符串就需要使用flatMap来扁平化过滤了 List<String> strings = Arrays.asList("hello", "world"); List<String> collect1 = strings.stream() //切割 .map(s -> s.split("")) //转换成单个arrays流 .flatMap(Arrays::stream) .distinct() .collect(Collectors.toList()); System.out.println(collect1); } }
短截流--limit(int i)
limit()方法:它会返回一个指定长度的的流,所需的长度作为参数传递给limit,如果流是有序的,那么就会返回i个元素
List<Integer> integers = Arrays.asList(1, 2, 1, 3, 4, 5, 6, 4); List<Integer> collect = integers.stream().distinct().limit(3).collect(Collectors.toList()); //[1, 2, 3] System.out.println(collect);
limit也可以用在无序的. 返回值式collect(toSet())
跳过元素---skip(int n)
skip() :它返回一个扔掉了前n 个元素的流.如果不足n个元素返回一个空流
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6); List<Integer> collect = integers.stream() .skip(2).collect(Collectors.toList()); //[3, 4, 5, 6] System.out.println(collect);
映射
一个非常常见的数据处理套路就是从某个对象中选择信息.比如在mysql里,你可以从表中选择一列.Stream API也通过map和flatMap方法提供了类似的工具
对流中每一个元素应用函数
流支持map方法,它会接收一个函数作为参数.这个函数会被应用到每个元素上.并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是"创建一个新版本",而不是去修改)
//如果是过滤字符串就需要使用flatMap来扁平化过滤了 List<String> strings = Arrays.asList("hello", "world"); List<String> collect1 = strings.stream() //切割每个元素并提取每个元素 .map(s -> s.split("")) //转换成单个arrays流 .flatMap(Arrays::stream) .distinct() //转存成list .collect(Collectors.toList()); System.out.println(collect1);
获取每个元素的长度
List<String> strings = Arrays.asList("hello", "world"); //获取长度 List<Integer> collect = strings.stream() //由于数组的内容是String 所以使用String .map(String::length) .collect(Collectors.toList()); //[5, 5] System.out.println(collect); }
流的扁平化
对于一张单 词表,如何返回一张列表,列出里面 各不相同的字符 呢?例如,给定单词列表 ["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]。
使用map实现
返回是List<String[]> 根本就不能去重复
List<String> strings = Arrays.asList("hello", "world"); List<String[]> collect = strings.stream().map(s -> s.split("")).distinct().collect(Collectors.toList());
使用flatMap扁平化流
将数组使用Arrays.stream将数组中的每个元素转换成字符串流
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接 起来成为一个流。
List<String> strings = Arrays.asList("hello", "world"); // List<String[]> collect = strings.stream().map(s -> s.split("")).distinct().collect(Collectors.toList()); strings.stream() //将其映射切割成独立的字符串数组 .map(s -> s.split("")) //将切割后的数组转换成字符串流 .flatMap(Arrays::stream) //在进行过滤重复 .distinct() //生成新的list .collect(Collectors.toList());
练习题
给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4,
5],应该返回[1, 4, 9, 16, 25]
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); List<Integer> collect = list.stream().map(integer -> integer * integer).collect(Collectors.toList());
给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应 该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。为简单起见,你可以用有两个元素的数组来代 表数对
/** * 你可以使用两个map来迭代这两个列表,并生成数对。但这样会返回一个Stream- * <Stream<Integer[]>>。你需要让生成的流扁平化,以得到一个Stream<Integer[]>。 / List<Integer> list1 = Arrays.asList(1, 2, 3); List<Integer> list2 = Arrays.asList(3, 4); //如果使用map的话他会返回一个List<Stream<String[]>流 List<int[]> collect = list1.stream() .flatMap(i -> list2.stream().map(j -> new int[]{i, j})).collect(toList());
如何扩展前一个例子,只返回总和能被3整除的数对呢?例如(2, 4)和(3, 3)是可以的。
List<Integer> list1 = Arrays.asList(1, 2, 3); List<Integer> list2 = Arrays.asList(3, 4); //如果使用map的话他会返回一个List<Stream<String[]>流 List<int[]> collect = list1.stream() .flatMap(i -> list2.stream().filter(j->(i+j)%3==0).map(j -> new int[]{i, j})).collect(toList());
查找和匹配
常见的数据处理套路是看看数据几张的某些元素是否匹配一个给定的属性.Stream API 通过allMatch,anyMath,noneMath,findFirst,和findAny方法提供了这样的工具.
检查谓词是否至少匹配一个元素--anyMatch
anyMath方法可以回答"流中是否有一个元素能匹配给定的谓词" 查看菜单里面是否有素食
if(menu.stream().anyMath(Dish::isVegetarian)){ //do something }
anyMath返回一个boolean,因此是一个终端操作
检查谓词是否匹配所有元素 -- allMatch(全部包含) noneMatch(全部不包含)
allMatch
方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词.如果遇到一个没有匹配那么会直接返回不会继续匹配.
List<Integer> list = Arrays.asList(1, 2, 3, 4); //判斷每个元素是否都大于0 如果都大于返回true 否则返回false boolean b = list.stream().allMatch(d -> d > 0); System.out.println(b);
noneMatch
相对allMatch,它可以确保流中没有任何元素与给定的谓词匹配
List<Integer> list = Arrays.asList(1, 2, 3, 4); //判斷每个元素是否都大于0 如果都大于返回true 否则返回false boolean b = list.stream().noneMatch(d -> d > 5); System.out.println(b);
查找元素----findAny
findAny方法将返回当前流中的任意元素.它可以与其他流操作结合使用..
List<Integer> list = Arrays.asList(1, 2, 3, 4, 1); Optional<Integer> any = list.stream() .findAny(); System.out.println(any); Integer integer = any.get(); System.out.println(integer);
和filter结合使用
List<Integer> list = Arrays.asList(1, 2, 3, 4, 1); //过滤掉1后的任意元素 Optional<Integer> any = list.stream().filter(d -> d > 1).findAny(); Integer integer = any.get(); //结果 2 System.out.println(integer);
查找第一个元素----findFirst
有些流有一个出现顺序来指定流中项目出现的逻辑顺序(list或者排序好的数据生成的顺序流).可以使用findFist来获取第一个元素
//给定一个数字列表,下面的代码能找出第一个平方能被3整除的数: List<Integer> list = Arrays.asList(1, 2, 3, 4); Optional<Integer> first = list.stream().map(d -> d * d).filter(e -> e % 3 == 0).findFirst(); Integer integer = first.get(); System.out.println(integer);
何时使用findFirst和findAny 你可能会想,为什么会同时有findFirst和findAny呢?答案是并行。找到第一个元素 在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流 时限制较少。
归约
元素求和 -- reduce
for-each循环求和
int a = 0; for(int n:nembers){ a+=n; }
使用流的reduce方法
int num = nembers.stream() .reduce(0,(a,b)->a+b);
让我们深入研究一下reduce操作是如何对一个数字流求和的。首先,0作为Lambda(a)的 第一个参数,从流中获得4作为第二个参数(b)。0 + 4得到4,它成了新的累积值。然后再用累 积值和流中下一个元素5调用Lambda,产生新的累积值9。接下来,再用累积值和下一个元素3 调用Lambda,得到12。最后,用12和流中最后一个元素9调用Lambda,得到最终结果21。
//求每个数相乘 int num = nembers.stream() .reduce(1,(a,b)->a*b);
使用方法引用简化代码
//求每个数之和 int num = nembers.stream() .reduce(1,Integer::num);
无初始值
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b)); //返回值类型是Optional 因为返回值有可能是null值.
最大值和最小值 max min
最大值 max
Optional<Integer> sum = numbers.stream().reduce(Integer::max);
最小值 min
Optional<Integer> sum = numbers.stream().reduce(Integer::min);
怎样用map和reduce方法数一数流中有多少个菜呢?
答案:要解决这个问题,你可以把流中每个元素都映射成数字1,然后用reduce求和。这 相当于按顺序数流中的元素个数。
int count = menu.stream() .map(d -> 1) .reduce(0, (a, b) -> a + b);
数值流
原始类型流特化
java8引入了三个原始类型特化流接口来解决这个问题; IntSteam,DoubleStream和LongStream 分别将流中的元素特化为int,double,long,从而避免了暗含的装箱成本.每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max,此外还有必要时再把它们转换会对象流的方法,要记住的是,这些特化的原因并不在与流的复杂性,而是装箱造成的复杂性.---即类似int和Integer之间的效率差异
1.映射到数值流---mapToInt ,mapToDouble,mapToLong
将流转换为特化版本 支持 sum 求和,max求最大值、min求最小值、average求平均值 方法
int sum = menu.stream() //返回intStream .mapToInt(Dish::getCalories) // .sum()
2.转换成对象流
一旦有了数值流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能 产生原始整数: IntStream 的 map 操作接受的 Lambda 必须接受 int 并返回 int (一个 IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream
接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个 Integer),可以使用boxed方法
int sum = menu.stream() //返回intStream .mapToInt(Dish::getCalories) // .sum(); Stream<Integer> stream = intStream.boxed(); //将数值转换为Stream
3.默认值OptionalInt
计算最大值 值为0是会报错的,我们可以利用OptionalInt设置
OptionalInt maxInt = menu.stream() //返回intStream .mapToInt(Dish::getCalories) // .max(); int max = maxInt.orElse(1); //如果没有最大值的话,显示提供一个默认最大值
数值范围
IntStream.rangeClosed(1, 100) 代表 1-100 包含100
IntStream.range(1, 100) 代表 1-99 不包含100
勾股数
/** * @author Administrator * 勾股数 * 3,4,5 *3*3+4*4=5*5 */ public class Demo6 { public static void main(String[] args) { //IntStream.rangeClosed(1, 100) 1-100 包含100 // IntStream.range(1, 100):不包含100 /* Stream<int[]> stream = IntStream.rangeClosed(1, 100).boxed() .flatMap(a -> IntStream.rangeClosed(a, 100) //java 测试a * a + b * b是不是整数可以使用expr%1来查看 .filter(b -> Math.sqrt(a * a + b * b) % 1 == 0) .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})); stream.limit(5).forEach(s-> System.out.println(s[0]+","+s[1]+","+s[2])); */ //升级 Stream<double[]> stream = IntStream.rangeClosed(1, 100).boxed() .flatMap(a -> IntStream.rangeClosed(a, 100) .mapToObj(b -> new double[]{a, b, Math.sqrt(a * a + b * b)}) //元组中第三个元素必须是整数 .filter(b -> b[2] % 1 == 0)); stream.limit(5).forEach(s-> System.out.println(s[0]+","+s[1]+","+s[2])); } }
构建流
从值序列,数组,文件来创建流,甚至有生成函数来创建无限流
由值创建流----Stream.of()
Stream<Integer> stream = Stream.of(1,2,3); //创建空流 Stream<String> emptyStream=Stream.empty();
有数组创建流---Arrays.stream(Object obj)
int[] members={1,3,4}; int sum= Arrays.stream(members).sum; // 创建数组流并求和
有文件生成流
Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。 java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是 Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
long uniqueWords = 0; try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count(); } catch(IOException e){ }
你可以使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行。然后,你 可以对line调用split方法将行拆分成单词。应该注意的是,你该如何使用flatMap产生一个扁 平的单词流,而不是给每一行生成一个单词流。最后,把distinct和count方法链接起来,数 数流中有多少各不相同的单词。
总结
Streams API可以表达复杂的数据处理查询。常用的流操作总结在表5-1中。
你可以使用filter、distinct、skip和limit对流做筛选和切片。
你可以使用map和flatMap提取或转换流中的元素。
你可以使用findFirst和 findAny方法查找流中的元素。你可以用allMatch、
noneMatch和anyMatch方法让流匹配给定的谓词。
这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
你可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大
元素。
filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才
能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所
有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操
作也有相应的特化。
流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法
创建。
无限流是没有固定大小的流