Java 8流API的深入教程
在本教程中,我们将看到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由组成:
sourcezero or more intermediate operationsterminal operation。
在我们的示例中,Stream Pipeline包括: Source: StreamList1 Intermediate operation: 映射(Map) 1 terminal operation:foreach.
下图将更清晰。 map是中间操作和 foreach是终端中的。
大多数流操作接受参数,如描述用户定义的行为,例如lambda表达式
map((s)->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->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());
这里的操作顺序可能是令人惊讶的。
一种常见的方法将是对所有元素执行中间操作,然后执行下一操作,但是,每个元素垂直移动。
这种行为可以减少实际操作次数。
例如:在前面的示例中,字符串 vaibhav和 amit没有经历过 map和 filter我们已经得到的结果( 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类有两个字段 name那 age那 listOfCities。
这里 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<Employee>到 Stream<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<T> reduce(BinaryOperator<T> accumulator):这种方法需要BinaryOperator累加器函数。BinaryOperator是BiFunction两个操作数都是相同类型的地方。 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<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
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> 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());
}
);

