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
由组成:
source
zero or more intermediate operations
terminal operation
。
在我们的示例中,Stream Pipeline包括: Source
: StreamList
1 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()
方法 Stream
java中的对象。
这是一个例子:
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()); } );