Java8新特性

Java8新特性

面试问到你Java8的新特性,你能答上来吗

Java现在更新到了很高的版本,不过不建议它一更新我们就去了解、就去学。因为很少概率能用的到,现在很多公司或企业使用的版本都是比较低的。新版本需要经过岁月的考验,才能知道哪些特性是好用的哪些是不好用的。

Java8可以看成自Java5以来最具有革命性的版本,**非常推荐**学习Java8的新特性。

在学习的时候,会遇到的非常多听着、看着都一头雾水的概念。不要被劝退,看代码,带着疑问去学习。怎么去实现?它能用来干嘛?

第一遍学习完过后,只是给自己了解到,“哦,原来有这个东西,可以这么样子用”,想进一步掌握,还是需要多敲代码,多思考。

Java8新特性简介

说一说Java8的新特性?

1.lambda表达式:

  • 速度更快
  • 代码更少(增加了新的语法 )

2.强大的Stream API

  • 并行流和串行流

    并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率

    Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel() 与 sequential()在并行流与顺序流之间进行切换。

  • 便于并行

3.Optional:

  • 最大化减少空指针异常
  • Nashorn引擎,允许在JVM上运行JS应用

4.时间和日期API

1、Lambda 表达式(重点)

关于Lambda 表达式和函数式接口,参考我的另一篇文章 Lambda表达式(重量级新特性)

2、函数式接口

1、使用lambda必须要具有接口,且要求接口中有且只有一个抽象方法。

无论是JDK内置的Runnable,Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才能使用lambda。

2、使用lambda必须具有上下文推断。

也就是方法的参数或者局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个的抽象方法的接口,称为“函数式接口”。

//添加函数式接口的注解,表示限定该接口只能有一个方法体
@FunctionalInterface  
public interface Test01 {
  
  void test01();//方法体1

  //void test02();  
  //方法体2,如果添加了函数式接口注解,该接口有超过一个方法,将会编译报错
}

如何理解函数式接口

Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,java不但可以支持OOP还可以支持OOF(面向函数编程)!

在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。

2.1、Java内置四大函数式接口

函数式接口参数类型返回类型用途
Consumer消费型接口Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier
供给型接口
T返回类型为T的对象,包含方法: T get()
Function<T,R>
函数型接口
TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法: R apply(T t)
Predicate
断定型接口
Tboolean确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t)

下面用代码举两个例子

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class Test02 {

    //1.消费型接口
    public void shopping(double money, Consumer<Double> con) {
        con.accept(money);
    }

    //2.断定型。根据给定的规则,过滤集合中的字符串。规则在Predicate制定
    public List<String> filterString(List<String> list, Predicate<String> pre) {
        List<String> filterString = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)) {  //这里只是判断,在使用的时候再创建规则传过来,灵活。
                filterString.add(s);
            }
        }

        return filterString;
    }

    //main方法
    public static void main(String[] args) {
        Test02 test02 = new Test02();

        //1.测试消费型接口
        //普通写法
        test02.shopping(400, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("买了一个鸡肉卷,花了" + aDouble);
            }
        });

        //Lambda表达式
        test02.shopping(500, (money) -> System.out.println("买了一个汉堡包,花了" + money));


        //2.测试断定型接口
        List<String> list = Arrays.asList("冬瓜", "西瓜", "南瓜", "北瓜", "种豆", "得瓜");

        //普通写法
        List<String> list1 = test02.filterString(list, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                //制定规则,如果s里面存在 瓜 ,返回true
                return s.contains("瓜");
            }
        });
        System.out.println(list1);

        //Lambda表达式
        List<String> list2 = test02.filterString(list, (s) -> !s.contains("瓜"));//这里是不存在瓜,返回true
        System.out.println(list2);
    }
}

还有非常多的内置函数接口,这只是用得比较多的四个。

学习这里的时候,觉得,哎好像我直接定义一个方法就能实现同样的功能了吖,为什么还要这么复杂,还要多写这些函数式接口。

