java8新特性之Stream API
一.概述
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询语句一样简单。也可以使用 Stream API 来并行执行操作。
简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式,它可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
特点:
1 . Stream 自己不会存储元素
2. Stream 不会改变数据源对象,相反,它会返回一个持有结果的新Stream
3. Stream 惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
二、本文预备工作
1.实体类
员工实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Objects;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer id;
private String name;
private Integer age;
private double salary;
private Status status;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age.equals(employee.age);
}
@Override
public int hashCode() {
return Objects.hash(age);
}
public enum Status{
FREE,
BUSY,
VOCATION
}
}
2.员工List
List<Employee> emps = Arrays.asList(
new Employee(101, "zhangsan", 19, 3333.33, Employee.Status.BUSY),
new Employee(102, "zhangsan1", 20, 5555.55,Employee.Status.FREE),
new Employee(104, "zhangsan2", 30, 6666.66,Employee.Status.BUSY),
new Employee(105, "zhangsan3", 50, 8888.88,Employee.Status.BUSY),
new Employee(106, "zhangsan4", 30, 6666.66,Employee.Status.VOCATION),
new Employee(107, "zhangsan5", 50, 8888.88,Employee.Status.VOCATION)
);
三、Stream的操作三个步骤
1.创建Stream
一个数据源(如:集合,数组),获取一个流
1.1 可以通过Collecttion系统集合提供的stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
1.2 通过Arrays中的静态方法stream()获取数组流
Employee [] emps = new Employee[10];
Stream<Employee> stream = Arrays.stream(emps);
1.3 通过Stream 类中的静态方法 of()、iterate()、generate()
//通过Stream 类中的静态方法of()
Stream<String> stream3 = Stream.of("aa","bb","cc")
//创建无限流
//迭代
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.limit(10).forEach(System.out::println);
//生成
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
1.4 使用 Pattern.splitAsStream() 方法,将字符串分隔成流
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d,e");
stringStream.forEach(System.out::println);
2.中间操作
一个中间操作链,对数据源的数据进行处理
2.1 筛选与切片:
**filter**---接收Lambda,从流中排除某些元素
**limit**----截断流,使其元素不超过给定数量
**skip(n)**---跳过流,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流,与limit(n)互补
**distinck** ---筛选,通过流所生成元数的hashCode()和equals()去除重复元素
2.2 映射:
map--接收lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元数上,并将其映射成一个新的元素
flatMap--接收一个函数作为参数,将流中每个值都换成另一个流,然后把所有流连接成一个流
2.3 排序
sorted()--自然排序(compareble---compareTo)
sorted(Compartor com)--定制排序(comparator)
测试代码如下:
/**
* 一.Stream 的三个操作步骤
* 1.创建Stream流
* <p>
* 2.中间操作
* <p>
* 3.终止操作
*/
//中间操作
public class StreamTest2 {
List<Employee> emps = Arrays.asList(
new Employee(101, "zhangsan", 19, 3333.33, Employee.Status.BUSY),
new Employee(102, "zhangsan1", 20, 5555.55,Employee.Status.FREE),
new Employee(104, "zhangsan2", 30, 6666.66,Employee.Status.BUSY),
new Employee(105, "zhangsan3", 50, 8888.88,Employee.Status.BUSY),
new Employee(106, "zhangsan4", 30, 6666.66,Employee.Status.VOCATION),
new Employee(107, "zhangsan5", 50, 8888.88,Employee.Status.VOCATION)
);
//中间操作
/**
* 筛选与切片
* filter---接收Lambda,从流中排除某些元素
* limit----截断流,使其元素不超过给定数量
* skip(n)---跳过流,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流,与limit(n)互补
* distinck ---筛选,通过流所生成元数的hashCode()和equals()去除重复元素
*/
//内部迭代:操作由Stream API完成
@Test
public void test1() {
Stream<Employee> stream = emps.stream()
.filter((e) -> e.getAge() > 20);
//终止操作
stream.forEach(System.out::println);
}
//外部迭代
@Test
public void test2(){
Iterator<Employee> it = emps.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
@Test
public void test3(){
emps.stream()
.filter((e)->{
System.out.println("短路!"); //&& ||
return e.getAge()>10;
})
.limit(2)
.forEach(System.out::println);
}
@Test
public void test4(){
emps.stream()
.filter((e)->{
System.out.println("扔掉前2个!"); //&& ||
return e.getAge()>10;
})
.skip(2)
.distinct()
.forEach(System.out::println);
}
/**
* 映射
* map--接收lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元数上,并将其映射成一个新的元素
* flatMap--接收一个函数作为参数,将流中每个值都换成另一个流,然后把所有流连接成一个流
* */
@Test
public void test5(){
List<String> list=Arrays.asList("aaa","sss","ddd","fff","ggg");
list.stream()
.map((str)->str.toUpperCase())
.forEach(System.out::println);
System.out.println("---------------------------------");
emps.stream()
.map((e)->e.getName())
.forEach(System.out::println);
}
/**
* 排序
* sorted()--自然排序(compareble---compareTo)
* sorted(Compartor com)--定制排序(comparator)
* */
@Test
public void test6(){
List<String> list=Arrays.asList("aaa","sss","ddd","fff","ggg");
list.stream()
.sorted()
.forEach(System.out::println);
System.out.println("--------------------------------------");
emps.stream()
.sorted((e1,e2)->{
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else{
return -e1.getAge().compareTo(e2.getAge());
}
})
.forEach(System.out::println);
}
}
3.终止操作
一个终止操作,执行中间操作链,并产生结果
3.1 查找与匹配
***allMatch**--检查是否匹配所有元素
* **anyMatch**--检查是否至少匹配一个元素
* **noneMatch**--检查是否没有匹配所有元素
* **findFirst**--返回第一个元素
* **findAny**--返回当前流中的任意元素
* **count**--返回流中元素的总个数
* **max**--返回流中最大值
* **min**--返回流中最小值
代码如下(示例):
@Test
public void test01() {
boolean b1 = emps.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);
System.out.println("--------------------------------------");
boolean b2 = emps.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);
System.out.println("--------------------------------------");
boolean b3 = emps.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);
Optional<Employee> op = emps.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
System.out.println(op.get());
System.out.println("-------------------------------------------");
Optional<Employee> opany = emps.stream()
.filter((e) -> e.getStatus().equals(Employee.Status.FREE))
.findAny();
System.out.println(opany.get());
System.out.println("-------------------------------------------");
long count = emps.stream()
.count();
System.out.println(count);
System.out.println("-------------------------------------------");
Optional<Employee> max = emps.stream()
.max((e1, e2) -> e1.getAge().compareTo(e2.getAge()));
System.out.println(max.get().getAge());
System.out.println("-------------------------------------------");
Optional<Integer> min = emps.stream()
.map(Employee::getAge)
.min(Double::compare);
System.out.println(min.get());
}
3.2 归约
**reduce(T identity,BinaryOperator) / reduce(BinaryOperator)** --可以将流中元素反复结合起来,得到一个值
/**
* 归约
* reduce(T identity,BinaryOperator) / reduce(BinaryOperator) --可以将流中元素反复结合起来,得到一个值
* */
@Test
public void test02(){
List<Integer> list =Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer reduce = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(reduce);
System.out.println("---------------------------------------");
Optional<Double> reduce1 = emps.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(reduce1.get());
}
3.3 收集
**collect**--将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
collector 接口中方法的实现决定了如何对溜执行收集操作(如收集到List,set,Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。
3.3.1 toList(), toSet(), toCollection()
@Test
public void test03(){
List<String> list = emps.stream()
.map(Employee::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println("-------------------------------------");
Set<String> set = emps.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println("----------------------------------------");
HashSet<String> hashSet = emps.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);
}
3.3.2 counting(),averagingDouble(),summingDouble(),maxBy()和minBy()
@Test
public void test04(){
//总数
Long count = emps.stream()
.collect(Collectors.counting());
System.out.println(count);
System.out.println("----------------------------------------");
//平均值
Double ave = emps.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(ave);
System.out.println("----------------------------------------");
//总和
Double sum = emps.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(sum);
System.out.println("----------------------------------------");
//最大值
Optional<Employee> max = emps.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(max.get());
System.out.println("----------------------------------------");
//最小值
Optional<Double> min = emps.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
}
3.3.3 分组,多级分组和分区
//分组
@Test
public void test05() {
Map<Employee.Status, List<Employee>> map = emps.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
Iterator<Map.Entry<Employee.Status, List<Employee>>> iterator = map.entrySet().iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
//多级分组
@Test
public void test06() {
Map<Double, Map<String, List<Employee>>> collect1 = emps.stream()
.collect(Collectors.groupingBy(Employee::getSalary, Collectors.groupingBy((e) -> {
if (e.getAge() <= 35) {
return "青年";
} else if (e.getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
Iterator<Map.Entry<Double, Map<String, List<Employee>>>> it= collect1.entrySet().iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
//分区
@Test
public void test07() {
Map<Boolean, List<Employee>> collect = emps.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 6000));
Iterator<Map.Entry<Boolean, List<Employee>>> iterator = collect.entrySet().iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}