Java之JDK1.8新特性三(Stream流)

1、Stream流

在jdk1.8之前,以List集合为例,我们可以使用Iterator或普通for循环对集合进行遍历。
但是不论以何种方式进行遍历,都存在有形式主义。
以for为例

for(int i=0;i<list.size;i++){
	System.out.println(list.get(i))
}

我们需要获取集合中的每一个元素,需要关注于"做什么"。但是for语句体现的是"怎么做"(形式),而循环体才是"做什么"。
遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

JDK1.8提出了Stream流,用于解决现有集合类库既有的弊端。
那什么是Stream流?
Stream流是一种数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
简而言之,以集合为例,“集合存储数据,而流是对集合内数据进行处理”

注意:

  • Stream流自己不会存储元素
  • Stream流不会改变源数据。相反,它会返回一个持有结果的新Stream
  • Stream操作是延迟执行的,这意味着它们需要等到需要结果的时候才会执行(提升了程序的执行效率)

2、Stream流操作

有3个步骤,依次为获取Stream、中间操作、终止操作。

2.1 获取Stream

有4种方式

  1. 通过集合获取Stream
    Collection接口中的默认方法stream()获取串行流或parallelStream()获取并行流
    Map集合如何获取流
        map.keySet().stream();
        map.values().stream();
        map.entrySet().stream();
  2. 通过数组获取Stream
    Arrays中的静态方法String(T[] array)
  3. 通过一个或多个同类型元素获取Stream
    Stream接口中的静态方法of(T… values)
  4. 获取无穷序列流
    static Stream iterator(T seed, UnaryOperator f)
    返回一个无穷序列有序流,其中第一个元素由seed指定,后面的元素由一元运算符f计算得到,可以实现迭代操作
    static Stream generate(Supplier s)
    返回一个无穷序列无序流,其中每个元素是由提供Supplier生成。适用于产生恒定的流,随机元素的流等。

示例代码如下:

// 数据源
List<String> list = Arrays.asList("Tom", "Jerry", "Tony");
String[] names = { "Tom", "Jerry", "Tony" };

// 1、获取集合的流对象
Stream<String> sm1 = list.stream();

// 2、获取数组的流对象
Stream<String> sm2 = Arrays.stream(names);

// 3、获取多个元素的流对象
Stream<String> sm3 = Stream.of("Tom", "Jerry", "Tony");

// 4、获取无穷序列流对象
Stream<Integer> sm4 = Stream.iterate(1, x -> x + 2);// 迭代
sm4.limit(10).forEach(System.out::println);

Stream<Integer> sm5 = Stream.generate(new Random()::nextInt);// 生成
sm5.limit(10).forEach(System.out::println);
2.2 中间操作

中间操作返回Stream,多个中间操作可以连接起来形成一个流水线。除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时,所有中间操作会一次性全部处理,这种操作称为“惰性求值”。
常见的中间操作有下面几类:
1)筛选与切片

  • filter(Predicate pre):过滤
  • limit(long maxSize):取用前几个
  • skip(long n):跳过前几个
  • distinct():根据对象的hashCode()和equals()去除重复

示例代码如下:

//数据源
List<String> list = Arrays.asList("Tom","Jerry","Tony","Tom","Tank");

//filter:获取所有以"T"开头的名字
list.stream()
.filter(s->s.startsWith("T"))
.forEach(System.out::println);

//limit:获取前2个以"T"开头的名字
list.stream()
.filter(s->s.startsWith("T"))
.limit(2)
.forEach(System.out::println);

//skip:获取以"T"开头的名字(跳过前2个)
list.stream()
.filter(s->s.startsWith("T"))
.skip(2)
.forEach(System.out::println);

//distinct:获取以"T"开头的名字(去除重复)
list.stream()
.filter(s->s.startsWith("T"))
.distinct()
.forEach(System.out::println);

2)映射:map

  • <R> Stream map(Function<? super T, ? extends R> mapper);
    将流中的每一个元素通过mapper函数转换成其它类型的数据
  • <R> Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    将流中的每一个元素都转成另一个流,然后将所有流连接成一个新流

示例代码如下:

//需求1:将集合中每个元素转成大写
List<String> list = Arrays.asList("Tom","Jerry","Tony","Tom","Tank");
Stream<String> sm = list.stream().map(String::toUpperCase);
sm.forEach(System.out::println);

//需求2:将集合元素的每个字符依次输出
public class StreamDemo {
	@Test
	public void test(){
		List<String> list = Arrays.asList("Tom","Jerry","Tony","Tom","Tank");
		Stream<Stream<Character>> sm = list.stream().map(s->new StreamDemo().mapMethod(s));
		sm.forEach(stm->stm.forEach(System.out::println));

		//使用map的操作有些复杂,我们可以通过flatMap()进行改进
		Stream<Character> sm2 = list.stream().flatMap(new StreamDemo02()::mapMethod);
		sm2.forEach(System.out::println);
	}
	