后面发现,使用这些接口,能非常的灵活。比如断定型,假如有多种过滤规则,列如上面的,一个过滤含有“瓜”的字符串,一个过滤不含有的。如果我们不使用函数式接口的话,那就是写两个方法,而且代码大部分都是一样的,造成了冗余,如果是有几十个几百个规则呢?

而函数式接口就是提供了一个模板,我们只需要在使用的时候,顺便将规则传进来就可以了。并不需要修改源码。

3、方法引用和构造器引用(Stream API中用到)

3.1、方法引用

方法引用(Metod References)

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法。
  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
  • 格式:使用操作符::将类(或对象)与方法名分隔开来。
  • 如下三种主要使用情况:
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

看不懂。往下看代码,待会再回来看。

用代码实现三种使用情况,将会用到上面的四个内置函数式接口。

先建一个实体类Employee

import java.util.Objects;
//定义一个员工实体类
public class Employee {
    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() {
        System.out.println("调用了无参构造");
    }

    public Employee(int id) {
        this.id = id;
        System.out.println("调用了id参数构造");
    }

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
        System.out.println("调用了双参构造");
    }

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    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 getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return id == employee.id &&
                age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, salary);
    }
}

提供测试数据类:

package com.yong.method;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author cyh
 * @Date 2021/8/23 16:52
 */

/**
 * 提供测试数据
 */
public class EmployeeData {
    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee(1001,"貂蝉",30,9808));
        list.add(new Employee(1002,"西施",32,5132));
        list.add(new Employee(1003,"杨玉环",18,4567));
        list.add(new Employee(1004,"王昭君",32,7845));
        list.add(new Employee(1005,"西门庆",22,6954));
        list.add(new Employee(1006,"菠萝吹雪",62,4521));
        list.add(new Employee(1007,"韩信",27,9874));
        list.add(new Employee(1008,"王大锤",48,2135));

        return list;
    }
}

测试:

import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 方法引用的使用
 * 1.对象::实例方法名
 * 2.类::静态方法名
 * 3.类::实例方法名
 */
public class Demo01 {
    public static void main(String[] args) {
        Demo01 method = new Demo01();

        //1
        method.test01();
        method.test01_1();

        //2
        method.test02();

        //3
        method.test03();
        method.test03_01();
    }

    /**
     * 1.当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
     * 对象::实例方法
     */
    public void test01() {
        //lambda写法
        Consumer<String> con1 = s -> System.out.println(s);//打印s的值,s的值哪里来
        con1.accept("乌鸦坐飞机");//s的值从这里来,accept方法接收一个值给s


        /**方法引用写法
         *
         * System是一个类
         * System.out: out是PrintStream类型的一个常量
         * void println:println是PrintStream的一个方法
         * 如果使用这个,那么这种情况就是lambda已经有实现的方法了,下面使用方法引用
         * 对象::实例方法名
         */
        PrintStream ps = System.out;
        Consumer<String> con2 = ps::println;  //Consumer无返回值
        con2.accept("老鹰捉小鸡");

        /**运行结果
         * 乌鸦坐飞机
         * 老鹰捉小鸡
         */
    }

    public void test01_1() {
        //再举一个例子说明,带返回值的使用Supplier
        Employee employee = new Employee(1100, "张三", 45, 2255);

        //lambda
        Supplier<String> sup1 = () -> employee.getName(); //注意泛型和返回类型保持一致
        System.out.println(sup1.get());  //输出:张三

        //方法引用
        Supplier<String> sup2 = employee::getName;
        System.out.println(sup2.get());  //输出:张三
    }

