优雅地操作 List、Map

2020-03-17  

集合是 Java 中使用最多的 API,但在 Java 中集合操作却不像 SQL 语句那样简洁明了;想要用并行架构处理大量集合元素,也很困难。

为了解决这个问题,Java 8 API 中添加了新的成员 - 流(Stream)。

它允许以声明性方式(即通过类似查询语句来表达,而不是编写一个实现类)处理数据集合;还可以不写任何多线程代码进行透明地并行处理。

 

流的作用:从支持数据处理操作的源,生成的元素序列。

特点:

  • 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。
  • 内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
  • 和迭代器类似,流只能遍历一次

 

Stream 接口的使用

Java 8 中的 java.util.Collection 接口,支持 2 个新方法:stream()、parallelStream(),返回一个流(接口定义在 java.util.stream.Stream 里)。

 

流的使用包括 3 个元素:

  • 数据源,如集合
  • 中间操作链,形成一条流的流水线
  • 终端操作,执行流水线,并能生成结果

 

中间操作:返回另一个流,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理,中间操作一般都可以合并起来,在终端操作时一次性全部处理

操作       返回类型      操作参数                   函数描述符        作用
filter    Stream<T>    Predicate<T>              T -> boolean    过滤元素
map       Stream<T>    Function<T, R>            T -> R          映射,将元素转换成其他形式或提取信息
flatMap   Stream<T>    Function<T, Stream<R>>    T -> Stream<R>  扁平化流映射
limit     Stream<T>    long                                      截断流,使其元素不超过给定数量
skip      Stream<T>    long                                      跳过指定数量的元素
sorted    Stream<T>    Comparator<T>             (T, T) -> int   排序
distinct  Stream<T>                                              去重

 

终端操作:从流的流水线生成结果

操作        返回类型      操作参数                   函数描述符        作用
anyMatch   boolean      Predicate<T>              T -> boolean    检查流中是否有一个元素能匹配给定的谓词
allMatch   boolean      Predicate<T>              T -> boolean    检查谓词是否匹配所有元素
noneMatch  boolean      Predicate<T>              T -> boolean    检查是否没有任何元素与给定的谓词匹配
findAny    Optional<T>                                            返回当前流中的任意元素(用于并行的场景)
findFirst  Optional<T>                                            查找第一个元素
collect    R            Collector<T, A, R>                        把流转换成其他形式,如集合 List、Map、Integer
forEach    void         Consumer<T>               T -> void       消费流中的每个元素并对其应用 Lambda,返回 void
reduce     Optional<T>  BinaryOperator<T>         (T, T) -> T     归约,如:求和、最大值、最小值
count      long                                                   返回流中元素的个数


 

构建流的方式

由值创建流

Stream<String> stream = Stream.of("a", "b", "c", "d");

 

由数组创建流

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

 

由文件生成流

long uniqueWords = 0;
Stream<String> lines = Files.lines(Paths.get("a.txt"), Charset.defaultCharset())){
								uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
								.distinct()
								.count();
	

 

由函数生成流,创建无限流

Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
Stream.generate(Math::random).limit(5).forEach(System.out::println);

 

为了避免了暗含的装箱成本,Java 8 还是推出了IntStream、DoubleStream和 LongStream,分别将流中的元素特化为int、long和double,将原始类型流特化。

映射到数值流

double weight = list.stream()
                    .mapToDouble(Fruit::getWeight)
                	.sum();

 

转换回对象流

DoubleStream doubleStream = list.stream().mapToDouble(Fruit::getWeight);
Streaing<Double> stream = doubleStream.boxed();

 

数值范围流,产生 1-100 之间的偶数

IntStream evenNumbers = IntStream.rangeClosed(1, 100)
                                    .filter(n -> n % 2 == 0);

 

 

使用示例

package constxiong.interview;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.OptionalDouble;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * 测试 Stream 的API
 * @author ConstXiong
 */
public class TestStreamAPI {
	
	public static void main(String[] args) {
		List<Fruit> list = Arrays.asList(new Fruit("苹果", "红色", 1.1),
				new Fruit("苹果", "绿色", 1.7),
				new Fruit("香蕉", "黄色", 0.8),
				new Fruit("香蕉", "绿色", 1.1),
				new Fruit("橘子", "橙色", 0.7));
		
		//获取颜色为绿色的水果的类别,按照重量排序
		System.out.println(list.stream()
				.filter(f -> "绿色".equals(f.getColor()))
				.sorted(comparing(Fruit::getWeight))
				.map(Fruit::getType)
				.collect(toList()));
		
		//利用多核架构并行执行这段代码,你只需要把stream()换成parallelStream()
		//获取颜色为绿色的水果的类别,按照重量排序
		System.out.println(list.parallelStream()
				.filter(f -> "绿色".equals(f.getColor()))
				.sorted(comparing(Fruit::getWeight))
				.map(Fruit::getType)
				.collect(toList()));
		
		//打印流的执行过程
		list.parallelStream()
				.filter(f -> {
					System.out.println("filter:" + f);
					return "绿色".equals(f.getColor());
				})
				.map(f -> {
					System.out.println("map:" + f);
					return f.getType();
				})
				.collect(toList());
		
		//按重量过滤,排序,取前 2 个水果
		System.out.println(list.stream()
				.filter(f -> f.getWeight() > 1)
				.sorted(comparing(Fruit::getWeight))
				.limit(2)
				.collect(toList()));
		
		//按水果类型分组
		Map<String, List<Fruit>> map = list.stream().collect(groupingBy(Fruit::getType));
		System.out.println(map);
		
		//打印水果重量合计
		double weight = list.stream()
                			.mapToDouble(Fruit::getWeight)
                			.sum();
		System.out.println(weight);
		
		//转换回对象流
		DoubleStream doubleStream = list.stream().mapToDouble(Fruit::getWeight);
		Stream<Double> stream = doubleStream.boxed(); //包装成Double流
		
		//打印水果的最重重量
		OptionalDouble maxWeight = list.stream()
			    			.mapToDouble(Fruit::getWeight)
			    			.max();
		System.out.println(maxWeight.getAsDouble());
		
		//数值范围流,产生 1-100 之间的偶数
		IntStream evenNumbers = IntStream.rangeClosed(1, 100)
							.filter(n -> n % 2 == 0);
		evenNumbers.forEach(System.out::println);
	}
}

 

参考:《Java8实战》

 

ConstXiong 备案号:苏ICP备16009629号-3