Java8Stream API

Stream (java.util.stream.*)是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

Stream和集合

流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算! ”

  1. Stream 自己不会存储元素。
  2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

如何操作Stream

 创建 Stream:一个数据源(如: 集合、数组), 获取一个流
 中间操作:一个中间操作链,对数据源的数据进行处理
 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果

创建 Stream

方式1:Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

 default Stream<E> stream() : 返回一个顺序流
 default Stream<E> parallelStream() : 返回一个并行流

// 1. Collection 提供了两个方法 stream() 与 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); // 获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); // 获取一个并行流

方式2:Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

 static <T> Stream<T> stream(T[] array): 返回一个流

// 2. 通过 Arrays 中的 stream() 获取一个数组流
Integer[] nums = new Integer[10];
Stream<Integer> stream1 = Arrays.stream(nums);

重载形式,能够处理对应基本类型的数组:

 public static IntStream stream(int[] array)
 public static LongStream stream(long[] array)
 public static DoubleStream stream(double[] array)

方式3:可以使用Stream提供的静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。

// 3. 通过 Stream 类中静态方法 of()
Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6);

方式4:可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。

 迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f):seed表示一个起始值

 生成
public static<T> Stream<T> generate(Supplier<T> s) 

// 4. 创建无限流
// 迭代
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream3.forEach(System.out::println);

// 生成
Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
stream4.forEach(System.out::println);

无限流会无限的创建,需要指定筛选条件。

Stream中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” 。

筛选与切片

先定义一个集合当作元数据测试使用

List<Apple> apples = Arrays.asList(
			new Apple("red", 100.22), 
			new Apple("blue", 100.22), 
			new Apple("red", 150.90),
			new Apple("blue", 120.56));

filter(Predicate p)

@Test
public void test1(){
	Stream<Apple> stream = apples.stream().filter(x -> x.getWeight() > 120);
	stream.forEach(System.out::println);
}

打印
Apple [color=red, weight=150.9]
Apple [color=blue, weight=120.56]

limit(long maxSize)

@Test
public void test2(){
	apples.stream().filter(x -> x.getWeight() > 100)
		           .limit(2).forEach(System.out::println);
}

//控制台
Apple [color=red, weight=100.22]
Apple [color=blue, weight=100.22]

注意:当新的Stream中的符合条件的元素个数大于limit()方法指定的长度时就不会继续运行了而是直接终止返回Stream。

skip(long n)

@Test
public void test3(){
	apples.stream().filter(x -> x.getWeight() > 100)
		           .skip(2).forEach(System.out::println);
}

//
Apple [color=red, weight=150.9]
Apple [color=blue, weight=120.56]

distinct()

先在apples集合添加两个元素,添加之后的集合

List<Apple> apples = Arrays.asList(
			new Apple("red", 100.22), 
			new Apple("blue", 100.22), 
			new Apple("red", 150.90),
			new Apple("blue", 80.56),
			new Apple("blue", 80.56));
@Test
public void test4(){
	apples.stream().distinct().forEach(System.out::println);
}

控制台打印

Apple [color=red, weight=100.22]
Apple [color=blue, weight=100.22]
Apple [color=red, weight=150.9]
Apple [color=blue, weight=80.56]
Apple [color=blue, weight=80.56]

发现并没有去重,是因为distinct()是根据hashCode和equals方法做比较的,现在重写Apple对象的hashCode和equals方法后再次测试。

Apple [color=red, weight=100.22]
Apple [color=blue, weight=100.22]
Apple [color=red, weight=150.9]
Apple [color=blue, weight=80.56]

映射

map(Function f)

@Test
public void test5() {
	List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
	Stream<String> stream = strList.stream().map(String::toUpperCase);
	stream.forEach(System.out::println);
		
	Stream<Stream<Character>> stream2 = strList.stream().map(MiddleOperation::filterCharacter);

	stream2.forEach((sm) -> {
		sm.forEach(System.out::println);
	});
		
}

public static Stream<Character> filterCharacter(String str) {
	List<Character> list = new ArrayList<>();

	for (Character ch : str.toCharArray()) {
		list.add(ch);
	}
	return list.stream();
}

这个方法中有两个例子,第一个例子通过String类的toUpperCase方法把strList内的所有元素变成大写,map()方法中的函数会应用在Stream中的每个元素上。

第二个例子,把strList中的每个元素的字符放入一个集合中返回,map()方法后返回的是一个Stream<Character>,也是一个流所所以最外层的结果就是Stream<Stream<Character>>类型的,因为map()将原元素改成了新元素,原元素是字符串类型,而新元素的类型是Stream<Character>,所以最终的类型就是Stream<Stream<Character>>,遍历的时候需要两层遍历。

flatMap(Function f)

List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
Stream<Character> stream = strList.stream().flatMap(MiddleOperation::filterCharacter);
		stream.forEach(System.out::println);

flatMap会把每个元素转换为一个流,然后把这些流连起来成为一个流,你可以回忆一下集合的add方法和addAll方法,add方法添加一个集合后,是把整个集合当作对象放进去,而addAll方法则是把被添加的集合中元素一个个放进去,flatMap就类似addAll。

排序