    /**
     *  2.类::静态方法名
     *  Integer中的compare是静态方法,小于返回-1,等于返回0,大于返回1
     *  static int compare(int x, int y)
     */
    public void test02(){
        //lambda
        Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(com1.compare(33,44));  // -1

        //类::静态方法名
        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(63,44));  // 1
    }

    /**
     * 3.类::实例方法名
     * String中的compareTo是一个实例方法
     * int compareTo(String anotherString)
     */
    public void test03(){
        //lambda
        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc", "abc"));//返回的是比较的第一个不同的ASCII值的差值,相同返回0

        //类::实例方法名
        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("ccc", "cbb"));
    }

    public void test03_01(){
        Employee employee = new Employee(1001, "李四", 22, 8888);

        //lambda
        Function<Employee, String> fun1 = employee1 -> employee.getName();
        System.out.println(fun1.apply(employee));//李四

        //类::实例方法名
        Function<Employee, Integer> fun2 = Employee::getId;  //注意这里是类Employee,而不是对象employee
        System.out.println(fun2.apply(employee));//1001

    }
}

3.2、构造器引用

一、构造器引用

和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型。

直接看代码

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class Demo02 {
    //构造器引用的使用
    public static void main(String[] args) {
        Demo02 constructor = new Demo02();

        //无参构造
        constructor.test01();

        //单参构造
        constructor.test02();

        //双参构造
        constructor.test03();
    }

    //无参构造
    public void test01(){
        Supplier<Employee> sup1 = () -> new Employee(); //Supplier<T>,返回值类型为T
        Employee employee = sup1.get();  //生成了一个对象
        System.out.println(employee);

        Supplier<Employee> sup2 = Employee::new;
        System.out.println(sup2.get());
        /**运行结果
         * 调用了无参构造
         * Employee{id=0, name='null', age=0, salary=0.0}
         */
    }

    //单参构造
    public void test02(){
        Function<Integer, Employee> fun1 = Employee::new; //Function<T,R>,对类型为T的对象应用操作,并返回R类型的结果
        Employee employee = fun1.apply(1002);
        System.out.println(employee);
        /**
         * 调用了id参数构造
         * Employee{id=1002, name='null', age=0, salary=0.0}
         */
    }

    //双参构造
    public void test03(){
        //BiFunction中的R apply(T t,U u)
        BiFunction<Integer,String,Employee> fun = Employee::new;
        Employee employee = fun.apply(1889, "王五");
        System.out.println(employee);
        /**运行结果
         * 调用了双参构造
         * Employee{id=1889, name='王五', age=0, salary=0.0}
         */
    }
}

将数组看成一个特殊的类即可像构造器引用一样使用。

import java.util.Arrays;
import java.util.function.Function;

public class Demo03 {
    //数组引用的使用
    public static void main(String[] args) {
        Demo03 arrays = new Demo03();

        arrays.test01();
        arrays.test02();
    }


    public void test01() {
        Function<Integer, String[]> fun = String[]::new;
        String[] strings = fun.apply(3);
        System.out.println(Arrays.toString(strings));
        //[null, null, null]
    }

    public void test02(){
        Function<Integer,Employee[]> fun =Employee[]::new;
        Employee[] e = fun.apply(3);
        System.out.println(Arrays.toString(e));
        //[null, null, null],只是创建了一个容器,并没有对象在里面
    }
}

4、Stream API(重点)

4.1、强大的Stream API

  • Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个则是Stream API。

  • Stream APl ( java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

  • Stream是 Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。

  • 使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。

简言之,Stream API提供了一种高效且易于使用的处理数据的方式。

为什么要使用Stream API?

  • 在实际开发中,项目中大多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。(过滤数据:一个是在数据库中过滤,一个是在java层面过滤)
  • Stream和 Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。

什么是Stream?

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,Stream讲的是计算!

注意:

  • Stream 自己不会存储元素。I
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream的操作步骤

  1. 创建Stream

    一个数据源(比如:集合、数组),获取一个流对象

  2. 进行中间操作

    一个中间操作链,对数据源的数据进行想要的处理,只有中间操作不执行!

  3. 终止操作(终端操作)

    一旦执行终止操作,就执行中间操作链,并产生结果。并且不能再被二次使用

在这里插入图片描述

4.2、创建Stream的方式

import com.yong.method.Employee;
import com.yong.method.EmployeeData;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        //获取数据
        List<Employee> employees = EmployeeData.getEmployees();

        //创建方式1,返回一个数据的顺序流,stream
        Stream<Employee> stream1 = employees.stream();

        //创建方式2,返回一个并行流,parallelStream
        Stream<Employee> stream2 = employees.parallelStream();

        //创建方式3,通过数组
        String[] strs = new String[]{"sda", "dsa", "2ads"};
        Stream<String> stream3 = Arrays.stream(strs);

        //创建方式4,通过Stream的of()
        Stream<Integer> stream4 = Stream.of(1, 2, 3, 4, 5);

        //创建方式5:创建无限流
        /**
         * 假设想遍历前10个偶数,下面这么写就是,从0开始,拿到加了2的值,但里面那层又会执行,不停的无限迭代。
         */
        //无限流,forEach是终止操作
        //Stream.iterate(0, t -> t + 2).forEach(System.out::println);//会无限打印偶数。

        //加一个中间操作,遍历前10个偶数。
        Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);

        //生成10个随机数,generate
        Stream.generate(Math::random).limit(10).forEach(System.out::println);

    }
}

