Java 8流API的深入教程

时间:2020-02-23 14:34:54  来源:igfitidea点击:

在本教程中,我们将看到java 8流的深入概述,其中包含很多示例和练习。

介绍

我们可能认为流必须类似于 InputStream或者 OutputStream,但情况并非如此。

一个 Stream表示支持顺序和并行聚合操作的元素序列。
Stream不存储数据,它在源数据结构上运行,例如列表,集合,阵列等。

大多数流操作接受函数接口,使其成为Lambda表达式的完美候选者。

如果我们不熟悉函数接口,Lambda表达式和方法引用,则可能希望在前进之前阅读以下教程。

Java 8 Functional interfaces
Java 8 Lambda expressions
Java 8 Method references

流操作的类型

有两种类型的流操作。

  • Intermediate operations:返回可以使用点点用其他中间操作链接的流。
  • Terminal operations:返回void或者非流输出。

让我们在简单的例子的帮助下了解。

package org.igi.theitroad.stream;
 
import java.util.Arrays;
import java.util.List;
 
public class StreamOperations {
 
    public static void main(String[] args) {
        List<String> stringList = Arrays.asList("John", "Martin", "Mary", "Steve");
 
        stringList.stream()
                   .map((s) -> s.toUpperCase())
                   .forEach(System.out::println);
    }
}

输出:

约翰马丁玛丽史蒂夫

其中为了执行计算,流操作内置于流管道中。 Stream pipeline由组成:

  • source
  • zero or more intermediate operations
  • terminal operation

在我们的示例中,Stream Pipeline包括: SourceStreamList1 Intermediate operation: 映射(Map) 1 terminal operation:foreach.

下图将更清晰。 map是中间操作和 foreach是终端中的。

大多数流操作接受参数,如描述用户定义的行为,例如lambda表达式

map((s)-&gt;s.toUpperCase())通过映射操作。

要获得正确的行为,Streams参数应该是: non-interfering:在执行流pipline时,不应修改流源。
我们可以了解有关干扰的更多信息。

Stateless:在大多数情况下,Lambda表达式应该是无状态的。
其输出不应依赖于在执行流管道期间可能会发生变化的状态。
我已经在并行流教程中涵盖了有状态λ表达。

流创造

有多种方法可以创建流。

空流 empty()方法可用于创建空流。

Stream s = Stream.empty()

它通常用于返回具有零元素而不是null的流。

收集流

可以通过呼叫从集合创建流 .stream()或者 .parallelStream()

List stringList=Arrays.asList("Andy","Peter","Amy","Mary");
 
stringList.stream()
.map((s)->s.toUpperCase())
.forEach(System.out::println);

stringList.stream()将返回常规对象流。

溪流

我们不需要创建集合来获取流。
我们也可以使用.of()

Stream streamArray =Stream.of("X","Y","Z");

Stream.generate()

生成()方法接受元素生成的供应商。

它创建无限流,我们可以通过调用limit()函数来限制它。

Stream<Integer> intStream=Stream.generate(() -> 1).limit(5);
intStream.forEach(System.out::println);
//Output
//1
//1
//1
//1
//1

这将创建具有10个元素的整数流,其中值为1.

stream.Iterate()

Stream.Iterate()也可用于生成无限流。

Stream<Integer> intStream = Stream.iterate(100 , n -> n+1).limit(5);
intStream.forEach(System.out::println);
//Output
//100
//101
//102
//103
//104

迭代方法的第一个参数表示流的第一个元素。
所有以下元素都将由Lambda表达生成 n-&gt;n+1和限制()用于将无限流转换为带有5个元素的有限流。

懒惰评估

流是惰性的;在遇到终端操作之前不会执行中间操作。

每个中间操作生成新流,存储提供的操作或者函数。
当调用终端操作时,流流水线执行开始,并且所有中间操作都是一个逐个执行的。

让我们通过示例来理解:

Stream<String> nameStream = Stream.of("mohan","john","vaibhav","amit");
Stream<String> nameStartJ = nameStream.map(String::toUpperCase)
                                    .peek( e -> System.out.println(e))
                                  .filter(s -> s.startsWith("J"));
 
System.out.println("Calling terminal operation: count");
long count = nameStartJ.count();
System.out.println("Count: "+ count);
//Output
//Calling terminal operation: count
//MOHAN
//JOHN
//VAIBHAV
//AMIT
//Count: 1

在前面的输出中,我们可以看到,除非调用终端操作计数,否则在控制台上打印任何内容。

在前面的示例中,PEEK()方法用于打印流元素。 peek()方法通常仅用于记录和调试目的。

操作顺序

