![cover](https://blog-1312110814.cos.ap-shanghai.myqcloud.com/images/2023-1-25%2014%3A18%3A571674627537858.png)
Java8 Stream 流操作
什么是Java 8 Stream?
Java stream API定义了数据元素离散序列上的函数式操作。这些数据元素通常由一些标准数据结构 (ADT) 提供,例如java.util.collection中提供的数据。这些数据结构通常称为Stream源。
Java 8增强了现有的API,例如集合,数组等,以添加新方法来创建流对象实例。这个API本身提供了一些静态的方法来生成有限/无限的数据元素流。
Stream是功能性的,它们在提供的源上运行并产生结果,而不是修改源。
Stream生命周期可以分为三种类型的操作
- 从数据源获取Stream的实例。数据源可能是数组、集合、生成器函数、I/O通道等
- 将Stream转换为另一个Stream的0..N个中间操作,例如过滤,排序,元素转换 (映射)
- 产生结果的终端操作,例如计数,汇总或新的集合。
![](https://blog-1312110814.cos.ap-shanghai.myqcloud.com/images/2023-1-25%2014%3A18%3A391674627519422.png)
![](https://blog-1312110814.cos.ap-shanghai.myqcloud.com/images/2023-1-25%2014%3A18%3A571674627537858.png)
Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
//获取按英文名称首字母(小写)分组下,字母最多的
Comparator<Employee> byNameLength = Comparator.comparing(Employee::getName);
Map<Character, Optional<Employee>> longestNameByAlphabet = empList.stream() //得到stream对象
.peek(employee -> employee.setName(employee.getName().toLowerCase())) //中间操作
.collect( //结束操作
Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
Collectors.reducing(BinaryOperator.maxBy(byNameLength)))
);
{b=Optional[Id: 2 Name:bill gates Price:200000.0],j=Optional[Id: 1 Name:jeff bezos Price:100000.0],m=Optional[Id: 3 Name:mark zuckerbergPrice:300000.0]}
如何得到Stream对象
Comparator<Employee> byNameLength = Comparator.comparing(Employee::getName);
Map<Character, Optional<Employee>> longestNameByAlphabet = empList.stream()
.sort
.collect(
Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
Collectors.reducing(BinaryOperator.maxBy(byNameLength))));
Java 8修改了现有的集合和其他数据结构API来创建/生成Stream实例:
收集接口添加了新的默认方法,如stream() 和parallelStream(),它们返回Stream实例。
List<String> list = Arrays.asList("1", "2", "3");
Stream<String> stream = list.stream();
在Arrays类中添加了生成流实例的新方法。
String[] strs = {"1", "2", "3"};
Stream<String> stream = Arrays.stream(strs);
Stream.generate()自定义流
Stream.generate(Math::random);
递归生成
Stream<Integer> infiniteStream = Stream.iterate(2, i -> i * 2); //生成2的N次方数据
中间操作
数据源
private static Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
private static List<Employee> empList = Arrays.asList(arrayOfEmps);
private static EmployeeRepository employeeRepository = new EmployeeRepository(empList);
peek()对元素内部调整
/**
* peek()函数用法,对元素内部数据进行调整时使用
* 如每个员工涨薪10%
*/
@Test
public void whenIncrementSalaryUsingPeek_thenApplyNewSalary() {
Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream()
.peek(System.out::println) //调整前
.peek(e -> e.salaryIncrement(10.0))
.peek(System.out::println)//调整后
//将前面操作后的对象重新包装为新的List集合返回
.collect(Collectors.toList());
}
运行结果:
Id: 1 Name:Jeff Bezos Price:100000.0
Id: 1 Name:Jeff Bezos Price:110000.0
Id: 2 Name:Bill Gates Price:200000.0
Id: 2 Name:Bill Gates Price:220000.0
Id: 3 Name:Mark Zuckerberg Price:300000.0
Id: 3 Name:Mark Zuckerberg Price:330000.0
map()映射转换
/**
* map函数的使用,用于将元素本身进行加工或类型转换
* 例如:遍历所有id对象,并在数据库查询导数据Employee对象,返回的是List<Employee>集合
* map()与peek()最大的区别是:
* peek()不改变对象本身,如进入的原始数据是List<Integer>,返回的还是List<Integer>
* map()则用于转换工作,如进入的原始数据是List<Integer>,返回的数据类型被转为List<Employee>
*/
@Test
public void whenMapIdToEmployees_thenGetEmployeeStream() {
Integer[] empIds = {1, 2, 3};
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.collect(Collectors.toList());
assertEquals(employees.size(), empIds.length);
}
flatMap()展平结果
/**
* flatMap()用于展平结果,将多级结构转换为平铺结构
*/
@Test
public void whenFlatMapEmployeeNames_thenGetNameStream() {
List<List<String>> namesNested = Arrays.asList(
Arrays.asList("Jeff", "Bezos"),
Arrays.asList("Bill", "Gates"),
Arrays.asList("Mark", "Zuckerberg"));
List<String> namesFlatStream = namesNested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(namesFlatStream);
}
// -----------------------
[Jeff, Bezos, Bill, Gates, Mark, Zuckerberg]
filter()过滤流数据
/**
* filter()用于过滤数据,符合条件的数据保留,向后流处理,不符合条件的数据被筛选掉
* 这里得到薪资>200000的所有员工对象
*/
@Test
public void whenFilterEmployees_thenGetFilteredStream() {
Integer[] empIds = {1, 2, 3, 4};
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 200000)
.collect(Collectors.toList());
System.out.println(employees);
}
偏移量运算,skip()跳过三个,limit()向后获取5个
/**
* 偏移量运算,skip跳过三个,limit向后获取5个
*/
@Test
public void whenLimitInfiniteStream_thenGetFiniteElements() {
//iterate迭代器方法用于生成流的规则,生成以为2的N次方数据
Stream<Integer> infiniteStream = Stream.iterate(2, i -> i * 2);
System.out.println(infiniteStream);
List<Integer> collect = infiniteStream
.skip(3)//跳过2^1,2^2,2^3
.limit(5)//保留2^4、2^5、2^6、2^7、2^8
.collect(Collectors.toList());//转换为数字
System.out.println(collect);
}
// ---------------------
[16, 32, 64, 128, 256]
sorted()数据排序
/**
* sorted()数据排序,e1、e2比较。e1-e2>0升序排列,e2-e1<0降序排列
*/
@Test
public void whenSortStream_thenGetSortedStream() {
List<Employee> employees = empList.stream()
.sorted((e1, e2) -> e1.getName().compareTo(e2.getName()))
.collect(Collectors.toList())
assertEquals(employees.get(0).getName(), "Bill Gates");
assertEquals(employees.get(1).getName(), "Jeff Bezos");
assertEquals(employees.get(2).getName(), "Mark Zuckerberg");
}
distinct()去重
/**
* distinct()去重函数
*/
@Test
public void whenApplyDistinct_thenRemoveDuplicatesFromStream() {
List<Integer> intList = Arrays.asList(2, 5, 3, 2, 4, 3);
List<Integer> distinctIntList = intList.stream().distinct().collect(Collectors.toList());
System.out.println(distinctIntList);
}
match()规则匹配返回Boolean
/**
* allMatch()判断给定的流数据是否全部符合判断要求,全部满足返回true
* anyMatch()判断给定的流数据是否存在符合要求的数据,只要有一个满足则返回true
* noneMatch()与anyMatch()相反,只要有一个满足要求则返回false
*/
@Test
public void whenApplyMatch_thenReturnBoolean() {
List<Integer> intList = Arrays.asList(2, 4, 5, 6, 8);
boolean allEven = intList.stream().allMatch(i -> i % 2 == 0);
boolean oneEven = intList.stream().anyMatch(i -> i % 2 == 0);
boolean noneMultipleOfThree = intList.stream().noneMatch(i -> i % 3 == 0);
assertEquals(allEven, false);
assertEquals(oneEven, true);
assertEquals(noneMultipleOfThree, false);
}
parallel()并行处理
/**
* parallel()并行处理
*/
@Test
public void whenParallelStream_thenPerformOperationsInParallel() {
Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream().parallel().forEach(e -> e.salaryIncrement(10.0));
assertThat(empList, contains(
hasProperty("salary", equalTo(110000.0)),
hasProperty("salary", equalTo(220000.0)),
hasProperty("salary", equalTo(330000.0))
));
}
结束操作
forEach()替代for循环
/**
* 利用forEach替代for循环进行遍历
* 为每一位员工增加涨薪10%
*/
@Test
public void whenIncrementSalaryForEachEmployee_thenApplyNewSalary() {
Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream().forEach(e -> e.salaryIncrement(10.0));
}
findFirst()获取首个流数据
/**
* findFirst()获取第一个符合规则的流数据
* 如果一个都没有则走orElse()返回Null
*/
@Test
public void whenFindFirst_thenGetFirstEmployeeInStream() {
Integer[] empIds = {1, 2, 3, 4};
Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);
System.out.println(employee);
}
collect()用于集合类型转换
/**
* collect()结尾函数,用于集合类型转换
* 例如将前面的数据转换为Set集合
*/
@Test
public void whenCollectStreamToList_thenGetList() {
Set<Employee> employees = empList.stream().collect(Collectors.toSet());
System.out.println(employees);
}
toArray(),将流式数据转换为数组
/**
* toArray(),结尾函数,将流式数据转换为数组
*/
@Test
public void whenStreamToArray_thenGetArray() {
Employee[] employees = empList.stream().toArray(Employee[]::new);
for (Employee employee: employees){
System.out.println(employee);
}
}
count()统计满足条件数据的总量
/**
* count()结尾函数,统计满足条件数据的总量
*/
@Test
public void whenStreamCount_thenGetElementCount() {
Long empCount = empList.stream()
.filter(e -> e.getSalary() > 200000)
.count();
System.out.println(empCount);
}
max()/min()取最大值与最小值
/**
* 对于数字类型,不必给条件,如下max()
*/
@Test
public void whenFindMaxOnIntStream_thenGetMaxInteger() {
Integer latestEmpId = empList.stream()
.mapToInt(Employee::getId)//只获取Id类型,取最大的id号
.max()
.orElseThrow(NoSuchElementException::new);
System.out.println(latestEmpId);
}
// ----------------------------------------
/**
* max()获取流中最大值
* orElseThrow()代表没有符合的结果时抛出NoSuchElementException异常
*/
@Test
public void whenFindMax_thenGetMaxElementFromStream() {
Employee maxSalEmp = empList.stream()
.max(Comparator.comparing(Employee::getSalary))
.orElseThrow(NoSuchElementException::new);
System.out.println(maxSalEmp);
}
// --------------------------------------------
/**
* min()获取流中最小值
* orElseThrow()代表没有符合的结果时抛出NoSuchElementException异常
*/
@Test
public void whenFindMin_thenGetMinElementFromStream() {
Employee minEmp = empList.stream()
.min((e1, e2) -> e1.getId() - e2.getId())
.orElseThrow(NoSuchElementException::new);
System.out.println(minEmp);
}
average()求平均数
/**
* average()求平均数运算,要求是流数据类型为数字类型
* 没有匹配抛出NoSuchElementException异常
*/
@Test
public void whenApplySumOnIntStream_thenGetSum() {
Double avgSal = empList.stream()
.mapToDouble(Employee::getSalary)//只获取工资数据
.average()
.orElseThrow(NoSuchElementException::new);
System.out.println(avgSal);
}
reduce()按规则聚合结果
public class Main {
public static void main(String[] args) {
int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.reduce(0, (acc, n) -> acc + n);
System.out.println(sum); // 45
}
}
翻译过来的伪代码
//stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
int sum = 0;
for (n : stream) {
sum = sum + n;
}
// -------------------
@Test
public void whenApplyReduceOnStream_thenGetValue() {
Double sumSal = empList.stream()
.map(Employee::getSalary)
.reduce(0.0, Double::sum);//起始值为0,每次对所有流元素进行累加
assertEquals(sumSal, new Double(600000));
}
Collectors.joining()创建长字符串
/**
* collect(Collectors.joining(", ")) 将流数据转为一个长字符串
*/
@Test
public void whenCollectByJoining_thenGetJoinedString() {
String empNames = empList.stream()
.map(Employee::getName)
.collect(Collectors.joining(", "))
.toString();
assertEquals(empNames, "Jeff Bezos, Bill Gates, Mark Zuckerberg");
}
Collectors.summarizing...(聚合运算)
/**
* 流数据为对象时,聚合运算.
*/
@Test
public void whenApplySummarizing_thenGetBasicStats() {
DoubleSummaryStatistics stats = empList.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
assertEquals(stats.getCount(), 3); //数量
assertEquals(stats.getSum(), 600000.0, 0);//求和
assertEquals(stats.getMin(), 100000.0, 0);//最小值
assertEquals(stats.getMax(), 300000.0, 0);//最大值
assertEquals(stats.getAverage(), 200000.0, 0);//平均值
}
/**
* 流数据为数值时,直接聚合运算
*/
@Test
public void whenApplySummaryStatistics_thenGetBasicStats() {
DoubleSummaryStatistics stats = empList.stream()
.mapToDouble(Employee::getSalary)
.summaryStatistics();
assertEquals(stats.getCount(), 3);
assertEquals(stats.getSum(), 600000.0, 0);
assertEquals(stats.getMin(), 100000.0, 0);
assertEquals(stats.getMax(), 300000.0, 0);
assertEquals(stats.getAverage(), 200000.0, 0);
}
Collectors.partitioningBy()分区运算
/**
* partitioningBy()分区运算,类似于SQL中的Group By ,并将其放入各自的Map's value中
*/
@Test
public void whenStreamPartition_thenGetMap() {
List<Integer> intList = Arrays.asList(2, 4, 5, 6, 8);
Map<Boolean, List<Integer>> isEven = intList.stream().collect(
Collectors.partitioningBy(i -> i % 2 == 0));
assertEquals(isEven.get(true).size(), 4);//偶数组
assertEquals(isEven.get(false).size(), 1);//奇数组
}
// -------------------------------
/**
* 按自定义条件进行分区(Group By)
*/
@Test
public void whenStreamGroupingBy_thenGetMap() {
//按首字母进行分区
Map<Character, List<Employee>> groupByAlphabet = empList.stream().collect(
Collectors.groupingBy(e -> new Character(e.getName().charAt(0))));
assertEquals(groupByAlphabet.get('B').get(0).getName(), "Bill Gates");
assertEquals(groupByAlphabet.get('J').get(0).getName(), "Jeff Bezos");
assertEquals(groupByAlphabet.get('M').get(0).getName(), "Mark Zuckerberg");
}
// -------------------------------
/**
* groupingBy高级玩法,先按首字母分区,然后将分区内数据转换为ID'List
*/
@Test
public void whenStreamMapping_thenGetMap() {
Map<Character, List<Integer>> idGroupedByAlphabet = empList.stream().collect(
Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
Collectors.mapping(Employee::getId, Collectors.toList())));
assertEquals(idGroupedByAlphabet.get('B').get(0), new Integer(2));
assertEquals(idGroupedByAlphabet.get('J').get(0), new Integer(1));
assertEquals(idGroupedByAlphabet.get('M').get(0), new Integer(3));
}
// ---------------------------------
@Test
public void whenStreamReducing_thenGetValue() {
Double percentage = 10.0;
Double salIncrOverhead = empList.stream().collect(Collectors.reducing(
0.0, e -> e.getSalary() * percentage / 100, (s1, s2) -> s1 + s2));
assertEquals(salIncrOverhead, 60000.0, 0);
}
// ---------------------------------
@Test
public void whenStreamGroupingAndReducing_thenGetMap() {
Comparator<Employee> byNameLength = Comparator.comparing(Employee::getName);
Map<Character, Optional<Employee>> longestNameByAlphabet = empList.stream().collect(
Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
Collectors.reducing(BinaryOperator.maxBy(byNameLength))));
assertEquals(longestNameByAlphabet.get('B').get().getName(), "Bill Gates");
assertEquals(longestNameByAlphabet.get('J').get().getName(), "Jeff Bezos");
assertEquals(longestNameByAlphabet.get('M').get().getName(), "Mark Zuckerberg");
}
其他与异常处理orElse、orElseThrow
@Test
public void whenFindFirst_thenGetFirstEmployeeInStream() {
Integer[] empIds = {1, 2, 3, 4};
Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);
System.out.println(employee);
}
// ---------------------------------
@Test
public void whenApplySumOnIntStream_thenGetSum() {
Double avgSal = empList.stream()
.mapToDouble(Employee::getSalary)//只获取工资数据
.average()
.orElseThrow(NoSuchElementException::new);
System.out.println(avgSal);
}
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果