4.3、Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!在终止操作时一次性全部处理,这称为惰性求值

1.筛选与切片
方法描述
filter(Predicate p)接收Lambda,从流中排除某些元素
distinct()筛选,通过流所生成元素的 hashCode()和equals()去除重复元素
limit(long maxsize)截断流,使其元素不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补

测试:

过滤 filter:

用上面提供的测试数据,使用流的方法获取工资大于6000的员工的信息

import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.List;
import java.util.stream.Stream;

public class Test02 {
    public static void main(String[] args) {
        //获取测试数据
        List<Employee> list = EmployeeData.getEmployees();

        //拿到流
        Stream<Employee> stream = list.stream();

        //获取工资大于6000员工的数据并打印,filter
        stream.filter(employee -> employee.getSalary() > 6000).forEach(System.out::println);
        /**
        Employee{id=1001, name='貂蝉', age=30, salary=9808.0}
        Employee{id=1004, name='王昭君', age=33, salary=7845.0}
        Employee{id=1005, name='西门庆', age=22, salary=6954.0}
        Employee{id=1007, name='韩信', age=27, salary=9874.0}
        */
    }
}

limit

打印前三条数据

因为stream一旦执行了终止操作,就不能再被使用了,所以如果在上面的代码再调用limit方法,就会报错。

stream.limit(3).forEach(System.out::println);
//java.lang.IllegalStateException: stream has already been operated upon or closed

需要重新生成流,再操作

list.stream().limit(3).forEach(System.out::println);
/*
Employee{id=1001, name='貂蝉', age=30, salary=9808.0}
Employee{id=1002, name='西施', age=32, salary=5132.0}
Employee{id=1003, name='杨玉环', age=18, salary=4567.0}
*/

skip

跳过前三个,如果数据不够跳的,比如跳过100个,则返回空。

list.stream().limit(3).forEach(System.out::println);
/*
Employee{id=1004, name='王昭君', age=33, salary=7845.0}
Employee{id=1005, name='西门庆', age=22, salary=6954.0}
Employee{id=1006, name='菠萝吹雪', age=62, salary=4521.0}
Employee{id=1007, name='韩信', age=27, salary=9874.0}
Employee{id=1008, name='王大锤', age=48, salary=2135.0}
*/

distinct()

筛选,去除重复元素。

//先添加重复元素,然后跳过前5个,再做筛选
list.add(new Employee(1010, "孙尚香", 20, 9988));
list.add(new Employee(1010, "孙尚香", 20, 9988));
list.add(new Employee(1010, "孙尚香", 20, 9988));
list.stream().skip(5).distinct().forEach(System.out::println);

