外部迭代与内部迭代的区别(图)代码的含义

图片[1]-外部迭代与内部迭代的区别(图)代码的含义-4747i站长资讯

集合是 Java 中使用最多的 API,几乎每个 Java 程序都需要创建和处理集合。为了更好的操作集合,JDK提供了很多工具类和很多第三方类库,比如Guava等,但是还不够。

例如:

在所有员工中,找到年龄小于 40 岁,按薪水倒序排序,列出薪水最高的前十名员工的姓名

在JAVA8之前,需要特殊处理。没有注释辅助,很难一眼看懂这段代码的含义。

public static void opBefore(List emps) {
    List filterEmps = new ArrayList();
    for (Emp emp : emps) {
        if (emp.getAge() < 40) {
            filterEmps.add(emp);
        }
    }
    Collections.sort(filterEmps, new Comparator() {
        @Override
        public int compare(Emp e1, Emp e2) {
            return Double.compare(e2.getSalary(), e1.getSalary());
        }
    });
    List names = new ArrayList();
    for (Emp emp : filterEmps) {
        names.add(emp.getName());
        if (names.size() >= 10) {
            break;
        }
    }
    System.out.println(names);
}

JAVA8之后呢?JAVA8的设计者设计了流程,使用流程,只需要下面的代码来表达同样的效果。代码简洁明了,关键是看懂意思。看完之后,是不是要再写一遍之前的代码呢?

public static void opAfter(List emps) {
    emps.stream().filter(v -> v.getAge() < 40)
            .sorted(Comparator.comparing(Emp::getSalary).reversed())
            .limit(10)
            .map(Emp::getName)
            .forEach(System.out::println);
}

如果列表非常大并且需要多线程,只需将 () 更改为 () 。

图片[2]-外部迭代与内部迭代的区别(图)代码的含义-4747i站长资讯

流动

是 JDK8 的新成员,具有以下特性:

只遍历一次

像迭代器一样,流只能被遍历一次。如果再次遍历,就会抛出异常。如果要再次遍历,只能通过数据源获取新的流。

List langs = Arrays.asList("Java", "PHP", "Python"); 
Stream stream = langs.stream();
stream.forEach(System.out::println);        // 打印
stream.forEach(System.out::println);        // 抛出 java.lang.IllegalStateException 异常

外部迭代与内部迭代

使用接口(例如 for-each)进行迭代称为外迭代。该库使用内部迭代。

List names = new ArrayList();
for (Emp emp : emps) {
    filterEmps.add(emp.getName());
}

List names = new ArrayList();
Iterator iterator = emps.iterator();
while(iterator.hasNext()) {
    Emp emp = iterator.next();
    filterEmps.add(emp.getName());  
}

List names = emps.stream()
        .map(Emp::getName)
        .collect(toList());

外部迭代和内部迭代的区别。外部迭代需要用户显式获取每个项目并对其进行处理,这完全取决于用户。内部迭代由JDK进行迭代,JDK可以透明地进行优化处理,如根据硬件进行优化、使用并行处理等。随着JDK的发展,可以继承内部迭代的优化。 JDK。

流操作

emps.stream()
        .filter(v -> v.getAge() < 40)
        .sorted(Comparator.comparing(Emp::getSalary).reversed())
        .map(Emp::getName)
        .limit(10)
        .forEach(System.out::println);

中间操作

比如 , , map 等,此类操作的目标是一个流,操作完成会返回另一个流,这样就可以将多个操作连接起来形成一个管道。中间操作不会立即处理,而是等待终端操作出现在管道上,并在终端操作期间一次性处理。

方法

操作说明

函数描述符

筛选

T->

地图

对象转换

T -> R

窥视

对象处理

T -> {}

种类

(T, T) ->

限制

截短

重复数据删除

跳过

跳过 n 个元素

终端操作

终端操作将从流管道生成结果。结果可以是统计、归约或处理函数。例如,上例中的连接是一个处理函数。

方法

操作说明

处理流中每个元素的处理函数,例如打印到控制台

数数

返回流中元素的数量

缩减功能,将流缩减为集合、List、Set、Map等。

图片[3]-外部迭代与内部迭代的区别(图)代码的含义-4747i站长资讯

使用 构建

集合构建流程

实现类(如:List、Set)直接调用()方法获取流。

