Java8-Stream流

Java8-Stream基础操做

在学习Stream以前必须有Lambda,的基础,最好有泛型的基础html

Stream是Java8的新特性,能够进行对集合进行一些相似SQL的操做,例如筛选,排序,分组等,极大提升编码效率java

而操做使用链式编程,只须要在源操做上.xxx()方法便可使用mysql

特色:sql

  1. 不是数据结构,不会存储数据
  2. 惰性求值,中间操做只对操做进行记录,只有执行结束操做时才会进行求值操做
  3. 不会修改原来数据源,Stream的操做会保存在另外一个新的对象中

Stream基础操做分类编程

中间操做 终止操做
filter allMatch
limit anyMatch
skip noneMatch
distinct findFirst
map findAny
flatMap count,max,min
sorted reduce
collect

操做Stream只须要3步 :数组

  1. 建立Stream,获得一个数据源,经过例如集合,数组,来获取一个流
  2. 中间操做对数据进行处理
  3. 终止操做,执行中间操做,产生结果

Stream的任何操做都是在stream对象上进行的,也就是说,任何Stream操做首先都须要获取到Stream对象数据结构

建立Stream流

  1. 能够经过Collection系列集合提供的stream()串行流或parallelStream并行流来获取流ide

    public void test(){
        List list=new ArrayList();
        //获取串行流
        list.stream();
        //获取并行流
        list.parallelStream();
    }

    对于串行和并行如今能够简单理解为 串行上的操做是在一个线程中依次完成,而并行是在多个线程上同时执行,咱们如今仅须要使用串行流便可函数

  2. 经过Arrays中的静态方法stream来获取数组流工具

    public void test(){
        User [] users=new User[1];
        Stream<User> stream = Arrays.stream(users);
    }
  3. 经过Stream的静态方法of()

    public void test(){
        String[] strings=new String[10];
        Stream<String> stream = Stream.of(strings);
    }

那么如今已经获取到Stream流了,咱们能够进行相似SQL的操做

为了方便演示,建立Student类

public class Student {
    //学生姓名
    private String name;
    //学生年龄
    private int age;
    //学生所在年级
    private int grade;
    //性别,1男0女
    private int sex;
    //省略构造,get,set,toString方法
}

在类初始化时添加一些数据

private static  List<Student> studentList =new ArrayList<>();
//用于下面演示
private static Student student =new Student("小明",7,1,1);
static{
    studentList.add(student);
    studentList.add(student);
    studentList.add(new Student("小花",7,2,0));
    studentList.add(new Student("小李",9,3,0));
    studentList.add(new Student("小赵",8,3,1));
    studentList.add(new Student("小王",7,3,0));
    studentList.add(new Student("小亮",6,2,1));
    studentList.add(new Student("小光",6,1,0));
}

中间操做

筛选和切片

  • filter 从流中排除某些元素
  • limit 相似mysql的limit 指定元素最大数量,具备短路功能,能够提升效率
  • skip(n) 跳过元素,返回跳过n个元素,不足n个返回一个空流
  • distinct 筛选 经过流所生成元素的hashcode和equals去除重复元素

首先来看filter 从流中排除元素

咱们来看一下filter方法参数

Stream<T> filter(Predicate<? super T> predicate);

//看一下传入参数,函数式接口,可使用Lambda
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

那也就说明,咱们能够本身定义判断条件,结果返回一个boolean类型便可

public void test(){  
    Stream<Student> studentStream = studentList.stream()
        .filter((x) -> x.getAge() > 8);
    System.out.println("未执行终止操做前");
    studentStream.forEach(System.out::println);
}
//结果,很明显只有一个小李年龄大于8岁
未执行终止操做前
User{name='小李', age=9, grade='3', sex=0}

关于上面两个来解释一下,能够理解为x就是每次循环studentList中那个Student对象,使用lambda简化了代码而已,同时咱们证实了全部的中间操做都是惰性求值,只有执行到终止方法才会真正的进行操做

若是不使用Lambda就是下面这样,很是的麻烦

studentList.stream()
    .filter(new Predicate(){
        @Override
        public boolean test(Object o) {
            Student student=(Student) o;
            return student.getAge()>8;
        }
    })
    .forEach(new Consumer() {
        @Override
        public void accept(Object o) {
            System.out.println(o);
        }
    });

接下来看limit,相似于SQL中的limit,可是没有起始值,只有须要的条数

limit具备短路功能,能够提升效率, 假如list中有不少条数据,limit只须要找到设置的数量就中止查找,减小了资源的浪费