/**
* Employee{id=1006, name='菠萝吹雪', age=62, salary=4521.0} 
* Employee{id=1007, name='韩信', age=27, salary=9874.0} 
* Employee{id=1008, name='王大锤', age=48, salary=2135.0} 
* Employee{id=1010, name='孙尚香', age=20, salary=9988.0} 
*/
2.映射
方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
map ToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
map Tolnt( TolntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
map ToLong( ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

map的使用

import com.yong.method.Employee;
import com.yong.method.EmployeeData;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class TestMap {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");

        //将小写转为大写
        list.stream().map(String::toUpperCase).forEach(System.out::println);
        //输出 AA BB CC DD  //映射关系 aa —> AA...

        //获取员工名字长度小于等于2的员工的名字
        List<Employee> employees = EmployeeData.getEmployees();
        //获得一个name流
        Stream<String> nameStream = employees.stream().map(Employee::getName);
        //使用name流过滤name长度
        nameStream.filter(name -> name.length() < 3).forEach(System.out::println);
        //优化代码
        employees.stream().map(Employee::getName).filter(name-> name.length()<=2).forEach(System.out::println);
        /**
         * 貂蝉
         * 西施
         * 韩信
         */

        //Stream嵌套Stream
        //将list里面的string元素再拆分为字符型元素,并且拿到它的流的流,使用map
        Stream<Stream<Character>> streamStream = list.stream().map(TestMap::toStream);
        //打印流的流
        streamStream.forEach(stream->{
            stream.forEach(System.out::println);
        });

        //使用flagMap,代码简单,效果一样,可以类比List的addALl
        list.stream().flatMap(TestMap::toStream).forEach(System.out::println);
        /**
         * a
         * a
         * b
         * b
         * c
         * c
         * d
         * d
         */
    }

    public static Stream<Character> toStream(String s){
        ArrayList<Character> list = new ArrayList<>();
        for (Character c : s.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }
}

3.排序
方法描述
sorted()产生一个新流,按自然顺序排序
sorted(Comparator com)产生一个新流,按比较器顺序排序

第二个排序需要了解为什么对实体类排序会报错,参考我的另一篇博客Comparable异常

可以在实体类中实现Comparable方法,重写comparaTo方法,也可以自定义比较规则,再传过去。

import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class SortTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(12, -59, 33, 66, 14, 99);
        //自然排序
        list.stream().sorted().forEach(System.out::println);
        /*-59  12  14  33  66  99*/

        //比较器排序,定制排序规则
        List<Employee> employeeList = EmployeeData.getEmployees();

        //根据年龄升序排序
        employeeList.stream().sorted((o1, o2) -> o1.getAge()-o2.getAge()).forEach(System.out::println);
        System.out.println();
        //写法二,方法引用
        employeeList.stream().sorted(Comparator.comparingInt(Employee::getAge)).forEach(System.out::println);
        System.out.println();

        //根据工资升序排序
        employeeList.stream().sorted(Comparator.comparingDouble(Employee::getSalary)).forEach(System.out::println);
        System.out.println();

        //根据年龄升序排序,如果年龄相同,根据工资升序排序
        employeeList.stream().sorted((o1, o2) -> {
            int num = o1.getAge()-o2.getAge();
            if(num!=0){
                return num;
            }else {
                return Double.compare(o1.getSalary(),o2.getSalary());
            }
        }).forEach(System.out::println);

    }
}

4.4、Stream的终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。

  • 流进行了终止操作后,不能再次使用。

1.匹配与查找
常用方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代(使用Collection接口需要用户去做迭代,称为外部迭代。相反,Stream API使用内部迭代——它帮你把迭代做了)

下面来看例子,熟悉这些方法的使用