让我们看看流程如何处理操作顺序。
你能猜出该计划的输出吗?

Stream<String> nameStream = Stream.of("mohan","john","vaibhav","amit");
Stream<String> nameStartJ = nameStream.map(
        (s) ->
        {
            System.out.println("Map: "+s);
            return s.toUpperCase();
 
        })
        .filter(
        (s) ->
        {
             System.out.println("Filter: "+s);
             return s.startsWith("J");
        } 
    );
 
Optional<String> findAny = nameStartJ.findAny();
System.out.println("Final output: "+findAny.get());

这里的操作顺序可能是令人惊讶的。
一种常见的方法将是对所有元素执行中间操作,然后执行下一操作,但是,每个元素垂直移动。

这种行为可以减少实际操作次数。
例如:在前面的示例中,字符串 vaibhavamit没有经历过 mapfilter我们已经得到的结果( findAny())带字符串 john

在整个集合上执行诸如排序等中的一些中间操作。

随着成功的操作可能取决于结果 sorted手术。

原始流

除了常规流,Java 8还提供了INT,LONG和DOUBLE的原始流。

原始流是:

  • INT的INTR.
  • Longstream长期
  • Doubleestream for Double.

所有原始流类似于常规流,具有以下差异。

  • 它支持很少的终端聚合函数 sum()average(), ETC。
  • 它接受专门的函数接口,如intpriedicate而不是谓词,而不是消费者。

这是一个Intrem的示例。

int sum = Arrays.stream(new int[] {1,2,3})
                .sum();
System.out.println(sum);
 
//Output 
//6

将流转换为Intrem

我们可能需要将流转换为IntStrem以执行终端聚合操作,例如SUM或者平均值。
我们可以使用 mapToInt()mapToLong()或者 mapToDouble()将流转换为原始流的方法。
这是一个例子:

Stream.of("10","20","30")
      .mapToInt(Integer::parseInt)
      .average()
      .ifPresent(System.out::println);
//Output
//20.0

将INTSTRSTROVER传输到流

我们可能需要将IntStream转换为流以将其用作任何其他数据类型。
我们可以使用 mapToObj()将原始流转换为常规流。
这是一个例子:

String collect = IntStream.of(10,20,30)
                          .mapToObj((i)->""+i)
                          .collect(Collectors.joining("-"));
System.out.println(collect);
//Output
//10-20-30

Employee程序

考虑A. Employee类有两个字段 nameagelistOfCities

这里 listOfCities表示员工到目前为止居住的城市。

package org.igi.theitroad.stream;
 
import java.util.List;
 
public class Employee implements Comparable<Employee>{
 
    private String name;
    private int age;
    private List<String> listOfCities;
 
    public Employee(String name, int age,List<String> listOfCities) {
        super();
        this.name = name;
        this.age = age;
        this.listOfCities=listOfCities;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public List<String> getListOfCities() {
        return listOfCities;
    }
 
    public void setListOfCities(List<String> listOfCities) {
        this.listOfCities = listOfCities;
    }
 
    @Override
    public String toString() {
        return "Employee [name=" + name + ", age=" + age + "]";
    }
 
    @Override
    public int compareTo(Employee o) {
        return this.getName().compareTo(o.getName());
    }
}

这个 Employee类将在所有后续示例中使用。
让我们创造 employeesList我们将执行中级和终端操作。

package org.igi.theitroad.stream;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
public class StreamGetListOfEmployees {
 
    public static void main(String[] args) {
        List<Employee> employeesList=getListOfEmployees();
 
        //Write stream code here
    }
 
