Java8之流

博客内容是学习Java8实战书籍后进行总结。

1 流

  1. 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不
    是临时编写一个实现)。

  2. 就现在来说,你可以把它们看成遍历数据集的高级迭代器。

  3. 此外,流还可以透明地并行处理,无需写任何多线程代码。

2 例子

菜单根据卡路里过滤,排序,取出菜名

public class Demo {
    public static void main(String[] args) {
        //菜单
        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) );

        List<Dish> lowCaloricDishes = new ArrayList<>();
        filterByCalories(menu, lowCaloricDishes);
        sortByCalories(lowCaloricDishes);
        List<String> lowCaloricDishesName = new ArrayList<>();
        getName(lowCaloricDishes, lowCaloricDishesName);

        //以上可以优化
        List<String> lowCaloricDishesName2 = menu.stream().filter(d -> d.getCalories() < 400)
                .sorted(comparing(Dish::getCalories)).map(Dish::getName).collect(toList());

        //为了利用多核架构并行执行这段代码,你只需要把stream()换成parallelStream():
        List<String> lowCaloricDishesName3 = menu.parallelStream().filter(d -> d.getCalories() < 400)
                        .sorted(comparing(Dish::getCalories)).map(Dish::getName).collect(toList());

        //跟据类型分组
        Map<Dish.Type, List<Dish>> dishesByType =
                menu.stream().collect(groupingBy(Dish::getType));
        System.out.println();
    }

    /**
     * 获取菜单名
     * @param lowCaloricDishes
     * @param lowCaloricDishesName
     */
    private static void getName(List<Dish> lowCaloricDishes, List<String> lowCaloricDishesName) {
        for(Dish d: lowCaloricDishes){
            lowCaloricDishesName.add(d.getName());
        }
    }

    /**
     * 根据卡路里排序
     * @param lowCaloricDishes
     */
    private static void sortByCalories(List<Dish> lowCaloricDishes) {
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
            public int compare(Dish d1, Dish d2){
                return Integer.compare(d1.getCalories(), d2.getCalories());
            }
        });
    }

    /**
     * 过滤卡路里
     * @param menu
     * @param lowCaloricDishes
     */
    private static void filterByCalories(List<Dish> menu, List<Dish> lowCaloricDishes) {
        for(Dish d: menu){
            if(d.getCalories() < 400){
                lowCaloricDishes.add(d);
            }
        }
    }
}

Dish类

public class Dish {
    private  String name;
    private  boolean vegetarian;
    private  int calories;
    private  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 "Dish{" +
                "name='" + name + '\'' +
                ", vegetarian=" + vegetarian +
                ", calories=" + calories +
                ", type=" + type +
                '}';
    }

    public enum Type { MEAT, FISH, OTHER }
}

3 流简介

  1. 元素序列:流的目的在于表达计算,比如你前面见到的filter、sorted和map。集合讲的是数据,流讲的是计算
  2. 源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集
    合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致
  3. 数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中
    的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执
    行,也可并行执行。
  4. 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大
    的流水线
  5. 内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

collect操作开始处理流水线,并返回结果(它和别的操作不一样,因为它返回的不是流,在这里是一个List)

3.1 流操作
 List<String> threeHighCaloricDishNames = menu.stream().filter(d -> d.getCalories() > 300).map(Dish::getName)
                //只选前3个
                .limit(3).collect(toList());
  1. filter——接受Lambda,从流中排除某些元素。在本例中,通过传递lambda d ->
    d.getCalories() > 300,选择出热量超过300卡路里的菜肴。
  2. map——接受一个Lambda,将元素转换成其他形式或提取信息。在本例中,通过传递方
    法引用Dish::getName,相当于Lambda d -> d.getName(),提取了每道菜的菜名。
  3. limit——截断流,使其元素不超过给定数量。
  4. collect——将流转换为其他形式。在本例中,流被转换为一个列表。可以把collect看
    作能够接受各种方案作为参数,并将流中的元素累积成为一个汇总结果的操作。这里的
    toList()就是将流转换为列表的方案。
3.2 流与集合

流只能消费一次

List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
//java.lang.IllegalStateException:流已被操作或关闭
s.forEach(System.out::println);

内部迭代时,项目可以透明地并行处理,或者用更优化的顺序进行处理

3.3 使用流

流的使用一般包括三件事:

  1. 一个数据源(如集合)来执行一个查询;
  2. 一个中间操作链,形成一条流的流水线;
  3. 一个终端操作,执行流水线,并能生成结果。
中间操作
操作类型返回类型操作参数函数描述符
filter中间StreamPredicateT -> boolean
map中间StreamFunction<T, R>T -> R
limit中间Stream
sorted中间StreamComparator(T, T) -> int
distinct中间Stream
终端操作
操作类型目的
forEach终端消费流中的每个元素并对其应用Lambda。这一操作返回void
count终端返回流中元素的个数。这一操作返回long
collect终端把流归约成一个集合,比如List、Map 甚至是Integer。

4 小结

  1. 流是“从支持数据处理操作的源生成的一系列元素”。
  2. 流利用内部迭代:迭代通过filter、map、sorted等操作被抽象掉了。
  3. 流操作有两类:中间操作和终端操作。
  4. filter和map等中间操作会返回一个流,并可以链接在一起。可以用它们来设置一条流
    水线,但并不会生成任何结果。
  5. forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
  6. 流中的元素是按需计算的。

5 使用流

5.1 筛选和切片
5.1.1 用谓词筛选
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
5.1.2 筛选各异元素(去重)

根据流所生成元素的hashCode和equals方法实现

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
5.1.3 截短流

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。