	//将指定的字符串转成流
	public Stream<Character> mapMethod(String s){
		List<Character> list = new ArrayList<>();
		for(int i=0;i<s.length();i++){
			list.add(s.charAt(i));
		}
		return list.stream();
	}
}

3)排序:sorted

  • sorted():自然排序
  • sorted(Comparator comparator):比较器排序

示例代码如下:

List<String> list = Arrays.asList("Tom","Jerry","Tony","Tank");

//自然排序
list.stream()
.sorted()
.forEach(System.out::println);

//比较器排序
list.stream()
.sorted((s1,s2)->s2.compareTo(s1))
.forEach(System.out::println);
2.3 终止操作

终止操作会从流的流水线生成结果,其结果可以是任何不是流的值。
常见的终止操作有下面几类:
1)匹配

  • allMatch(Predicate pre):检查是否匹配所有元素
  • angMatch(Predicate pre):检查是否至少匹配一个元素
  • noneMatch(Predicate pre):检查是否没有匹配所有元素

示例代码如下:

//该实体类适应于所有终止操作示例代码,下面代码中不会重复
class Employee{
	private String name;
	private double salary;
	private Status status;//状态
	
	//省略了非核心代码
	
	public enum Status{
		FREE,//空闲
		BUSY,//忙
		VACATION;//休假
	}
}

//数据源:所有终止操作示例代码使用同一数据源,下面不会重复
List<Employee> employees = Arrays.asList(
			new Employee("Tom", 6000, Status.BUSY),
			new Employee("Jerry", 5000, Status.BUSY),
			new Employee("Tony", 9000, Status.VACATION),
			new Employee("Lucy", 8000, Status.BUSY),
			new Employee("Habi", 7000, Status.BUSY));

//匹配操作