public void test3() {
    studentList.stream()
        .limit(2)
        .forEach(System.out::println);
}
//结果,只显示两条
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}

skip(n) 跳过n条数据,若是流中数据不足n条,那么返回个空流, 这里的空流指流里没有数据,而不是返回null

public void test3() {
    studentList.stream()
        .skip(6)
        .forEach(System.out::println);
}
//结果,只获取到数据的最后两条
User{name='小亮', age=6, grade='2', sex=1}
User{name='小光', age=6, grade='1', sex=0}
//====咱们来设置跳过条数大点,让它返回一个空流看看
//由于返回的是个空流,forEach看不到效果,咱们使用另外一个中间操做count,它返回的是流中数据的数量
public void test3() {
    long count = studentList.stream()
        .skip(100)
        .count(); 
    System.out.println(count);
}
//结果 0

distinct 筛选,经过流所生成元素的hashcode和equals去除重复元素

public void test4() {
    studentList.stream()
        //排除重复
        .distinct()
        .forEach(System.out::println);
}
//结果,只有一个小明,咱们在开始时添加了两个同一对象的小明,distinct方法根据hashcode和equals去除了
//其余Student数据....就不展现了

映射

map方法,这个map不是咱们理解的那个相似于hashMap,它的做用是接收一个函数做为参数,该函数会被应用到每一个元素上,并将其映射为一个新的元素

什么意思呢,来看下面的例子,例如数组中存放了学生的英文名字,咱们须要把名字所有转换为大写

@Test
public void test5() {
    String[] names = new String[]{"jame", "joker", "jack"};
    Arrays.stream(names)
        .map((name)-> name.toUpperCase())
        .forEach(System.out::println);
}
//结果
JAME
JOKER
JACK

能够看到,全部的名字都转换为了大写,那么意味着数组中全部的字符串都使用了toUpperCase()转大写方法

咱们来看下面的这个例子

//这个方法将student对象转换为一个Stream<Student >流
public static Stream<Student> getStudentStream(Student student){
    List<Student> studentList=new ArrayList<>();
    studentList.add(student);
    return studentList.stream();
}
//这个方法里调用getStudentStream方法,将每个学生类都包装为Stream类型
public void test() {
    Stream<Stream<Student>> streamStream = studentList.stream()
        .map(MyStream::getStudentStream);
    streamStream.forEach((ss)->{
        ss.forEach(System.out::println);
    });
}
//结果
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}
.....

能够看到在从Stream中获取Student对象时很麻烦,有简单点的方法吗?固然有,来看下面这个方法

flatMap 方法接收一个函数做为参数,将流中每一个值转换为另外一个流,而后把全部流链接为一个流

仍是上面那个例子,咱们将map替换为flatMap方法,结果和上面一致就不展现了

public void test() {
    studentList.stream()
        .flatMap(MyStream::getStudentStream)
        .forEach(System.out::println);
}

什么原理呢?咱们上面的方法getStudentStream调用完成后每个学生对象都变为一个流,可是它们仍是在最初的studentList的流中,相似套娃,流中还有一个流,而flatMap就是将最初的流中的全部流的数据都提取到最初的流中,造成一个流

好比背包中有一个钱包,钱包里面有钱,咱们打开背包,而后打开钱包才能拿到钱,而如今flatMap把钱包中的钱都拿出来放入了背包中,咱们只须要打开背包便可拿到

排序

sorted()若是不传入参数,则根据天然排序

public void test() {
    studentList.stream()
        .sorted()
        .forEach(System.out::println);
}

咱们启动测试,出现了异常

java.lang.ClassCastException: com.jame.basics.stream.Student cannot be cast to java.lang.Comparable

为何呢?原来sorted方法的天然排序会调用Comparable接口的compareTo方法,而咱们的Student根本没有实现这个接口,咱们来实现一下,只是个简单的判断,判断了年龄和年级

@Override
public int compareTo(Object o) {
    Student student = (Student) o;
    if (student.getAge() > this.getAge())
        return 1;
    if (student.getGrade() > this.getGrade())
        return 1;
    return -1;
}

那么咱们再来试一下sorted方法,看一下结果

User{name='小李', age=9, grade='3', sex=0}
User{name='小赵', age=8, grade='3', sex=1}
User{name='小王', age=7, grade='3', sex=0}
User{name='小花', age=7, grade='2', sex=0}
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}
User{name='小亮', age=6, grade='2', sex=1}
User{name='小光', age=6, grade='1', sex=0}