List langs = Arrays.asList("JAVA", "PHP", "PYTHON")
Stream stream = langs.stream();

数组构建流

数组通过调用 .of() 方法获取流。

String[] langs = {"JAVA", "PHP", "Python"};
Stream stream = Stream.of(langs)

构建无限流

无限流由 .() 方法构造。该方法的参数是一个表达式,通常结合limit方法进行截断。否则,真正的无限流只能等待OOM。

建立一个均匀的流

Stream.iterate(0, n -> n + 2)
        .limit(10)
        .forEach(System.out::println);

构建随机无限流

使用 .() 方法构造一个无限流,流中的每个元素都有一个延续,因为向流中添加一个新元素是基于前一个元素的。相反,它是非连续的,适合生成随机流。

Stream.generate(Math::random)
        .limit(5)
        .forEach(System.out::println);

查找和匹配

示例:在流中查找偶数并打印到控制台

List numbers = Arrays.asList(1,2,3,4,5,6,7,8);
numbers.stream()
        .filter(v -> v % 2 == 0)
        .forEach(System.out::println);

输出如下

2
4
6
8

&

通过方法查找流中任意元素,该方法返回一个对象

List numbers = Arrays.asList(1,2,3,4,5,6,7,8);
numbers.stream()
        .filter(v -> v % 2 == 0)
        .findAny()
        .ifPresent(System.out::println);

输出如下

2

通过方法找到流中的第一个元素。两种方法的区别在于找到第一种。当流使用并行处理时,需要等待所有的结果返回,归约,排序后判断是否是第一个。采取任何一个都更有效。所以如果对订单没有要求c语言字符数组逆序输出,请使用。

&&

用于判断流中是否存在满足条件的元素

示例:判断流中是否有偶数

List numbers = Arrays.asList(1,2,3,4,5,6,7,8);
System.out.println(numbers.stream().anyMatch(v -> v % 2 == 0));

输出如下

true

同样,判断流中任意一个元素匹配成功就足够了,但流中的所有元素都需要匹配。

示例:确定流中的所有数字是否都是偶数

List numbers = Arrays.asList(1,2,3,4,5,6,7,8);
System.out.println(numbers.stream().allMatch(v -> v % 2 == 0));

输出如下

false

反之,则判断流中的所有元素都不匹配。

示例:确定流中的所有数字是否不是偶数

List numbers = Arrays.asList(1,2,3,4,5,6,7,8);
System.out.println(numbers.stream().noneMatch(v -> v % 2 == 0));

输出如下

false

类似 SQL 的操作

重复数据删除

这个名字是不是很眼熟?col … from table,没错,就是SQL语句中的去重关键字,已经成为流计算中的一种方法,其作用也是去重。

例子

List numbers = Arrays.asList(1,2,2,3,3,4);
numbers.stream()
        .distinct()
        .forEach(System.out::println);

输出如下

1
2
3
4

截断和跳过

skip表示跳过n个元素,limit表示截取多少个元素,这两种方法可以组合成一个内存分页方法

List numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
numbers.stream().skip(6).limit(3).forEach(System.out::println);

输出如下

7
8
9

内存分页工具方法

public static  List pagination(List rows, int page, int pageSize) {
    if (CollectionUtils.isEmpty(rows)) {
        return Collections.emptyList();
    }
    int totalRows = rows.size();
    int skipRows = (page - 1) * pageSize;
    if (totalRows <= skipRows) {
        return Collections.emptyList();
    }
    return rows.stream().skip(skipRows).limit(pageSize).collect(Collectors.toList());
}

地图

创建一个员工类以在后续示例中使用

public class Emp {
    private Long id;
    private String name;
    private Integer age;
    private Double salary;
    private Integer sex;
}

地图

map 方法接受一个函数作为参数,将该函数应用于流中的每个元素,并返回一个新元素,替换流中的对象。

示例:打印员工姓名

emps.stream()                     // 流中的元素为 Emp 对象
        .map(Emp::getName)  // 流中的元素转换为 String 对象
        .forEach(System.out::println);

示例:为所有员工提高 1000

emps.stream()               // 流中的元素为 Emp 对象
        .map(v -> {
            v.setSalary(v.getSalary() + 1000D);
            return v;
        })                           // 流中的元素还是 Emp 对象
        .collect(Collectors.toList());

窥视

