(三)使用流

一、筛选和切片

1、用谓词筛选(filter)

谓词:用来描述或判定客体性质、特征或者客体之间关系的词项。在程序中解释是接收一个参数值,并返回truefalse
filter方法会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。
public class Test05 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();//这个初始化方法在流介绍中
        //筛选出所有的素菜
        List<Dish> list = init.stream().filter(Dish::isVegetarian).collect(Collectors.toList());
        list.forEach(System.out::println);
    }
}
/** 运行结果 */
Dish(name=白菜, vegetarian=true, calories=530, type=OTHER)
Dish(name=菠菜, vegetarian=true, calories=350, type=OTHER)
Dish(name=时令水果, vegetarian=true, calories=120, type=OTHER)
Dish(name=黄瓜, vegetarian=true, calories=550, type=OTHER)

2、筛选各异的元素(distinct)

distinct方法会返回一个元素各异(根据流所生成元素的hashCodeequals方法实现)的流。去除重复元素。
public class Test06 {
    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(1, 2, 3, 5, 2, 3, 5);
        List<Integer> list = nums.stream().distinct().collect(Collectors.toList());
        System.out.println(list);
    }
}
/** 运行结果 */
[1, 2, 3, 5]

3、截断流(limit)

limit方法会返回一个不超过给定长度的流。取出前n个元素。
public class Test05 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //筛选出所有的素菜,并取出前2个
        List<Dish> list = init.stream().filter(Dish::isVegetarian).limit(2).collect(Collectors.toList());
        list.forEach(System.out::println);
    }
}
/** 运行结果 */
Dish(name=白菜, vegetarian=true, calories=530, type=OTHER)
Dish(name=菠菜, vegetarian=true, calories=350, type=OTHER)

4、跳过元素(skip)

skip方法会返回一个去掉了前n个元素的流,如果流中元素不足n个,则返回一个空流,与limit互补。
public class Test05 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //筛选出所有的素菜,跳过前2个元素
        List<Dish> list = init.stream().filter(Dish::isVegetarian).skip(2).collect(Collectors.toList());
        list.forEach(System.out::println);
    }
}
/** 运行结果 */
Dish(name=时令水果, vegetarian=true, calories=120, type=OTHER)
Dish(name=黄瓜, vegetarian=true, calories=550, type=OTHER)

二、映射

1、对流中每一个元素应用函数(map)

map方法会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。
public class Test07 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //筛选出所有的菜名
        List<String> list = init.stream().map(Dish::getName).collect(Collectors.toList());
        System.out.println(list);
    }
}
/** 运行结果 */
[猪肉, 牛肉, 鸡肉, 白菜, 菠菜, 时令水果, 黄瓜, 对虾, 三文鱼]

2、流的扁平化(flatMap)

flatMap方法把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。
public class Test08 {
    public static void main(String[] args) {
        //给定单词列表["Hello","World"],返回列表["H","e","l", "o","W","r","d"]
        String [] array = {"Hello","World"};
        //将数组转为流
        Stream<String> world = Arrays.stream(array);
        List<String> list = world.map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
        System.out.println(list);
    }
}
/** 运行结果 */
[H, e, l, o, W, r, d]
使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。

在这里插入图片描述

三、查找和匹配

1、至少匹配一个元素(anyMatch)

anyMatch方法判断流中是否有一个元素能匹配给定的谓词,返回一个boolean,因此是一个终端操作。
public class Test04 {
    public static void main(String[] args) {
        List<String> title = Arrays.asList("Java8", "In", "Action");
        //判断是否存在长度大于5的数据
        boolean b = title.stream().anyMatch(i -> i.length() > 5);
        System.out.println(b);
    }
}
/** 运行结果 */
true

2、匹配所有元素(allMatch)

allMatch方法与anyMatch工作原理类似,但它会看看流中的元素是否都能匹配给定的谓词。
public class Test04 {
    public static void main(String[] args) {
        List<String> title = Arrays.asList("Java8", "In", "Action");
        //判断所有字符长度是否大于5
        boolean b = title.stream().allMatch(i -> i.length() > 5);
        System.out.println(b);
    }
}
/** 运行结果 */
false

3、都不匹配(noneMatch)