List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.limit(3)
.collect(toList());
5.1.4 跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一
个空流。请注意,limit(n)和skip(n)是互补的。

List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories() > 300)
.skip(2)
.collect(toList());
5.2 映射

一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里,你可以从表中选
择一列。Stream API也通过map和flatMap方法提供了类似的工具。

流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映
射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一
个新版本”而不是去“修改”)。例如,下面的代码把方法引用Dish::getName传给了map方法

5.2.1 字符流 Arrays.stream()
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
5.2.2 流的扁平化

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所
有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流

List<String> uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());

flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。

5.2.3 例子

(1) 给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4,5],应该返回[1, 4, 9, 16, 25]。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares =numbers.stream().map(n -> n * n).collect(toList());

(2) 给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应
该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。为简单起见,你可以用有两个元素的数组来代
表数对

List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs =numbers1.stream().flatMap(i -> numbers2.stream().map(j -> new int[]{i,j})).collect(toList());
5.3 查找和匹配

看看数据集中的某些元素是否匹配一个给定的属性。StreamAPI通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。

5.3.1 检查谓词是否至少匹配一个元素
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

anyMatch方法返回一个boolean,因此是一个终端操作。

5.3.2 检查谓词是否匹配所有元素

allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词。比如,你可以用它来看看菜品是否有利健康(即所有菜的热量都低于1000卡路里):

boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);

noneMatch和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。比如,
你可以用noneMatch重写前面的例子:

boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);
5.3.3 查找元素

findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。比如,你可能想找到一道素食菜肴。你可以结合使用filter和findAny方法来实现这个查询:

Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();

Optional:Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。

Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法:

  1. isPresent()将在Optional包含值的时候返回true, 否则返回false。

  2. ifPresent(Consumer block)会在值存在的时候执行给定的代码块。我们在第3章介绍了Consumer函数式接口;它让你传递一个接收T类型参数,并返回void的Lambda表达式。

  3. T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。

  4. T orElse(T other)会在值存在时返回值,否则返回一个默认值。

menu.stream().filter(Dish::isVegetarian)
.findAny().ifPresent(d -> System.out.println(d.getName());
5.3.4 查找第一个元素

有些流有一个出现顺序(encounter order)来指定流中项目出现的逻辑顺序(比如由List或
排序好的数据列生成的流)。对于这种流,你可能想要找到第一个元素。为此有一个findFirst
方法,它的工作方式类似于findany。例如,给定一个数字列表,下面的代码能找出第一个平方
能被3整除的数:

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst(); // 9
5.4 归约 reduce
5.4.1 元素求和
//计算list的和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
int sum = numbers.stream().reduce(0, Integer::sum);

reduce接受两个参数:

  1. 一个初始值,这里是0;
  2. 一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是lambda (a, b) -> a + b。

无初始值
reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:

Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
5.4.2 最大值和最小值
//最大值
Optional<Integer> max = numbers.stream().reduce(Integer::max);
//最小值
Optional<Integer> min = numbers.stream().reduce(Integer::min);
5.4.3 例子

怎样用map和reduce方法数一数流中有多少个菜呢?

int count = menu.stream().map(d -> 1).reduce(0, (a, b) -> a + b);
long count = menu.stream().count();
中间操作和终端操作
操作类型返回类型操作参数函数描述符
filter中间StreamPredicateT -> boolean
distinct中间((有状态-无界))Stream
skip中间(有状态-有界)Streamlong
map中间StreamFunction<T, R>T -> R
limit中间(有状态-有界)Stream
flatMap中间StreamFunction<T, Stream>T -> Stream
sorted中间(有状态-无界)StreamComparator(T, T) -> int
anyMatch终端booleanPredicateT -> boolean
noneMatch终端booleanPredicateT -> boolean
allMatch终端booleanPredicateT -> boolean
findAny终端Optional
findFirst终端Optional
forEach终端voidConsumerT -> void
collect终端RCollector<T, A, R>
reduce终端(有状态-有界)OptionalBinaryOperator(T, T) -> T
count终端long
5.4.4 原始类型流特化

Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。

1. 映射到数值流

将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。

int calories = menu.stream().mapToInt(Dish::getCalories).sum();

如果流是空的,sum默认返回0。IntStream还支持其他的方便方法,如max、min、average等。

2. 转换回对象流
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
3. 默认值OptionalInt

在Optional…避免 max,min流为空

//OptionalInt、OptionalDouble和OptionalLong。
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
//显式处理OptionalInt去定义一个默认值
int max = maxCalories.orElse(1);
5.4.5 数值范围
  1. rangeClosed:包含结束值
  2. range:不包含结束值
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
//长度
System.out.println(evenNumbers.count());
5.4.6 数值流应用:勾股数
 Stream<int[]> pythagoreanTriples =
                IntStream.rangeClosed(1, 100).boxed()
                        .flatMap(a ->
                                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)})
                        );
        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));
        pythagoreanTriples.limit(5)
                .forEach(t ->
                        System.out.println(t[0] + ", " + t[1] + ", " + t[2]));

5.5 构建流

5.5.1 由值创建流
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);

//空流
Stream<String> emptyStream = Stream.empty();
5.5.2 由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
5.5.3 由文件生成流
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){
}
5.5.4 由函数生成流:创建无限流
  1. 迭代
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
  1. 生成
//个0到1之间的随机双精度数
Stream.generate(Math::random).limit(5).forEach(System.out::println);
//一值返回的是2
IntStream twos = IntStream.generate(new IntSupplier(){
public int getAsInt(){
return 2;
}
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值