```java
@Test
public void test1(){
//检查是否所有人都在忙
boolean b1 = employees.stream().allMatch(e->e.getStatus().equals(Status.BUSY));
System.out.println(b1);//f

//检查是否至少有一个人在休假
boolean b2 = employees.stream().anyMatch(e->e.getStatus().equals(Status.VACATION));
System.out.println(b2);//t

//检查是否所有人都不在空闲
boolean b3 = employees.stream().noneMatch(e->e.getStatus().equals(Status.FREE));
System.out.println(b3);//t
}

2)查找

  • Optional findFist():返回第一个元素
  • Optional findAny():返回任意一个元素
  • long count():返回流中元素的总个数
  • Optional max(Comparator comparator):返回流中最大值
  • Optional min(Comparator comparator):返回流中最小值

注:Optional是一种容器,里面可以存储对象,用来解决空指针异常。关于Optional的一些常见用法及应用,可点击Java之JDK1.8新特性四(Optional)了解。

示例代码如下:

//查找操作
@Test
public void test2(){
//1、获取工资最高的那个员工
//tips:Optional是一个容器,可以减少空指针异常。如果容器为空,可以通过它提供的orElse(T other)方法,将other添加到容器中
Optional<Employee> op = employees.stream()
		.sorted((e1,e2)->Double.compare(e2.getSalary(), e1.getSalary()))
		.findFirst();
Employee emp = op.get();
System.out.println(emp);

//2、获取任意一个在忙的员工
Optional<Employee> op2 = employees.stream()
		.filter(e->e.getStatus().equals(Status.BUSY))
		.findAny();
System.out.println(op2.get());

//多执行几次上面的操作,发现每次都是第一个在忙的员工,不能获取其他在忙的员工(因为采用的是串行流)
//将流改进为并行流,就可以解决这个问题
Optional<Employee> op3 = employees.parallelStream()
		.filter(e->e.getStatus().equals(Status.BUSY))
		.findAny();
System.out.println(op3.get());

//3、获取流中元素的总个数
long count = employees.stream().count();
System.out.println(count);

//4、获取工资最高的那个员工
Optional<Employee> op4 =employees.stream()
		.max((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op4.get());

//5、获取工资最低的那个员工
Optional<Employee> op5 =employees.stream()
		.min((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op5.get());

//获取最低工资
Optional<Double> op6 = employees.stream()
		.map(e -> e.getSalary())
		.min(Double::compare);
System.out.println(op6.get());
}

3)归约

  • T reduce(T identity, BinaryOperator bo)
    identity作为初始元素,参与到二元运算bo中,作为bo中第一个元素,而第二个元素来自于Stream流中的第一个元素。
    当执行完bo操作之后,运算结果作为第一个元素,Stream流中的第二个元素作为第二元素再次执行bo操作,一直到Stream中所有
    元素都参与了运算,最终获取了一个T类型的值
  • Optional reduce(BinaryOperator bo)
    将流中的所有元素依次按照二元运算bo操作执行,最终获得一个Optional容器

注:map和reduce的连接通常称为map-reduce模式(Google用它来进行网络搜索而出名)

示例代码如下:

List<Integer> li = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//需求1:求集合li中所有元素之和
Integer sum = li.stream().reduce(0, (x,y)->x+y);
System.out.println(sum);

//需求2:获取所有员工的总工资
Optional<Double> op = employees.stream()
		.map(Employee::getSalary)
		.reduce(Double::sum);
System.out.println(op.get());

4)收集
collect(Collector c):通过接受一个收集器(用于给Stream中元素做汇总的方法),将流转换成其它形式。

注:
Collector接口中方法的实现决定了如何对流执行收集操作(如将元素收集到List、Map、Set等)。
Collectors工具类中提供了很多静态方法,可以方便的创建常见收集器实例。
常用方法有:
1)将收集到的元素转成集合

  • toList():转List
  • toSet():转Set
  • toCollection(Supplier sup):转指定生成的集合

2)将收集到的数据进行汇总

  • counting():总数
  • averagingDouble(Function mapper):平均值
  • summingDouble(Function mapper):总值
  • maxBy(Comparator comr):最大值
  • minBy(Comparator comr):最小值
  • summarizingDouble(Function mapper):汇总结果

3)分组

  • groupingBy(Function mapper):分组
  • groupingBy(Function mapper, Collector clt) :多级分组

4)分区

  • partitioningBy(Predicate pre):分区

5)连接

  • joining():连接
  • joining(String delimiter):连接且设置分隔符
  • joining(String delimiter, String prefix, String suffix):连接且设置分隔符、前缀和后缀

示例代码如下

1、将收集到的元素转成集合

//需求:获取所有员工的姓名并存储到一个List中
List<String> list = employees.stream()
		.map(Employee::getName)
		.collect(Collectors.toList());
list.forEach(System.out::println);

//需求:获取所有员工的姓名并存储到一个Set中
Set<String> set = employees.stream()
		.map(Employee::getName)
		.collect(Collectors.toSet());
set.forEach(System.out::println);

//需求:获取所有员工的姓名并存储到一个HashSet中
HashSet<String> hs = employees.stream()
		.map(Employee::getName)
		.collect(Collectors.toCollection(HashSet::new));
hs.forEach(System.out::println);

2、将收集到的数据进行汇总

//需求:获取工资大于6000的员工数
long count = employees.stream()
		.filter(e -> e.getSalary()>6000)
		.collect(Collectors.counting());
System.out.println(count);

//需求:获取所有员工工资的平均值
Double avgSalary = employees.stream()
		.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(avgSalary);

//需求:获取所有员工的工资
Double sumSalary = employees.stream()
		.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sumSalary);

//需求:获取员工工资的最大值
Optional<Double> op = employees.stream()
		.map(Employee::getSalary)
		.collect(Collectors.maxBy(Double::compare));
System.out.println(op.get());

//也可以通过summarizingDouble获取总数,最大值,最小值,总值,平均值
DoubleSummaryStatistics dss = employees.stream()
		.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getCount());
System.out.println(dss.getMax());
System.out.println(dss.getMin());
System.out.println(dss.getSum());
System.out.println(dss.getAverage());

3、分组

//需求:按员工的工作状态进行分组
Map<Status, List<Employee>> map = employees.stream()
		.collect(Collectors.groupingBy(Employee::getStatus));
map.forEach((k,v) -> {
	System.out.println(k);
	v.forEach(System.out::println);
});

4、多级分组

//需求:先按员工的工作状态进行分组,同一状态再按工资进行分组
Map<Status, Map<String, List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
	if(((Employee) e).getSalary()<5000){
		return "普工";
	}else if(e.getSalary()<8000){
		return "技工";
	}else{
		return "高工";
	}
})));

map.forEach((k,v) ->{
	System.out.println(k);
	v.forEach((s,l) -> {
		System.out.println(s);
		l.forEach(System.out::println);
	});
});

5、分区

//需求:将员工分为两个区,工资>=8000的一个区,小于<8000的另一个区
Map<Boolean, List<Employee>> map = employees.stream()
		.collect(Collectors.partitioningBy(e -> e.getSalary()>=8000));
map.forEach((k,v) -> {
	System.out.println(k);
	v.forEach(System.out::println);
});

6、连接

//需求:将所有员工的姓名连接成一个字符串
String s1 = employees.stream()
		.map(Employee::getName)
		.collect(Collectors.joining());
System.out.println(s1);

//需求:将所有员工的姓名连接成一个字符串,以','分割
String s2 = employees.stream()
		.map(Employee::getName)
		.collect(Collectors.joining(","));
System.out.println(s2);

//需求:将所有员工的姓名连接成一个字符串,以','分割,且两边添加<<和>>符号
String s3 = employees.stream()
		.map(Employee::getName)
		.collect(Collectors.joining(",","<<",">>"));
System.out.println(s3);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值