什么是Java 8 Stream?

Java stream API定义了数据元素离散序列上的函数式操作。这些数据元素通常由一些标准数据结构 (ADT) 提供,例如java.util.collection中提供的数据。这些数据结构通常称为Stream源。

Java 8增强了现有的API,例如集合,数组等,以添加新方法来创建流对象实例。这个API本身提供了一些静态的方法来生成有限/无限的数据元素流。

Stream是功能性的,它们在提供的源上运行并产生结果,而不是修改源。

Stream生命周期可以分为三种类型的操作

  1. 从数据源获取Stream的实例。数据源可能是数组、集合、生成器函数、I/O通道等
  2. 将Stream转换为另一个Stream的0..N个中间操作,例如过滤,排序,元素转换 (映射)
  3. 产生结果的终端操作,例如计数,汇总或新的集合。
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);
}