151、了解Stream API吗?说说它
Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个是StreamAPI。Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用StreamAPI对集合数据进行操作,就类似于使用SQL执行的数据库查询一样简单。也可以使用StreamAPI来执行并行操作。简而言之,StreamAPI提供了一种高效且易于使用的数据处理方式。
流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”
注意:
- Stream自己不会存储元素
- Stream不会改变源对象,相反,他们会返回一个持有结果的新Stream。
- Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才会触发执行。
Stream的操作可分为三个步骤:
- 创建Stream:可以通过集合、数组来获取一个流
- 中间操作:一个中间操作链,对数据源的数据进行处理
- 终止操作:执行中间操作链,并产生结果
第一步:创建Stream(4种方式)
//创建流的四种方式
@Test
public void test1(){
//1、通过Collection系列集合提供的stream()或者parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2、通过Arrays中的静态方法stream()来获取流
Integer[] emps = new Integer[10];
Stream<Integer> stream2 = Arrays.stream(emps);
//3、通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa", "bb", "ab");
//4、通过迭代创建无限流
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
stream4.limit(10).forEach(System.out::println);
//4、通过生成创建无限流
Stream<Double> stream5 = Stream.generate(() -> Math.random());
stream5.limit(5).forEach(System.out::println);
}
第二步:对流进行一系列中间操作
filter——接收Lambda,从流中排除某些元素
limit——截断流,使其元素不超过给定数量
skip(n) ——跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个时,则返回一个空流。
distinct——筛选,通过流所生成元素的hashCode()和equals()去除重复元素
//中间操作
List<Employee> employees = Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",58,5555.55),
new Employee("王五",26,3333.33),
new Employee("赵柳",36,6666.66),
new Employee("天琪",12,8888.88),
new Employee("天琪",12,8888.88));
@Test
public void test2(){
//过滤年龄大于35岁的,并把其打印出来
//多个中间操作可以连接起来行成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理
Stream<Employee> stream = employees.stream().filter((e) -> e.getAge() > 35);
//执行终止操作,一次性触发全部执行——惰性求值或延迟加载
stream.forEach(System.out::println);
}
@Test
public void test3(){
//取出工资大于5000的前两个
employees.stream().filter((e) -> e.getSalay() > 5000).limit(2).forEach(System.out::println);
}
@Test
public void test4(){
//去除前两个工资大于5000的,然后打印其他工资大于5000的
employees.stream().filter((e) -> e.getSalay() > 5000).skip(2).forEach(System.out::println);
}
@Test
public void test5(){
//去重复 注意:必须要重写employee的hashCode和equals方法,否则无法去重
employees.stream().filter((e) -> e.getSalay() > 5000).skip(2).distinct().forEach(System.out::println);
}
public class Employee {
private String name;
private int age;
private double salay;
public Employee(String name, int age, double salay) {
this.name = name;
this.age = age;
this.salay = salay;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalay() {
return salay;
}
public void setSalay(double salay) {
this.salay = salay;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
if (age != employee.age) return false;
if (Double.compare(employee.salay, salay) != 0) return false;
return name != null ? name.equals(employee.name) : employee.name == null;
}
@Override
public int hashCode() {
int result;
long temp;
result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
temp = Double.doubleToLongBits(salay);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salay=" + salay +
'}';
}
}
映射操作:
map——接收Lambda,将元素转换为其他形式或者提取元素中的信息,接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
@Test
public void test6(){
//map——接收Lambda,将元素转换为其他形式
List<String> list = Arrays.asList("aaa","bda","cca","desa","daa");
//把小写转换为大写
list.stream().map((str) -> str.toUpperCase()).forEach(System.out::println);
System.out.println("----------------------------------");
//map——接收Lambda,提取元素中的信息
employees.stream().map(Employee::getName).forEach(System.out::println);
}
flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后再把所有流连接成一个流。
如果没有flatMap,想把list里面的每个元素都拆分后再换行,需要下面的实现方法:
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()){
list.add(ch);
}
return list.stream();
}
@Test
public void test7(){
List<String> list = Arrays.asList("aaa","bda","cca","desa","daa");
Stream<Stream<Character>> stream = list.stream().map(TestStreamAPI::filterCharacter);
stream.forEach((sm) -> sm.forEach(System.out::println));
}
使用flatMap后,简单一点:
@Test
public void test8(){
List<String> list = Arrays.asList("aaa","bda","cca","desa","daa");
Stream<Character> sm = list.stream().flatMap(TestStreamAPI::filterCharacter);
sm.forEach(System.out::println);
}
查找与匹配:
allMatch——检查是否匹配所有元素
anyMatch——检查是否至少匹配一个
noneMatch——检查是否没有匹配所有元素
findFirst——返回第一个元素
findAny——返回当前流中的任意元素
count——返回流中元素的个数
max——返回流中最大值
min——返回流中最小值
@Test
public void test10(){
boolean match = employees.stream().allMatch((e) -> e.getName().equals("张三"));
System.out.println(match);
boolean match1 = employees.stream().anyMatch((e) -> e.getName().equals("张三"));
System.out.println(match1);
boolean match2 = employees.stream().noneMatch((e) -> e.getName().equals("张三"));
System.out.println(match2);
Optional<Employee> first = employees.stream().sorted((e1, e2) -> -Double.compare(e1.getSalay(), e2.getSalay())).findFirst();
System.out.println(first.get());
Optional<Employee> optional = employees.stream().filter((e) -> e.getName().equals("张三")).findAny();
System.out.println(optional.get());
long count = employees.stream().count();
System.out.println(count);
Optional<Employee> max = employees.stream().max((e1, e2) -> Double.compare(e1.getSalay(), e2.getSalay()));
System.out.println(max.get());
Optional<Double> min = employees.stream().map(Employee::getSalay).min(Double::compare);
System.out.println(min.get());
}
归约操作:可以将流中的元素反复结合起来,得到一个值 Map-Reduce模式,适用于大数据Hadoop框架编程。
@Test
public void test11(){
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);
//求员工工资的总和。总和有可能为空,所以封装到 Optional中去
Optional<Double> reduce = employees.stream().map(Employee::getSalay).reduce(Double::sum);
System.out.println(reduce.get());
}
收集操作:collect()将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
Collector接口中方法的实现决定了如何对流执行收集操作(比如收集到List、Set、Map)。但是Collectors实用类提供了许多静态方法,可以方便地创建常见的收集器实例。
@Test
public void test12(){
//收集员工姓名,放入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());
System.out.println(set);
//收集到特殊集合中——HashSet
HashSet<String> hashSet = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
hashSet.forEach(System.out::println);
//获取集合中员工总数
Long aLong = employees.stream().collect(Collectors.counting());
System.out.println(aLong);
//员工平均工资
Double aDouble = employees.stream().collect(Collectors.averagingDouble(Employee::getSalay));
System.out.println(aDouble);
//员工工资总和
DoubleSummaryStatistics sum = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalay));
System.out.println(sum);
//工资最大的员工
Optional<Employee> max = employees.stream().collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalay(), e2.getSalay())));
System.out.println(max.get());
//工资的最小值
Optional<Double> min = employees.stream().map(Employee::getSalay).collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
}