    public static List<Employee> getListOfEmployees() {
 
        List<Employee> listOfEmployees = new ArrayList<>();
 
        Employee e1 = new Employee("Mohan", 24,Arrays.asList("Newyork","Banglore"));
        Employee e2 = new Employee("John", 27,Arrays.asList("Paris","London"));
        Employee e3 = new Employee("Vaibhav", 32,Arrays.asList("Pune","Seattle"));
        Employee e4 = new Employee("Amit", 22,Arrays.asList("Chennai","Hyderabad"));
 
        listOfEmployees.add(e1);
        listOfEmployees.add(e2);
        listOfEmployees.add(e3);
        listOfEmployees.add(e4);
 
        return listOfEmployees;
    }
}

常见的intemediate操作

Map()

Map()操作用于将Stream <T>转换为流<R>。

它产生类型的一个输出结果 'R' 对于类型的每个输入值 'T' 。
它将函数接口作为参数。

例如:我们有员工列表流,我们需要一份员工名称列表,我们只需转换 Stream&lt;Employee>Stream&lt;String>

List<String> employeeNames = employeesList.stream()
                                .map(e -> e.getName())
                               .collect(Collectors.toList());
System.out.println(employeeNames);
 
//Output
//[Mohan, John, Vaibhav, Amit]

映射(Map)操作的逻辑表示

我们也可以使用映射(Map),即使它会产生相同类型的结果。
如果,我们希望以大写的员工姓名,可以使用另一个 map()函数要将字符串转换为大写。

List<String> employeeNames = employeesList.stream()
                                .map(e -> e.getName())
                                .map(s -> s.toUpperCase())
                                .collect(Collectors.toList());
System.out.println(employeeNames);
 
//Output
//[MOHAN, JOHN, VAIBHAV, AMIT]

Filter()

Filter()操作用于基于条件过滤流。

筛选方法采用谓词()接口返回布尔值。
让我们说你想要名字以'A'开头的员工。
我们可以编写以下函数代码以实现相同的代码。

List<String> employeeNames = employeesList.stream()
                                .map(e -> e.getName())
                                .filter(s -> s.startsWith("A"))
                               .collect(Collectors.toList());
System.out.println(employeeNames);
 
//Output
//[AMIT]

sorted()

我们可以使用sorted()方法来对对象进行排序。

没有参数的排序方法在自然顺序中排序列表。
Sorted()方法还接受比较器作为参数以支持自定义排序。

自然顺序是指根据列表元素类型实现的可比较接口对列表进行排序。
例如:
List<Integer>将根据Integer类实现的可比较接口进行排序。

以下是sorted()方法示例

List<Employee> employees = employeesList.stream()
                                            .sorted()
                                            .collect(Collectors.toList());
System.out.println(employees);
 
//Output
//[Employee [name=Amit, age=22], Employee [name=John, age=27], Employee [name=Mohan, age=24], Employee [name=Vaibhav, age=32]]

这里是 sorted()使用比较器作为参数的方法示例。

List<Employee> employees = employeesList.stream()
                              .sorted((e1,e2)->e1.getAge() - e2.getAge())
                               .collect(Collectors.toList());
System.out.println(employees);
 
//Output
//[Employee [name=Amit, age=22], Employee [name=Mohan, age=24], Employee [name=John, age=27], Employee [name=Vaibhav, age=32]]

我们还可以使用以下方法参考重写此方法:

List<Employee> employees = employeesList.stream()
                                                .sorted(Comparator.comparing(Employee::getAge))
                                                .collect(Collectors.toList());
System.out.println(employees);
 
//Output
//[Employee [name=Amit, age=22], Employee [name=Mohan, age=24], Employee [name=John, age=27], Employee [name=Vaibhav, age=32]]

limit()

我们可以使用limit()来限制流中的元素数。
例如: limit(3)返回列表中的前3个元素。

让我们在一个例子的帮助下看看:

List<Employee> employees = employeesList.stream()
                                     .limit(3)
                                  .collect(Collectors.toList());
System.out.println(employees);
 
//Output
//[Employee [name=Mohan, age=24], Employee [name=John, age=27], Employee [name=Vaibhav, age=32]]

skip()

skip(int n)方法用于从流中丢弃第一个n个元素。
例如: skip(3)从流中丢弃前3个元素。

让我们随着示例的帮助查看:

List<Employee> employees = employeesList.stream()
                                     .skip(3)
                                  .collect(Collectors.toList());
System.out.println(employees);
 
//Output
//[Employee [name=Amit, age=22]]

flatmap()

map()操作为每个输入元素生成一个输出。

如果我们对每个输入有多个输出,该怎么办?
为此目的完全使用Flatmap()操作。

它用于为每个输入映射多个输出。
例如:我们希望累积所有员工居住的城市列表。
一名员工可以住在多个城市中,以便我们可能拥有每个员工的多个城市。

让我们随着示例的帮助查看:

List<String> listOfCities = employeesList.stream()
                                           .flatMap(e -> e.getListOfCities().stream())
                                           .collect(Collectors.toList());
 
System.out.println("listOfCities: " +listOfCities);
 
//Output
//listOfCities: [Newyork, Banglore, Paris, London, Pune, Seattle, Chennai, Hyderabad]

通用终端操作

Foreach.

foreach()是终端操作,用于迭代对象集合/流。

它将消费者作为参数。

让我们说要打印流的元素。

employeesList.stream()
             .forEach(System.out::println);
 
//Output
//Employee [name=Mohan, age=24]
//Employee [name=John, age=27]
//Employee [name=Vaibhav, age=32]
//Employee [name=Amit, age=22]

collect

collect()终端操作是使用收集器对流元素进行可变减少的终端操作。
Collector 是提供内置收集器的实用类。

例如: Collectors.toList()提供一个收集器,将流转换为列表对象。
以下代码将员工名称累积到ArrayList中

List<String> employeeNames = employeesList.stream()
                                          .map(Employee::getName)
                                          .collect(Collectors.toList());
System.out.println(employeeNames);
 
//Output
//[Mohan, John, Vaibhav, Amit]

Reduce

减少操作结合了流的所有元素并产生单一结果。
Java 8有三种重载版本的减少方法。