如上例所示,在map方法中,只处理流中的元素,不需要替换对象。peek 方法就是用来处理这种情况的

示例:为所有员工提高 1000

emps.stream()
        .peek(v -> v.setSalary(v.getSalary() + 1000D))   // 对流中的元素进行处理,但不更换
        .collect(Collectors.toList());

流的扁平化。在前面的例子中,流中的元素都是对象,每个元素都可以通过map方法进行处理,但是如果流中的元素是一个集合呢?

例如:指定单词列表[“Hello”, “World”],返回包含的所有字母并去重

首先需要将单词分解成字母,然后对所有字母的集合进行去重操作。通过上一篇的例子可以知道该方法可以去重,所以合并成如下代码

List words = Arrays.asList("Hello", "World");
List letters = words.stream()
        .map(v -> v.split(""))
        .distinct()
        .collect(Collectors.toList());

可以看出,返回的结果是一个List类型,而不是想象中的一组字母。这是因为 map 方法返回一个字符串数组。处理完map之后,流中的元素就变成了一个字母数组,而我们其实想要基本上就是通过扁平化流。

List words = Arrays.asList("Hello", "World");
List letters = words.stream()
        .map(v -> v.split(""))               // 流中元素转换为 String[]
        .flatMap(v -> Stream.of(v))     // 流中元素转换为 String
        .distinct()
        .collect(Collectors.toList());

自己做

1、给定一个数字列表,[1,2,3,4],返回每个数字的平方列表,结果是[1,4,9,16]

2、给定两个数字列表[1,2,3]和[3,4],对这两个列表进行笛卡尔集,结果为[(1,3),(1,< @4),(2,3),(2,<@4),(3,3),(3,<@4)]

答案 1

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

答案 2

List numbers1 = Arrays.asList(1, 2, 3); 
List numbers2 = Arrays.asList(3, 4); 
List pairs = numbers1.stream()
        .flatMap(i -> numbers2.stream().map(j -> new int[]{i, j}))
        .collect(toList());

减少

在上一篇文章中,我们使用 map 来处理流中的每个元素。处理后,我们需要收集处理结果。在前面的例子中,我们通过类似的方法收集为值,或者通过类似的方法收集为对象,但这些是定向收集,流也提供了更复杂的结果归约的功能。

设置初始值

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

没有初始值(可能流中没有元素)

Optional sum = numbers.stream().reduce((a, b) -> (a + b));

int result = numbers.stream().reduce(1, (a, b) -> a * b);

最大值 最小值

找到最大值

Optional max = numbers.stream().reduce(Integer::max);

找到最小值

Optional min = numbers.stream().reduce(Integer::min);

元素个数

通过map将集合中的元素转换为1,然后通过累加计算流中的元素个数。

int count = numbers.stream().map(v -> 1).reduce(0, (a, b) -> a + b);

图片[4]-外部迭代与内部迭代的区别(图)代码的含义-4747i站长资讯

集电极

提供了收集结果的方法,提供了类,提供了很多收集器,基本可以满足日常的需求。如果实现不能满足要求,也可以实现自定义采集器进行采集。

柜台

示例:计算流中的员工数量

long count = emps.Stream().collect(Collectors.counting());

也可以简写为

long count = emps.Stream().count()

比较器

示例:查找最年长的员工

Optional collect = emps.stream().collect(Collectors.maxBy(Comparator.comparing(Emp::getAge)));

也可以简写为

Optional collect = emps.stream().max(Comparator.comparing(Emp::getAge));

示例:查找薪水最低的员工

Optional collect = emps.stream().collect(Collectors.minBy(Comparator.comparing(Emp::getSalary)));

也可以简写为

Optional collect = emps.stream().min(Comparator.comparing(Emp::getSalary));

聚合集合

提供了三个收集器 , , 用于对相应的数据类型求和。

示例:计算所有员工的工资总和

Double sumSalary = emps.stream().collect(Comparator.summingDouble(Emp::getSalary));

, , 三个收集器,用于平均对应的数据类型。

示例:计算员工的平均工资

Double avgSalary = emps.stream().collect(Comparator.averagingDouble(Emp::getSalary));

, , 和三个收集器用于对相应的数据类型进行统计。统计结果包括记录数、汇总值、平均值、最大值和最小值。