能够看到已经进行降序的排序了,若是想要升序排序只须要修改Student中的compareTo方法便可

那么除了实现Comparabl接口来进行排序,还能有其余办法吗? 还有一个办法

咱们看到除了使用无参,还有个构造能够传入一个lambda,咱们能够在lambda中定义排序规则

public void test() {
    studentList.stream()
        .sorted((s1,s2)->{
            if(s1.getSex()>s2.getSex())
                return 1;
            return -1;
        })
        .forEach(System.out::println);
}
//结果
User{name='小光', age=6, grade='1', sex=0}
User{name='小王', age=7, grade='3', sex=0}
User{name='小李', age=9, grade='3', sex=0}
User{name='小花', age=7, grade='2', sex=0}
User{name='小亮', age=6, grade='2', sex=1}
User{name='小赵', age=8, grade='3', sex=1}
User{name='小明', age=7, grade='1', sex=1}
User{name='小明', age=7, grade='1', sex=1}

咱们定义了根据性别排序,女生排在前面

终止操做

全部的终止操做使用后会直接返回结果,后面不容许在进行任何操做

查找与匹配

allMatch 查找全部元素是否符合条件

//判断全部人的年龄是否都等于8
public void test10(){
    boolean b = studentList.stream()
        .allMatch((x) -> x.getAge() == 8);
}
//结果 false

anyMatch 查找是否有一个符合条件

//判断是否有一个学生年龄为8
public void test10(){
    boolean b = studentList.stream()
        .anyMatch((x) -> x.getAge() == 8);
}
//结果 true

noneMatch 查找是否有不符合条件的 全都不知足才会返回true

//判断是否有年龄不为8的
public void test10(){
    boolean b = studentList.stream()
        .noneMatch((x) -> x.getAge() == 8);
    System.out.println(b);
}
//false 双重否认就是确定,也就是说学生中有年龄为8岁的

findFirst 返回第一个元素

public void test10(){
    Optional<Student> first = studentList.stream()
        .findFirst();
    System.out.println(first.get());
}
//结果 User{name='小明', age=7, grade='1', sex=1}
//为何返回的是一个Optional呢?咱们能够简单理解为Optional就是对Student的一个包装
//由于在Stream中,只有可能出现null的地方,都会返回一个包装类

findAny 返回任意一个元素

public void test10(){
    Optional<Student> first = studentList.stream()
        .findAny();
    System.out.println(first.get());
}
//这个方法通常和filter 方法一块儿使用,单独使用的话通常返回的都是第一个对象

count 返回Stream流中总数

public void test11() {
    long count = studentList.stream()
        .count();
    System.out.println(count);
}
//结果 8

max 返回流中最大对象,咱们能够自定义判断条件

//这里咱们使用学生类的年龄做为判断条件
public void test10() {
    Optional<Student> student = studentList.stream()
        .max((s1, s2) -> {
            if (s1.getAge() > s2.getAge())
                return 1;
            return -1;
        });
    System.out.println(student.get());
}
//结果  User{name='小李', age=9, grade='3', sex=0}

min 返回流中最小对象 一样,咱们也能够自定义判断的条件

//使用学生类的年级来判断
public void test12() {
    Optional<Student> student = studentList.stream()
        .min((s1, s2) -> {
            if (s1.getGrade() > s2.getGrade())
                return 1;
            return -1;
        });
    System.out.println(student.get());
}
//结果  User{name='小明', age=7, grade='1', sex=1}

归约

reduce 能够将流中元素反复结合起来,获得一个值,来看下面例子

public void test15() {
    List<Integer> list=new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    Optional<Integer> reduce = list.stream()
        .reduce((x, y) -> { 
            System.out.println(x+"=="+y);
            return x+y;
        });
    System.out.println(reduce.get());
}
//结果 
x:1==y:2
x:3==y:3
x:6==y:4
10

咱们能够清楚的看到第一次进入这个方法时,流中的第一条数据赋值给了x,而y赋值了第2条数据,当执行完一遍后将结果赋值给了x,y的数据则依次迭代下去

还有一个注意的点: 返回的类型可能为空,因此Stream使用了Optional来包装

reduce还能够设置起始值

public void test15() {
    List<Integer> list=new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);

    Integer reduce = list.stream()
        .reduce(5, (x, y) -> {
            System.out.println("x:" + x + "==y:" + y);
            return x + y;
        });
    System.out.println(reduce);
}
//结果
x:5==y:1
x:6==y:2
x:8==y:3
x:11==y:4
15