noneMatch方法与allMatch相对,它可以确保流中没有任何元素与给定的谓词匹配。如果匹配上返回false
public class Test04 {
    public static void main(String[] args) {
        List<String> title = Arrays.asList("Java8", "In", "Action");
        boolean b = title.stream().noneMatch(i -> i.length() > 5);
        System.out.println(b);
    }
}
/** 运行结果 */
false

4、短路求值

有些操作不需要处理整个流就能得到结果。例如,假设你需要对一个用and连起来的大布尔表达式求值。不管表达式有多长,你只需找到一个表达式为false,就可以推断整个表达式将返回false,所以用不着计算整个表达式。这就是短路。
对于流而言,某些操作(例如allMatch、anyMatch、noneMatch、findFirst和findAny) 不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样,limit也是一个短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。在碰到无限大小的流的时候,这种操作就有用了:它们可以把无限流变成有限流。

5、查找元素(findAny)

findAny方法将返回当前流中任意元素,只要找到就会立即返回。
public class Test01 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //找出一道素菜
        Optional<Dish> any = init.stream().filter(Dish::isVegetarian).findAny();
        System.out.println(any);
    }
}
/** 运行结果 */
Optional[Dish(name=菠菜, vegetarian=true, calories=350, type=OTHER)]
在上面代码中,可以看到出现了一个新的类Optional,Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。findAny可能什么元素都没找到,这样就不会出现空指针问题了
Optional类几个方法介绍:
  • isPresent()将在Optional包含值的时候返回true,否则返回false
  • ifPresent(Consumer<T> block)会在值存在的时候执行给定的代码块。
  • T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。
  • T orElse(T other)会在值存在时返回值,否则返回一个默认值。

6、查找第一个元素(findFirst )

findFirst方法返回第一个元素。
public class Test02 {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 3, 5, 6, 7, 9, 10);
        //找出第一个可以被2整除的数
        Optional<Integer> first = list.stream().filter(x -> x % 2 == 0).findFirst();
        System.out.println(first);
    }
}
/** 运行结果 */
Optional[6]

7、归约(reduce)

reduce操作可以实现从Stream中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。

定义了初始值

public class Test02 {
    public static void main(String[] args) {
        //定义了初始值,用来指定stream循环的初始值,如果stream为空,就直接返回该值
        List<Integer> list1 = Arrays.asList();
        Integer reduce1 = list1.stream().reduce(0, (a, b) -> a + b);
        System.out.println(reduce1);
        List<Integer> list2 = Arrays.asList(4, 5, 3, 9);
        Integer reduce2 = list2.stream().reduce(0, (a, b) -> a + b);
        System.out.println(reduce2);
    }
}
/** 运行结果 */
0
21
reduce操作流的流程:Lambda反复结合每个元素,直到流被归约成一个值。
首先,0作为Lambda(a)的 第一个参数,从流中获得4作为第二个参数(b)。0 + 4得到4,它成了新的累积值。然后再用累 积值和流中下一个元素5调用Lambda,产生新的累积值9。接下来,再用累积值和下一个元素3调用Lambda,得到12。最后,用12和流中最后一个元素9调用Lambda,得到最终结果21。

在这里插入图片描述

无初始值

public class Test03 {
    public static void main(String[] args) {
        //没有定义初始值,但是会返回一个Optional对象
        List<Integer> list1 = Arrays.asList();
        Optional<Integer> reduce1 = list1.stream().reduce((a, b) -> a + b);
        System.out.println(reduce1);
        List<Integer> list2 = Arrays.asList(4, 5, 3, 9);
        Optional<Integer> reduce2 = list2.stream().reduce((a, b) -> a + b);
        System.out.println(reduce2);
    }
}
/** 运行结果 */
Optional.empty
Optional[21]

四、数值流

1、原始类型流特化