自然排序就是指类通过实现了Comparable接口重写了compareTo方法实现的排序,所以要使用sorted()方法必须保证类实现了Comparable并重写了compareTo方法。

定制排序是指通过定义一个外部比较器Comparator来实现排序。

@Test
public void test7() {
	apples.stream().map(Apple::getColor).sorted().forEach(System.out::println);

	System.out.println("------------------------------------");

	apples.stream().sorted((x, y) -> {
		if (x.getColor().equals(y.getColor())) {
			return Double.compare(x.getWeight(), y.getWeight());
		} else {
			return x.getColor().compareTo(y.getColor());
		}
	}).forEach(System.out::println);
}

现在直接用对象做自然排序试试

apples.stream().sorted().forEach(System.out::println);

控制台报错,因为Apple类没有实现Comparator接口

Stream终止操作

终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List、 Integer,甚至是 void 。当流(Stream)进行终止操作后当前流不能再次使用了。

查找和匹配

allMatch(Predicate p)

// 所有Apple是否匹配颜色是红色
boolean b1 = apples.stream().allMatch(a -> a.getColor().equals("red"));
System.out.println(b1); // false

anyMatch(Predicate p)

// 是否有Apple匹配颜色是红色
boolean b2 = apples.stream().anyMatch(a -> a.getColor().equals("red"));
System.out.println(b2); // true

noneMatch(Predicate p)

// 是否所有苹果都不匹配颜色是红色
boolean b3 = apples.stream().noneMatch(a -> a.getColor().equals("red"));
System.out.println(b3); // false

findFirst()

// 将Apple的重量取出来自然排序取出第一个
Optional<Double> op1 = apples.stream().map(Apple::getWeight)
		               				  .sorted()
		               			      .findFirst();
System.out.println(op1.get());//80.56

findFirst()的返回值是Optional类型的,它是Java8新增的避免空指针异常的一个容器。

findAny()

Optional<Apple> op2 = apples.stream().filter(a -> a.getColor().equals("red"))
	               					     .findAny();
System.out.println(op2.get());

findAny()会返回符合条件的任意元素,如果你想实现随机性,可以使用并行流parallelStream,它会使用多个线程同时去取符合条件的元素,直到某个线程取到为止。

Optional<Apple> op2 = apples.parallelStream().filter(a -> a.getColor().equals("red"))
	               					     .findAny();
System.out.println(op2.get());

count()

Long count = apples.stream().count();
System.out.println(count);//5

max(Comparator c)

Optional<Apple> op3 =apples.stream().max((x,y) -> x.getColor().compareTo(y.getColor()));
System.out.println(op3.get());

min(Comparator c)

Optional<Double> op4 =apples.stream().map(Apple::getWeight).min(Double::compare);
System.out.println(op4.get());

forEach(Consumer c)

apples.stream().forEach(System.out::println);

归约

reduce(T iden, BinaryOperator b)

参数iden是一个起始值,b是一个二元运算接口的实现类,因为它具有起始值所以不会存在返回null的情况,比如实现将一个数组内元素累加的功能。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Integer sum = list.stream().reduce(0, (x, y) -> x + y);

System.out.println(sum);

reduce(BinaryOperator b)

因为它不具有起始值所以会存在返回null的情况,需要用Optional包装,比如计算集合中红色苹果的个数。

Optional<Integer> sum = apples.stream().map(a -> {
	if(a.getColor().equals("red")){
		return 1;
	}else{
		return 0;
	}
}).reduce(Integer::sum);
System.out.println(sum.get());

收集

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、 Set、 Map)。但是 Collectors 实用类提供了很多静态
方法,可以方便地创建常见收集器实例, 具体方法与实例如下表:

定义一个Employee类

public class Employee {
	
	private int id;
	private String name;
	private double salary;
	private Status status;
	
	public Employee() {
		
	}

	public Employee(int id, String name, double salary, Status status) {
		this.id = id;
		this.name = name;
		this.salary = salary;
		this.status = status;
	}

	// get set hasCode equals toString


	
}
enum Status{
	FREE,
	BUSY
}

准备一个Employee集合
 

List<Employee> emps = Arrays.asList(
		new Employee(102, "李四", 79, 6666.66, Status.BUSY),
		new Employee(101, "张三", 18, 9999.99, Status.FREE),
		new Employee(103, "王五", 28, 3333.33, Status.VOCATION),
		new Employee(104, "赵六", 8, 7777.77, Status.BUSY),
		new Employee(104, "赵六", 8, 7777.77, Status.FREE),
		new Employee(104, "赵六", 8, 7777.77, Status.FREE),
		new Employee(105, "田七", 38, 5555.55, Status.BUSY)
);

接下来讲几个上面的静态方法

joining

你也可以在方法中指定连接符

String str = emps.stream()
			.map(Employee::getName)
			.collect(Collectors.joining("," , "----", "----"));
		
System.out.println(str);
//----李四,张三,赵六,赵六,赵六,田七----

groupingBy

groupingBy也可以多级分组

Map<Status, Map<String, List<Employee>>> map = emps.stream()
				.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
					if (e.getAge() >= 60)
						return "老年";
					else if (e.getAge() >= 35)
						return "中年";
					else
						return "成年";
				})));

猜你喜欢

转载自blog.csdn.net/WYA1993/article/details/83507328