DoubleSummaryStatistics collect = emps.stream().collect(Collectors.summarizingDouble(Emp::getSalary));
System.out.println("员工总数:" + collect.getCount());
System.out.println("员工总薪资:" + collect.getSum());
System.out.println("员工平均薪资:" + collect.getAverage());
System.out.println("员工最高薪资:" + collect.getMax());
System.out.println("员工最低薪资:" + collect.getMin());

字符串连接收集器

相当于.join()方法,将字符串集合连接成字符串,可以指定元素之间的间隔字符。

示例:获取所有员工的姓名并输出为字符串

String names = emps.stream().map(Emp::getName).collect(Collectors.joining(","));

列表收集器

示例:获取员工姓名列表

List names = emps.Stream()
        .map(Emp::getName)
        .collect(Collectors.toList());

集收集器

通过Set本身的特性可以实现去重

示例:获取员工 ID 的集合

Set names = emps.Stream()
        .map(Emp::getId)
        .collect(Collectors.toSet());

地图收集器

示例:获取员工ID->员工信息的键值对

Map names = emps.Stream()
        .map(Emp::getName)
        .collect(Collectors.toMap(Emp::getId, Function.identity()))

如果有相同id的记录,上面的代码会出现异常,因为有key冲突,没有解决冲突的办法。其实toMap方法的第三个参数就是解决冲突的函数,如下例,如果某个key出现了冲突,则丢弃后面的元素,保留第一个。

Map names = emps.Stream()
        .map(Emp::getName)
        .collect(Collectors.toMap(Emp::getId, Function.identity(), (v1, v2) -> v1))

数据包收集器

示例:按性别分组

Map<Integer, List> sexGroup = emps.stream()
        .collect(Collectors.groupingBy(Emp::getSex));

示例:按年龄分组

Map<String, List> ageGroup = emps.stream()
        .collect(Collectors.groupingBy(v -> {
            return v.getAge() <= 35 ? "safety" : "dangerous";
        }));

多级数据包收集器

收集器还可以接收第二个参数,类型为收集器,可以理解为组内的二级收集,然后传入一个分组收集器,实现多级分组收集。

示例:第二次按性别分组,然后按年龄分组

Map<Integer, Map<String, List>> collect = emps.stream()
        .collect(Collectors.groupingBy(Emp::getSex, Collectors.groupingBy(v -> {
            return v.getAge() <= 35 ? "safety" : "dangerous";
        })));

组集合

第二个参数是收集器,可以在组内收集。

结合收集器统计组内记录数

示例:按性别查找人数的总和

Map sexCount = emps.stream()
        .collect(Collectors.groupingBy(Emp::getSex, Collectors.counting()));

结合 maxBy 收集器查找组内的最大值

示例:按性别查找年龄最大的员工

Map<Integer, Optional> maxAge = emps.stream()
        .collect(Collectors.groupingBy(Emp::getSex, Collectors.maxBy(Comparator.comparing(Emp::getAge))));

群内采集改造

提供了一个收集器。的第一个参数是一个,也就是实际执行的。第二个参数是一个转换器,用于对采集器的采集结果进行转换。

示例:按性别查找年龄最大的员工

Map maxAge = emps.stream()
        .collect(Collectors.groupingBy(Emp::getSex, 
            Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(Emp::getAge)), Optional::get)));

组内转化

提供了一个收集器,可以对组内的元素进行变换和收集

示例:获取不同性别员工姓名的集合

Map<Integer, List> collect = emps.stream()
        .collect(Collectors.groupingBy(Emp::getSex, 
            Collectors.mapping(Emp::getName, Collectors.toList())));

示例:按性别分组,将每个分组转换为 id -> name 的键值对

Map<Integer, Map> collect = emps.stream()
        .collect(Collectors.groupingBy(Emp::getSex, 
            Collectors.mapping(Function.identity(), Collectors.toMap(Emp::getId, Emp::getName))));

划分

提供一个分区收集器,与收集器类似,只是收集的Key被指定为一个类型

示例:按年龄划分,35岁以下为真,否则为假c语言字符数组逆序输出,分为两组

Map<Boolean, List> collect = emps.stream()
        .collect(Collectors.partitioningBy(v -> v.getAge() <= 35));

文章来源:http://www.toutiao.com/a7018801121716224516/

------本页内容已结束,喜欢请分享------

感谢您的来访,获取更多精彩文章请收藏本站。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享