当设置起始值后,第一次x的值为设置的起始值,而y则为流中第一条数据的值,之后的遍历每次都会把return的值赋值给x,y的值则根据流中得数据迭代赋值

咱们能够看到返回的类型已经不是Optional,缘由也能够想到,由于赋值了起始值,因此不用怕传入的流中没有数据了

下面是一个小例子,用一个Student对象来保存整个学生集合中年龄最大的值,班级最大的值

public void test13() {
    Student s = new Student();
    Optional<Student> reduce = studentList.stream()
        .reduce((x, y) -> {
            if (x.getAge() < y.getAge())
                s.setAge(y.getAge());
            if (x.getGrade() < y.getGrade())
                s.setGrade(y.getGrade());
            return s;
        });
    System.out.println(reduce.get());
    //结果  User{name='null', age=9, grade='3', sex=0}

若是不太理解能够本身试试

收集

若是咱们想经过Stream流来获取List,Map,Set怎么办呢?

咱们能够经过collect方法,将流转换为其余形式,接收一个Collector接口的实现用于给Stream中元素作总汇的方法

例如咱们想把全部学生名字转换为一个Set集合

能够看到须要一个Collector收集器接口来进行须要的收集操做,而JDK提供了一个Collectors的工具类来提供不一样的收集操做

public void test16() {
    Set<String> nameSet = studentList.stream()
        //map接收一个函数做为参数,该函数会被应用到每一个元素上,并将其映射为一个新的元素
        //而这个函数是getName,能够理解为每一个对象都调用getName方法,那么返回的就全都是一个String类型的
        .map(Student::getName)
        //toSet(),将结果收集为一个Set集合
        .collect(Collectors.toSet());
    for (String s : nameSet) {
        System.out.println(s);
    }
}
//结果小光
小明
小李
....

相似的操做还有toList,就再也不演示,也可使用toCollection来生成Collection接口类型下的其余集合

public void test16() {
    studentList.stream()
        .map(Student::getName)
        .collect(Collectors.toCollection(HashSet::new));
}

也能够经过收集器来获取平均值

public void test16() {
    Double sAveragin = studentList.stream()
        //还有averagingInt averagingLong 就是结果类型不一样,一个为int 一个为long
        .collect(Collectors.averagingDouble(Student::getAge));
    System.out.println(sAveragin);
}
//结果 7.125

获取总和

public void test16() {
    Double sAveragin = studentList.stream()
        //和上面获取平均值同样,还有不一样的返回类型,相似summingInt等等
        .collect(Collectors.summingDouble(Student::getAge));
    System.out.println(sAveragin);
}
//结果 57.0

分组

相似于SQL的分组,那么它的返回类型确定是一个map,该怎么写呢?来看下面的例子

//咱们根据年龄分组
public void test17() {
    Map<Integer, List<Student>> collect = studentList.stream()
        .collect(Collectors.groupingBy(Student::getAge));
    Set<Integer> integers = collect.keySet();
    for (Integer integer : integers) {
        System.out.println("key:"+integer+"===value:"+collect.get(integer));
    }
}
//结果
key:6===value:[User{name='小亮', age=6, grade='2', sex=1}, User{name='小光', age=6, grade='1', sex=0}]
key:7===value:[User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}, User{name='小花', age=7, grade='2', sex=0}, User{name='小王', age=7, grade='3', sex=0}]
key:8===value:[User{name='小赵', age=8, grade='3', sex=1}]
key:9===value:[User{name='小李', age=9, grade='3', sex=0}]

多级分组

好比咱们先按照年级分组,而后根据性别分组该怎么办呢?这时候就须要使用多级分组了

咱们能够看到,这个Collectors.groupingBy()方法中还一个再传入一个Collectors对象,一切都明白了,再写一个收集器放入就能够了,来看下面例子

public void test18() {
    Map<Integer, Map<Integer, List<Student>>> map = studentList.stream()
        .collect(Collectors.groupingBy(
            (x) -> x.getGrade(),
            Collectors.groupingBy((y) -> y.getSex())
        ));
    Set<Integer> keySet = map.keySet();
    for (Integer k : keySet) {
        System.out.println("年级为:"+k);
        Map<Integer, List<Student>> map1 = map.get(k);
        Set<Integer> keySet1 = map1.keySet();
        for (Integer k1 : keySet1) {
            System.out.println(map1.get(k1));
        }
    }
}
}
//结果
年级为:1
[User{name='小光', age=6, grade='1', sex=0}]
[User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}]
年级为:2
[User{name='小花', age=7, grade='2', sex=0}]
[User{name='小亮', age=6, grade='2', sex=1}]
年级为:3
[User{name='小李', age=9, grade='3', sex=0}, User{name='小王', age=7, grade='3', sex=0}]
[User{name='小赵', age=8, grade='3', sex=1}]