public class Test04 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //计算出总热量
        Integer reduce = init.stream().map(Dish::getCalories).reduce(0, Integer::sum);
        System.out.println(reduce);
    }
}
上面代码,暗含一个装箱的成本。每个Integer都必须拆箱成一个原始类型, 再进行求和。如果能直接调用sun()方法岂不是更好?int calories = init.stream().map(Dish::getCalories).sum();但是这种是不可能的,问题在于map方法会生成一个Stream<T>。虽然流中的元素是Integer类 型,但Streams接口没有定义sum方法。Stream API还提供了原始类型流特化,专门支持处理数值流的方法。
Java 8引入了三个原始类型特化流接口来解决这个问题:IntStreamDoubleStreamLongStream,分别将流中的元素特化为intlongdouble,从而避免了暗含的装箱成本。每 个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到大元素的max。 此外还有在必要时再把它们转换回对象流的方法。要记住的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似intInteger之间的效率差异。

2、映射到数值流

将流转化为特化版本的常用方法是mapToIntmapToDoublemapToLong。这些方法和map方法的工作方式一样,只是它返回的是一个特化流,而不是Stream<T>
public class Test05 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //流特化
        IntStream intStream = init.stream().mapToInt(Dish::getCalories);
        //计算总和,如果流是空的,sum默认返回0,IntStream还支持其他的方法,如max、min、average等
        int sum = intStream.sum();
        System.out.println(sum);
    }
}

3、转换回对象流

一旦有了数值流,可能会想把它转换回非特化流。例如,IntStream上的操作只能产生原始整数:IntStream的map操作接受的Lambda必须接受int并返回int(一个 IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream 接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个 Integer),可以使用boxed方法。
public class Test06 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //流特化
        IntStream intStream = init.stream().mapToInt(Dish::getCalories);
        //调用boxed方法转为一般流
        Stream<Integer> boxed = intStream.boxed();
        //找出是3的倍数的数
        List<Integer> collect = boxed.filter(x -> x % 3 == 0).collect(Collectors.toList());
        System.out.println(collect);
    }
}
/** 运行结果 */
[120, 300, 450]

4、默认值OptionalInt

对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
public class Test07 {
    public static void main(String[] args) {
        //初始化数据
        List<Dish> init = DishService.init();
        //流特化
        IntStream intStream = init.stream().mapToInt(Dish::getCalories);
        //找到最大元素,如果流为空,则会返回【OptionalInt.empty】
        OptionalInt max = intStream.max();
        System.out.println(max);
    }
}

5、数值范围

Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成范围的数值,rangerangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。
public class Test08 {
    public static void main(String[] args) {
        //生成0-100的数字
        IntStream intStream1 = IntStream.rangeClosed(1, 100);
        IntStream intStream2 = IntStream.range(1, 100);
        //统计生成个数
        long count1 = intStream1.count();
        long count2 = intStream2.count();
        System.out.println("rangeClosed生成数字个数:" + count1);
        System.out.println("range生成数字个数:" + count2);
    }
}
/** 运行结果 */
rangeClosed生成数字个数:100
range生成数字个数:99

五、构建流

1、由值创建流

可以使用静态方法Stream.of,通过显式值创建一个流,它可以接受任意数量的参数。
Stream.empty得到一个空流。
public class Test09 {
    public static void main(String[] args) {
        //由Stream.of生成一个流
        Stream<Integer> integerStream = Stream.of(1, 2, 4, 5);
        List<Integer> collect = integerStream.filter(x -> x % 2 == 0).collect(Collectors.toList());
        System.out.println(collect);
    }
}
/** 运行结果 */
[2, 4]

2、由数组创建流

可以使用静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。
public class Test09 {
    public static void main(String[] args) {
        int[] num = {1, 2, 3, 4, 5};
        int sum = Arrays.stream(num).sum();
        System.out.println(sum);
    }
}
/** 运行结果 */
15

3、由文件生成流

Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。 java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是 Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
public class Test10 {
    public static void main(String[] args) {
        long uniqueWords = 0;
        try (Stream<String> lines = Files.lines(Paths.get("D:\\Users\\yeyanbin\\Desktop\\data.txt"), StandardCharsets.UTF_8)) {
            uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(","))).distinct().count();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(uniqueWords);
    }
}
上面代码使用Files.lines得到一个流,其中的每个元素都是给定文件中的一行。然后,可以对line调用split方法将行拆分成单词。应该注意的是,你该如何使用flatMap产生一个扁平的单词流,而不是给每一行生成一个单词流。最后,把distinctcount方法链接起来,数数流中有多少各不相同的单词。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值