import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class TestShutDown {
    public static void main(String[] args) {
        //拿到测试数据
        List<Employee> list = EmployeeData.getEmployees();

        //检擦所有员工年龄是否都够18,allMatch
        boolean b = list.stream().allMatch(employee -> employee.getAge() >= 18);
        System.out.println("所有员工年龄是否都够18:"+b);//true

        //是否存在有员工的工资大于1万,存在返回true
        boolean b1 = list.stream().anyMatch(employee -> employee.getSalary() > 10000);
        System.out.println(b1); //false

        //是否没有员工姓赵,noneMatch,存在返回false
        boolean b2 = list.stream().noneMatch(employee -> employee.getName().startsWith("赵"));
        System.out.println(b2);//true

        //返回第一个元素,Optional类型
        Optional<Employee> first = list.stream().findFirst();
        System.out.println(first);
        //Optional[Employee{id=1001, name='貂蝉', age=30, salary=9808.0}]

        //返回任意元素
        Optional<Employee> any = list.parallelStream().findAny();  //使用串行流会显示第一个
        System.out.println(any);
        //Optional[Employee{id=1006, name='菠萝吹雪', age=62, salary=4521.0}]

        //统计求员工工资大于6千的个数,count
        long count = list.stream().filter(employee -> employee.getSalary()>6000).count();
        System.out.println(count); //4

        //返回最高的工资的员工
        Optional<Employee> maxEmployee = list.stream().max(Comparator.comparingDouble(Employee::getSalary));
        System.out.println(maxEmployee);

        //返回最高的工资
        Optional<Double> maxSalary = list.stream().map(e -> e.getSalary()).max(Double::compare);
        System.out.println(maxSalary);

        //最小同理

        //forEach:内部迭代,使用流的终止操作
        list.stream().forEach(System.out::println);

        System.out.println();

        list.forEach(System.out::println);//使用集合的遍历方法

    }
}
2.归约
方法描述
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回Optional

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

map做映射,reduce做规约。

import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

public class TestReduce {
    public static void main(String[] args) {
        //计算整数1-10的和
        Stream<Integer> numStream = Stream.iterate(1, t -> t + 1).limit(10);//生成1-10
        Integer sum = numStream.reduce(0, Integer::sum); //reduce,将流中元素反复结合起来,得到一个值,identity是偏移量
        System.out.println(sum); //55

        //计算员工工资的总和
        List<Employee> list = EmployeeData.getEmployees();
        Stream<Double> moneyStream = list.stream().map(Employee::getSalary); //得到工资的流
        Optional<Double> sumMoney = moneyStream.reduce(Double::sum);//不加identity,返回一个optional
        System.out.println(sumMoney); //Optional[50836.0]
    }
}
3.收集
方法描述
collect(Collector c)将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。

另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用
toListList把流中元素收集到List
toSetSet把流中元素收集到Set
toCollectionCollection把流中元素收集到创建的集合
countingLong计算流中元素的个数
summinglntInteger对流中元素的整数属性求和
averaginglntDouble计算流中元素Integer属性的平均值
summarizinglntlntSummaryStatistics收集流中Integer属性的统计值。如:平均值
import com.yong.method.Employee;
import com.yong.method.EmployeeData;
import java.util.List;
import java.util.stream.Collectors;

public class TestCollector {
    public static void main(String[] args) {
        List<Employee> employees = EmployeeData.getEmployees();

        //查找工资大于6000的员工,返回一个List或Set
        List<Employee> list = employees
                .stream().filter(employee -> employee.getSalary() > 6000)
                .collect(Collectors.toList());//Set改成toSet即可。也可以to其他东西
        list.forEach(System.out::println);
    }
}

5、Optional类(了解即可)

  • 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java8类库的一部分。
  • Optional<T>类(java.util.Optional)是一个容器类它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
  • Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional提供了许多有用的方法,这样我们就不用显示进行空值检验,我们主要关注也是Optional的方法。

创建Optional类对象的方法:

方法作用
Optional.of(T t)创建一个Optional 实例,t必须非空;
Optional.empty()创建一个空的Optional实例
Optional.ofNullable(T t)t可以为null

判断Optional容器中是否包含对象:

方法返回值作用
isPresent()boolean判断是否包含对象
ifPresent(Consumer<? super T> consumer)void如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

获取Optional容器的对象:

方法返回值作用
get()T如果调用对象包含值,返回该值,否则抛异常
orElse(T other)T如果有值则将其返回,否则返回指定的other对象。
orElseGet(Supplier<? extends T> other)T如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
orElse Throw(Supplier<? extends X>exceptionSupplier)T如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

6、时间和日期API(记录下来当成字典查用了)

地址

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值