Stream流的操作
1.什么是Stream
它是一个数据渠道,用于操作数据源(集合,数组等)所生成的元素序列,集合讲的是数据,Stream讲的是计算!使用Stream API对集合数据进行操作,就类似使用SQL执行的数据库查询!
2.为什么要使用Stream API
实际开发中,项目中多数数据源来自Mysql,Oracel关系型数据库,但现在
还有来自Nosql的数据:如MongoDB,Redis,而这些NoSQL的数据就需要
java层面去处理
3.Stream的操作三个步骤
1.创建Stream:一个数据源(集合,数组),获取Stream
2.中间操作:一个中间操作链,对数据源进行一系列处理
3.终止操作:一旦执行终止操作,就执行中间操作,并产生结果,之后不会再
一:Stream流的实例化
四种方式:
a:通过集合对象.stream()b:通过Arrays类的stream()
c:stream本身的of()
d:stream本身的iterate()、generate()
package cn.itcast.stream;
import cn.itcast.lambda.Employee;
import cn.itcast.lambda.EmployeeData;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* 1.
* Stream关注的是数据的运算,和cpu打交道
* 集合关注的是数据的存储,和内存打交道!
*
* 2.
* a.Stream自己不会存储元素
* b.Stream不会改变源对象,相反他们会返回一个持有结果的新Stream
* c.Stream操作是延迟执行的,这意味着他们会等需要结果的时候才去执行(懒加载)
*
* 3.Stream执行流程
* a.Stream的实例化
* b.一系列的中间过程(过滤,映射...)
* c.终止操作!
*
* 4.说明
* a.一个中间操作链,对数据源的数据进行处理
* b.一旦执行终止操作,就执行中间操作链,并产生结果,之后不会再被使用!
*/
public class StreamTest {
//创建 Stream方式一: 通过集合
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// default Stream<E> stream():返回一个顺序流
Stream<Employee> stream = employees.stream();
//default Stream<E> parallelStream(): 返回一个并行流
Stream<Employee> employeeStream = employees.parallelStream();
}
//创建 Stream方式二: 通过数组
@Test
public void test2(){
int[] arr = {1,3,2,4};
//调用Arrays类的public static <T> Stream<T> stream(T[] array):返回一个流!
IntStream intStream = Arrays.stream(arr);
Employee employee1 = new Employee(1001,"wzj",22,50000);
Employee employee2 = new Employee(1002,"tom",22,50000);
Employee[] employees = new Employee[]{employee1,employee2};
Stream<Employee> stream = Arrays.stream(employees);
}
//创建 Stream方式三: 通过stream本身的of()
@Test
public void test3(){
Stream<String> stringStream = Stream.of("1", "2", "3");
}
//创建 Stream方式四: 创建无限流
/**
* UnaryOperator也是一个函数式接口!,所以可以用lambda表达式的方式!
*/
@Test
public void test4(){
//迭代
//public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//遍历前10给个偶数
Stream.iterate(0,t -> t+2).limit(10).forEach(System.out::println);
//生成
//public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
}
二:Stream流的中间操作
三大分类
a:筛选与切片:filter()、limit()、skip()、distinct()b:映射:map()、flatMap()
c:排序:sorted()、sorted(Comparator c)
- 准备数据!!!
@Data
public class Employee {
private int id;
private String name;
private int age;
private double salary;
public Employee() {
}
public Employee(int id) {
this.id = id;
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public Employee(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
}
public class EmployeeData {
public static List<Employee> getEmployees(){
List<Employee> list = new ArrayList();
list.add(new Employee(1001,"wzj",22,50000));
list.add(new Employee(1002,"tom",22,40000));
list.add(new Employee(1003,"zhangsan",24,30000));
list.add(new Employee(1004,"lisi",25,20000));
list.add(new Employee(1005,"wangwu",26,10000));
list.add(new Employee(1005,"wangwu",26,10000));
return list;
}
}
2.1:筛选与切片
public class StreamAPITest1 {
//1.筛选与切片
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
//1.filter(Predicate p) 接收lambda表达式,从流中排除某些元素
//查询员工表中工资>10000的所有员工!
employees.stream().filter(e -> e.getSalary()>10000).forEach(System.out::println); //注意stream流关闭之后,必须重新开一个去使用!
System.out.println();
//2.limit(n) 截断流,使其元素不超过给定数量
//查询员工表中前三条记录!
employees.stream().limit(3).forEach(System.out::println);
System.out.println();
//3.skip(n) 跳过元素,返回一个扔掉了前n个元素的流,若流中的元素不足n个,则返回一个空流
//跳过前三条记录开始查询
employees.stream().skip(3).forEach(System.out::println);
//4.distinct 筛选,通过流所生成元素的hashcode和equals去除重复元素!
employees.stream().distinct().forEach(System.out::println);
}
}
2.2:映射
public class StreamAPITest2 {
//映射
@Test
public void test1(){
//map(Function f) -接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会应用到每个函数上,并将其映射成一个新的元素!
List<String> list = Arrays.asList("aa", "bb", "cc");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
System.out.println();
//练习一:获取员工姓名长度>3的员工
List<Employee> employees = EmployeeData.getEmployees();
Stream<String> nameStream = employees.stream().map(Employee::getName);
nameStream.filter(name -> name.length()>3).forEach(System.out::println);
System.out.println();
//练习二: 这里返回的效果,相当于add(Object obj)的效果,[1, 2, 3, [4, 5, 6]] 见test2()
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest2::fromStringToStream);
streamStream.forEach( s -> {
s.forEach(System.out::println);
});
System.out.println();
// flatMap(Function f) 接收一个函数作为参数, 将流中的每个值转换成另一个流, 然后把所有流连接成一个流.
Stream<Character> characterStream = list.stream().flatMap(StreamAPITest2::fromStringToStream);
characterStream.forEach(System.out::println);
}
//将字符串中的多个字符构成的集合转换为对应的Stream的实例!
public static Stream<Character> fromStringToStream(String str){
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()){
list.add(c);
}
return list.stream(); //alt+enter: 快速生成编译时类型和返回值类型是什么!
}
@Test
public void test2(){
List list1 = new ArrayList(Arrays.asList("1","2","3"));
List list2 = new ArrayList(Arrays.asList("4","5","6"));
// list1.add(list2);
// System.out.println(list1); //[1, 2, 3, [4, 5, 6]]: 这就好比map()的思想!
list1.addAll(list2);
System.out.println(list1); // [1, 2, 3, 4, 5, 6] :这就好比flatMap()的思想
}
}
2.3:排序
public class StreamAPITest3 {
/**
* 排序:
* 1.自然排序 sorted()
* 2.定制排序 sorted(Comparator com)
*/
@Test
public void test1(){
//自然排序 sorted()
List<String> list = new ArrayList<>(Arrays.asList("1","3","2","4"));
list.stream().sorted().forEach(System.out::println);
System.out.println();
//定制排序 sorted(Comparator com)
// List<Employee> employees = EmployeeData.getEmployees();
// employees.stream().sorted().forEach(System.out::println);
//cn.itcast.lambda.Employee cannot be cast to java.lang.Comparable: 因为Employee没有实现Comparator接口!
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted((o1,o2) ->{
int age = Integer.compare(o1.getAge(),o2.getAge());
if(age != 0){ //!=0,就说明年龄不相等!
return age;
}else{ //年龄相等,就按薪水排序!
return Double.compare(o1.getSalary(),o2.getSalary());
}
}).forEach(System.out::println);
}
}
三:Stream流的终止操作
3.1:匹配与查找
//小技巧:如果你想不起来该函数式接口的抽象方法,除了点进来自己看,还可以先自己用匿名生成出来的基础去改!
public class StreamStopTest1 {
//匹配与查找
@Test
public void test(){
/**
* 1.allMatch(Predicate p) 检查是否匹配所有元素!
* 练习:是否所有的员工的年龄都>18
*/
List<Employee> employees = EmployeeData.getEmployees();
System.out.println(employees.stream().allMatch(e -> e.getAge() > 18));
/**
* 2.anyMatch(Predicate p) 检查是否至少匹配一个元素
* 练习:是否存在员工的工资>10000
*/
System.out.println(employees.stream().anyMatch(e -> e.getSalary() > 10000));
/**
* 3.noneMatch(Predicate p) 检查是否没有匹配的元素
* 练习:是否存在员工姓“王”
*/
System.out.println(employees.stream().noneMatch(e -> e.getName().startsWith("王")));
}
@Test
public void test2(){
// 3.findFirt() 返回第一个元素(注:可以排完序后返回第一个!)
List<Employee> employees = EmployeeData.getEmployees();
Optional<Employee> first = employees.stream().findFirst();
System.out.println(first);
// 4.findAny() 返回当前流中任意元素: 使用并行流,而不是串行流!
Optional<Employee> any = employees.parallelStream().findAny();
System.out.println(any);
}
@Test
public void test3(){
//5.count() 返回流中元素的个数
List<Employee> employees = EmployeeData.getEmployees();
long count = employees.stream().count();
System.out.println(count);
/**
* 6.max(Comparator c) 返回流中的最大值!
* 练习:返回最高的工资
*
* employees.stream().map(e -> e.getSalary()) 这个e就表示从流中拿出来一个一个的元素!
* map(e -> e.getSalary()) 映射吗,左边是员工(key), 右边是员工的工资(value)
*/
Optional<Double> maxSalary = employees.stream().map(e -> e.getSalary()).max(Double::compareTo);
System.out.println(maxSalary); //Optional[50000.0]
/**
* 7.min(Comparator c) 返回流中的最小值!
* 练习:返回最低的工资
*/
Optional<Employee> minSalaryEmp = employees.stream().min((o1, o2) -> Double.compare(o1.getSalary(), o2.getSalary()));
System.out.println(minSalaryEmp); //Optional[Employee(id=1005, name=wangwu, age=26, salary=10000.0)] 因为没做map等映射,所以返回是整条记录!
}
@Test
public void test4(){
//8.foreach(Consumer c) 内部迭代
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().forEach(System.out::println);
System.out.println();
//9.使用集合的遍历操作,外部迭代
employees.forEach(System.out::println);
}
}
3.2:归约
@Test
public void test1(){
/**
* reduce(T identity, BinaryOperator): 可以将流中的元素反复结合起来得到一个值,返回T
* identity: 是初始化的值!
* BinaryOperator接口继承了BiFunction中的抽象方法:R apply(T t, U u);
*
* 练习:计算1-10自然数的和!
*/
List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer reduce = nums.stream().reduce(0, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer num1, Integer num2) {
return num1+num2;
}
});
System.out.println(reduce); //55
//lambda表达式
Integer reduce1 = nums.stream().reduce(0, (num1, num2) -> num1 + num2);
System.out.println(reduce1); //55
//方法引用: Integer apply(Integer integer, Integer integer2) 结构类似于 int sum(int a, int b)
Integer reduce2 = nums.stream().reduce(10, Integer::sum);
System.out.println(reduce2); //65, 因为初始化是10
}
/**
* reduce(BinaryOperator): 可以将流中的元素反复结合起来得到一个值,返回Optional<T>
*
* 练习:计算员工工资的总和!
*/
@Test
public void test2(){
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> reduce = salaryStream.reduce(Double::sum);
System.out.println(reduce);
}
3.3:收集
/**
* 收集
*/
@Test
public void test1(){
// collect(Collector c) 将流转换成其他形式,接收一个Collector接口的实现!
// 练习:查找工资大于6000的员工,结果返回一个list或者set
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> list = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
System.out.println(list);
System.out.println();
//返回一个set集合
Set<Employee> set = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
System.out.println(set);
}
Collectors的其他方法
- 演示一下Collectors中的groupingBy方法
@Data
public class FruitBasket {//水果篮
private List<Fruit> fruits;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Fruit {//水果
private String kindId; //种类id
private String name; //水果
}
public class Demo {
public static void main(String[] args) {
//1.
FruitBasket fruitBasket = new FruitBasket();
List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Fruit("1","砂糖橘"));
fruits.add(new Fruit("1","蜜桔"));
fruits.add(new Fruit("2","无籽西瓜"));
fruits.add(new Fruit("2","麒麟西瓜"));
fruits.add(new Fruit("3","米蕉"));
fruitBasket.setFruits(fruits);
Map<String, List<Fruit>> collect = fruitBasket.getFruits().stream().collect(Collectors.groupingBy(fruit -> fruit.getKindId()));
System.out.println(collect);
/**
* {
* 1=[Fruit(kindId=1, name=砂糖橘), Fruit(kindId=1, name=蜜桔)],
* 2=[Fruit(kindId=2, name=无籽西瓜), Fruit(kindId=2, name=麒麟西瓜)],
* 3=[Fruit(kindId=3, name=米蕉)]
* }
*/
}
}
注:肯定不止这些,我列出来的都是比较常见的!