  • Optional&lt;T&gt; reduce(BinaryOperator&lt;T&gt; accumulator):这种方法需要 BinaryOperator累加器函数。 BinaryOperatorBiFunction两个操作数都是相同类型的地方。 First参数是TAGE TANT CHIRE执行,第二个参数是流的当前元素。
    让我们找到最低年龄的人的名称。
employeesList.stream()
.reduce( (e1,e2)-> (e1.getAge() < e2.getAge()? e1:e2))
.ifPresent(System.out::println);
//Output
//Employee [name=Amit, age=22]
  • T reduce(T identity, BinaryOperator&lt;T> accumulator):此方法采用身份值和累加器函数。身份值是减少的初始值。如果流为空,则结果值是结果。
    让我们找到所有年龄段的员工的总和
int sumAge = employeesList.stream()
.mapToInt(Employee::getAge)
.reduce(0, (age1,age2)-> (age1 + age2));
 
System.out.println("Sum of ages of all Employees: "+sumAge);
//Output
//Sum of ages of all Employees: 105
  • &lt;U&gt; U reduce(U identity, BiFunction&lt;U,? super T,U&gt; accumulator, BinaryOperator&lt;U&gt; combiner):该方法采用身份值和累加器函数和组合器。在并行流的情况下,组合器是Mainy。

Combiner Comibnes Combine并行运行的子流的结果。

count

count()用于计数流中的元素数。

long empCountStartJ = employeesList.stream()
                                   .map(Employee::getName)
                                   .filter(s -> s.startsWith("J"))
                                   .count();
System.out.println(empCountStartJ);
 
//Output
//1

Allmatch()

allMatch()当流中的所有元素满足提供条件时,返回true。

这是一种短路终端操作,因为一旦遇到任何不匹配的元素,操作就会停止。

boolean allMatch = employeesList.stream()
                                .allMatch(e ->e.getAge()>18);
 
System.out.println("Are all the employess adult: " +allMatch);
 
//Output
//Are all the employess adult: true

nonmatch()

nonMatch()当流中的所有元素不符合提供的条件时返回true。

这是一个短路终端操作,因为操作一旦遇到任何匹配的元素就会停止。

boolean noneMatch = employeesList.stream()
                                 .noneMatch(e ->e.getAge()>60);
 
System.out.println("Are all the employess below 60: " +noneMatch);
 
//Output
//Are all the employess below 60: true

AnyMatch()

anyMatch()当流中的任何元素满足提供条件时返回true。

这是一个短路终端操作,因为操作一旦遇到任何匹配的元素就会停止。

boolean anyMatch = employeesList.stream()
                                 .anyMatch(e ->e.getAge()>30);
 
System.out.println("is any employee's age greater than 30: " +anyMatch);
 
//Output
//is any employee's age greater than 30: true

MIN()

min(Comparator)基于所提供的比较器返回流中的最小元素。
它返回包含实际值的对象。

Optional<Employee> minEmpOpt = employeesList.stream()
                                            .min(Comparator.comparing(Employee::getAge));
 
Employee minAgeEmp = minEmpOpt.get();
System.out.println("Employee with minimum age is: " +minAgeEmp);
 
//Output
//Employee with minimum age is: Employee [name=Amit, age=22]

max()

max(Comparator)基于所提供的比较器返回流中的最大元素。
它返回包含实际值的对象。

Optional<Employee> maxEmpOpt = employeesList.stream()
                                            .max(Comparator.comparing(Employee::getAge));
 
Employee maxAgeEmp = maxEmpOpt.get();
System.out.println("Employee with maxium age is: " +maxAgeEmp);
 
//Output
//Employee with maxium age is: Employee [name=Vaibhav, age=32]

并行流

我们可以使用并行流 .parallel()方法 Streamjava中的对象。
这是一个例子:

int[] array= {1,2,3,4,5};
 
System.out.println("=================================");
System.out.println("Using Parallel Stream");
System.out.println("=================================");
IntStream intParallelStream=Arrays.stream(array).parallel();
intParallelStream.forEach((s)->
{
    System.out.println(s+" "+Thread.currentThread().getName());
}
);