若是有多个条件,只须要在后面添加Collectors.groupingBy便可,不过条件多了取的时候就很是麻烦了

咱们也能够根据数值本身设置key的值,须要注意的是key的类型必须和当前分组的类型一致

public void test18() {
    Map<Integer, Map<Integer, List<Student>>> map = studentList.stream()
        .collect(Collectors.groupingBy(
            (x) -> {
                if(x.getGrade()==1){
                    return 111;
                }else if(x.getGrade()==2){
                    return 222;
                }else {
                    return 333;
                }
            },
            Collectors.groupingBy((y) -> y.getSex())
        ));
    Set<Integer> keySet = map.keySet();
    for (Integer k : keySet) {
        System.out.println("最外层k的值:"+k);
        Map<Integer, List<Student>> map1 = map.get(k);
        Set<Integer> keySet1 = map1.keySet();
        for (Integer k1 : keySet1) {
            System.out.println(map1.get(k1));
        }
    }
}
//结果
最外层k的值:333
[User{name='小李', age=9, grade='3', sex=0}, User{name='小王', age=7, grade='3', sex=0}]
[User{name='小赵', age=8, grade='3', sex=1}]
最外层k的值:222
[User{name='小花', age=7, grade='2', sex=0}]
[User{name='小亮', age=6, grade='2', sex=1}]
最外层k的值:111
[User{name='小光', age=6, grade='1', sex=0}]
[User{name='小明', age=7, grade='1', sex=3}, User{name='小明', age=7, grade='1', sex=3}]

partitioningBy 咱们还能够经过某些判断根据true和false来分组

public void test20() {
    Map<Boolean, List<Student>> map = studentList.stream()
        .collect(Collectors.partitioningBy((x) -> x.getGrade() >= 2));
    Set<Boolean> booleans = map.keySet();
    for (Boolean aBoolean : booleans) {
        System.out.println("年级是否大于等于2年级:"+aBoolean);
        List<Student> studentList = map.get(aBoolean);
        for (Student student : studentList) {
            System.out.println(student);
        }
    }
}
//结果
年级是否大于等于2年级:false
User{name='小明', age=7, grade='1', sex=3}
User{name='小明', age=7, grade='1', sex=3}
User{name='小光', age=6, grade='1', sex=0}
年级是否大于等于2年级:true
User{name='小花', age=7, grade='2', sex=0}
User{name='小李', age=9, grade='3', sex=0}
User{name='小赵', age=8, grade='3', sex=1}
User{name='小王', age=7, grade='3', sex=0}
User{name='小亮', age=6, grade='2', sex=1}

summarizingInt 能够直接获取全部元素的某一项数值得最大值,最小值,平均值,总和,咱们一块儿来看下面的例子

public void test21() {
    IntSummaryStatistics collect = studentList.stream()
        .collect(Collectors.summarizingInt((x)->x.getAge()));
    System.out.println("学生年龄的最大值:"+collect.getMax());
    System.out.println("学生年龄的最小值:"+collect.getMin());
    System.out.println("学生年龄的平均值:"+collect.getAverage());
    System.out.println("学生年龄的总和:"+collect.getSum());
}
//结果
学生年龄的最大值:9
学生年龄的最小值:6
学生年龄的平均值:7.125
学生年龄的总和:57

还有相似的例如summarizingDouble,summarizingLong,只不过是返回的类型不一样,这里再也不展现

链接操做

能够经过join方法将结果收集在一块儿,下面例子

public void test22() {
    String collect = studentList.stream()
        .map(Student::getName)
        .collect(Collectors.joining());
    System.out.println(collect);
}
//结果  小明小明小花小李小赵小王小亮小光

还能够设置分割符,开头符号,结尾符号

public void test22() {
    String collect = studentList.stream()
        .map(Student::getName)
        .collect(Collectors.joining(",","我是开头符号","我是结尾符号"));
    System.out.println(collect);
}
//结果
我是开头符号小明,小明,小花,小李,小赵,小王,小亮,小光我是结尾符号

到此,Stream的基本使用就完成了,感谢阅读

本文仅我的理解,若是有不对的地方欢迎评论指出或私信,谢谢٩(๑>◡<๑)۶

相关文章
相关标签/搜索