【Java基础】Java8新特性

【Java基础】Java8新特性

1. 前言

Java8(又称jdk1.8)是Java语言开发的一个主要的版本,其为Java语言、编译器、类库、开发工具与JVM带来了大量新特性

其特点如下:

  • 速度更快
  • 代码更少(新增Lambda表达式)
  • 强大的StreamAPI
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn引擎,运行在JVM上运行JS应用

2. Lambda表达式

Lambda表达式是一个匿名函数

当然也可以理解为Lambda表达式是一段可以传递的代码,使用Lambda表达式可以写出更加简洁、更灵活的代码

@Test
public void test2(){
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);	
        }
    };
    int compare1 = com1.compare(12, 21);
    System.out.println(compare1);

    System.out.println("--------------------------------------------------");

    //Lambda表达式的写法
    Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
    int compare2 = com1.compare(12, 21);
    System.out.println(compare2);

    System.out.println("--------------------------------------------------");

    //方法引用
    Comparator<Integer> com3 = Integer::compare;
    int compare3 = com1.compare(12, 21);
    System.out.println(compare3);
}

上面第一种是匿名内部类的写法,第二种是Lambda表达式的写法,第三种则是方法引用的写法

可见相对于一般的匿名内部类的写法来说,Lambda表达式的写法更见简洁,更加灵活

Lambda表达式的本质是作为函数式接口的实例

函数式接口:如果一个接口中,只声明了一个抽象方法,则该接口就成为函数式接口

其格式如下:

  • 比如该Lambda表达式:(o1, o2) -> Integer.compare(o1, o2)
    • ->:Lambda操作符或箭头操作符
    • ->左边:Lambda形参列表(也就是接口中的抽象方法的形参列表)
    • ->右边:Lambada体(也就是重写抽象方法的方法体)

Lambda表达式有6种格式,下面一一介绍

第一种,无参,无返回值

也就是说匿名内部类要实现的方法没有参数,也可以返回值

@Test
public void test() {
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("我爱Java");
        }
    };
    r1.run();

    System.out.println("-------------------------------------------------");

    Runnable r2 = () -> System.out.println("我爱JavaEE");
    r2.run();
}

第二种,Lambda表达式需要一个参数,但没有返回值

匿名内部类要实现的方法有参数,也可以返回值

@Test
public void test2(){
    Consumer<String> con = new Consumer<String>() {
    @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    con.accept("Java是最好的语言");

    System.out.println("-------------------------------------------------------");

    Consumer<String> con1 = (String s) -> {
    System.out.println(s);
    };
    con1.accept("Spring是最好的生态");
}

语法格式三:数据类型可以省略,因为可以由编译器推断得出,成为类型推断

对于一些泛型的类的实现,可以由前面的具体类型推出参数的类型

@Test
public void test03(){
    Consumer<String> con1 = (s) -> {
        System.out.println(s);
    };
    con1.accept("Spring是最好的生态");
}

语法格式四:Lambda若只需要一个参数,参数的小括号可以省略

这个没什么特别,就是只有一个参数,就可以省略小括号

@Testpublic void test04(){
    Consumer<String> con1 = s -> {
        System.out.println(s);
    };
    con1.accept("Spring是最好的生态");
}

语法格式五:Lambda需要两个或两个以上参数,多条执行语句,并且可以有返回值

有多个参数则不可以省略小括号

有多条执行语句则不可以省略大括号

@Test
public void test05(){
    Comparator<Integer> com1 = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        System.out.println(o1);
        System.out.println(o2);
        return Integer.compare(o1, o2);
    }
    };
    int compare1 = com1.compare(12, 21);
    System.out.println(compare1);

    System.out.println("--------------------------------------------------");

    //Lambda表达式的写法
    Comparator<Integer> com2 = (o1, o2) -> {
        System.out.println(o1);
        System.out.println(o2);
        return Integer.compare(o1, o2);
    };
    int compare2 = com1.compare(12, 21);
    System.out.println(compare2);
}

语法格式6:当Lambda体只有一条语句的时候,return与大括号若有,都可以省略

@Test
public void test06(){
    //Lambda表达式的写法
    Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
    int compare2 = com2.compare(12, 21);
    System.out.println(compare2);
}

3. Java内置的4大核心函数式接口

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

当我们遇到如上类型的方法,就不需要我们重新写了,直接实现该函数式接口即可

第一个:Consumer< T >

@Test
public void test01(){
    happyTime(500.0, new Consumer<Double>() {
        @Override
        public void accept(Double aDouble) {
            System.out.println("花了" + aDouble + "开心");
        }
    });

    System.out.println("-----------------------------------------------");

    happyTime(888.88, money -> System.out.println("花了" + money + "开心"));
}

private void happyTime(Double money, Consumer<Double> consumer){
    consumer.accept(money);
}

第二个:Predicate< T >

@Test
public void test02(){
    List<String> list = Arrays.asList("Java", "C", "C++", "C#", "Go", "Python");
    List<String> strings = filterString(list, new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.contains("C");
        }
    });

    System.out.println(strings);

    System.out.println("----------------------------------------");

    strings = filterString(list, s -> s.contains("C"));

    System.out.println(strings);
}

private List<String> filterString(List<String> list, Predicate<String> predicate){
    ArrayList<String> filterList = new ArrayList<>();
    for (String s : list) {
        if (predicate.test(s)){
            filterList.add(s);
        }
    }
    return filterList;
}

其他两个函数式接口和这两个使用的方法没有多大区别,只是使用的场合不一样而已,所以就不演示了


4. 方法引用

当要传递给Lambda体的操作,已经有实现的方法,那么就可以用方法引用。

方法引用可以看成Lambda表达式的深层次的表达,也就是说,方法引用本质上也是Lambda表达式,也是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖

使用格式:

  • 对象 :: 非静态方法
  • 类 :: 静态方法
  • 类 :: 非静态方法

要求:要求接口中的抽象方法的形参列表和返回值类型必须和方法引用调用的方法的形参列表和返回值类型一样

并且一定要记住抽象方法的需要实现的方法长什么用

(上面这两句话很重要,一定一定要理解)

情况一:对象 :: 非静态方法

比如Supplier里面的get方法要调用Employee的getName方法,就可以写成employee::getName

在这里我的理解是,方法体内调用了什么方法,比如需要调用employee.getName(),那么就写成employee::getName

/**
* 情况一:对象 :: 实例方法
* Consumer中的void accept(T t)
* PrintStream中的void println(T t)
*/
@Test
public void test01(){
    Consumer<String> con1 = s -> System.out.println(s);
    con1.accept("Java");

    System.out.println("--------------------------------------------------");

    PrintStream ps = System.out;
    Consumer con2 = ps::println;
    con2.accept("Spring");
}
/**
* Supplier中的T get()
* Employee中的String getName()
* Employee是自定义的里面有id、name、age、salary
*/
@Test
public void test02(){
    Employee employee = new Employee(1001, "Java", 35, 2000);

    Supplier<String> sup1 = () -> employee.getName();
    System.out.println(sup1.get());

    System.out.println("--------------------------------------------------------------------------------");

    Supplier<String> sup2 = employee::getName;
    System.out.println(sup2.get());
}

情况二:类 :: 静态方法

对于这种格式,我觉得和情况一有点相似,只是这次用类来

/**
* Function中的R apply(T t)
* Math中的Long round(Double d)
*/
@Test
public void test03(){
    Function<Double, Long> function1 = d -> Math.round(d);
    Long apply = function1.apply(10.0);
    System.out.println(apply);

    System.out.println("---------------------------------------------------------");

    Function<Double, Long> function2 = Math::round;
    Long apply1 = function2.apply(100.0);
    System.out.println(apply1);
}

情况三:类 :: 实例方法(有点难理解)

Comparator:这是一个功能界面,因此可以用作lambda表达式或方法引用的赋值对象。

T - 可以由此比较器进行比较的对象的类型

这里可以理解为传入两个参数s1、s2,然后固定了s1.compareTo(s2)

也就是说这里的调用顺序固定了,所以直接写成String::compareTo

(解释有点牵强)

@Test
public void test04(){
    Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
    int compare1 = com1.compare("abc", "abc");
    System.out.println(compare1);

    System.out.println("------------------------------------------------------------");

    Comparator<String> com2 = String::compareTo;
    int compare2 = com2.compare("abc", "abc");
    System.out.println(compare2);
}

5. 构造器引用

构造器引用和方法引用很相似,只是构造器引用是返回一个new的对象而已

下面这种是无参构造,因为实现的类里面有个无参方法是返回一个无参构造的对象

所以会默认调用该对象的无参构造方法来实现

/**
     * 构造器引用
     * Supplier中的T get()
     * Employee的空参构造器:Employee()
     */
@Test
public void test(){

    Supplier<Employee> sup1 = new Supplier<Employee>() {
        @Override
        public Employee get() {
            return new Employee();
        }
    };
    System.out.println(sup1.get());
    System.out.println("----------------------------------------------------------------");

    Supplier<Employee> sup2 = () -> new Employee();
    System.out.println(sup2.get());
    System.out.println("----------------------------------------------------------------");

    Supplier<Employee> sup3 = Employee::new;
    System.out.println(sup3.get());
}

而下面这种,实现的类里面有一个有参构造,传递一个参数进去,返回另一个类型

将Integer类型的数据传进去,然后new一个有ID的Employee

用构造器引用的话,因为它有参,然后返回的数据又是另外一个类型

用Employee::new来实例,就会调用其的有参构造方法

这里面是会匹配对象的构造器里面与泛型的类型,类型匹配一样,就会调用对应构造方法

/**
 * Function中的R apply(T t)
 */
@Test
public void test2() {
    Function<Integer, Employee> fun1 = id -> new Employee(id);
    Employee employee = fun1.apply(1000);
    System.out.println(employee);

    System.out.println("------------------------------");

    Function<Integer, Employee> fun2 = Employee::new;
    Employee employee1 = fun2.apply(888);
    System.out.println(employee1);
}
public Employee(Integer id) {
    System.out.println("id = " + id + "的Employee正在创建.........");
    this.id = id;
}

6. 数组引用

有一说一,数组引用和构造器引用就非常相似了,仔细对比构造引用和数组引用你就会发现

把数组当成一个特殊的类,然后写法就和构造器引用一模一样了

/**
     * 数组引用
     * Function中的R apply(T t)
     */
@Test
public void test3(){
    Function<Integer, String[]> fun1 = length -> new String[length];
    String[] arr1 = fun1.apply(5);
    System.out.println(Arrays.toString(arr1));

    System.out.println("------------------------------");

    Function<Integer, String[]> fun2 = String[]::new;
    String[] arr2 = fun2.apply(10);
    System.out.println(Arrays.toString(arr2));
}

7. Stream API

Stream API 把真正的函数式编程风格引入Java中。

这是目前为止对Java类库最好的补充,因为StreamAPI可以极大提供Java程序猿的生产力,让程序猿写出高效、干净、简洁的代码

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

使用StreamAPI 对集合数据进行操作,就类似使用SQL执行的数据库查询

Stream和Collection集合的区别:Collection是一种静态的内存数据结构,而Steam是有关计算的。前者是主要面对内存,存储在内存的,而后者是面向CPU,通过CPU实现计算的

也就是说,集合讲的是数据,而Stream讲的是计算

但是需要注意以下几点

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

7.1 Stream 操作的三个步骤

  1. 创建Stream

    一个数据源(如集合或者数组),获取一个流

  2. 中间操作

    一个中间操作链,对数据源的数据进行处理

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

    一旦执行终止操作,就执行中间操作链,并产生结果,之后不再被使用

在这里插入图片描述


7.2 创建Stream

方式一:通过集合

Java8中Collection集合被扩展,提供了两个获取流的方法

default Stream< E > stream():返回一个顺序流

default Stream< E > parallelStream():返回一个并行流

顺序流就是和集合的顺序一样

而并行流则不是一样,有几个同时取

@Test
public void test01(){
    List<Employee> employees = EmployeeData.getEmployees();
    //返回一个顺序流
    Stream<Employee> stream = employees.stream();

    //返回一个并行流
    Stream<Employee> employeeStream = employees.parallelStream();
}

方式二:通过数组

Java8中的Arrays的静态方法stream()可以获取数组流

static< T > Stream < T > stream(T[] array):返回一个流

当然数组的类型可以是自定义的

@Test
public void test2() {
    int[] arr = new int[]{1, 2, 3, 4, 5, 6};
    //调用Arrays类返回一个流
    IntStream stream = Arrays.stream(arr);
}

方式三:通过Stream的of()

可以调用Stream类静态方法of(),通过显示创建一个流。它可以接收任意数量的参数

publicl static< T > Stream< T > of(T …values)

可以更具values的类型自动找到其包装类

比如这是的values是1,2,3,4,5,其包装类是Integer

@Test
public void test3(){
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
}

方式四:创建无限流(用得比较少)

创建无限流有两种,一种是迭代,另一种是生成

迭代:public static< T > Stream< T > iterate(final T seed, final UnaryOperator< T > f)

  • 其中seed是种子,也就是在种子的基础上进行迭代
  • UnaryOperator这是迭代的操作,就是在种子的基础上需要进行什么操作

生成:public static< T > Stream< T > generate(Supplier< T > s)

  • Supplier是Java内置4大核心函数式接口之一
@Test
public void test4(){
    //1.迭代
    Stream<Integer> iterate = Stream.iterate(0, t -> t + 2);
    //limit生成十个数据
    //forEach循环输出,里面需要一个消费者
    iterate.limit(10).forEach(System.out::println);

    //2.生成
    Stream.generate(Math::random).limit(10).forEach(System.out::println);
}

7.3 中间操作

中间操作分为三种:

  • 筛选和切片
  • 映射
  • 排序

7.3.1 筛选和切片

@Test
public void test5(){
    List<Employee> list = EmployeeData.getEmployees();
    Stream<Employee> stream = list.stream();

    //filter(Predicate<? super T> predicate)——接收Lambda,从流中排除某些元素
    //之所以这里能直接用employee -> employee.getSalary() > 7000
    //是因为Stream<Employee>已经规定了泛型为Employee

    //查询集合中工资大于7000的
    stream.filter(employee -> employee.getSalary() > 7000).forEach(System.out::println);

    System.out.println("------------------------------------------------------");

    //limit(n) —— 截断流,使其元素不超过给定的数量
    list.stream().limit(10).forEach(System.out::println);

    System.out.println("------------------------------------------------------");

    //skip(n) —— 跳过元素,返回一个扔掉了前n个元素的流
    //若流中元素不足n个,则返回空流,也就是无数据
    list.stream().skip(3).forEach(System.out::println);

    System.out.println("------------------------------------------------------");

    //distinct() —— 筛选,通过流所生成元素的hashCode()和equals()去除重复元素
    //对象中有一个属性不相同都不会被去重
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.add(new Employee(1010, "刘强东", 40, 8000));
    list.stream().distinct().forEach(System.out::println);
}

注意:

  • 一旦执行终止操作,就执行中间操作链,这就流就没用了

7.3.2 映射

map(Function f) —— 接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,将其映射成一个新的函数

下面的代码是将集合中的字母变成大写然后输出,map里面写的是对list集合的每个元素的操作

/**
     * 映射
     */
@Test
public void test6(){
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    list.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
}

小小的练习

/**
     * 映射
     */
@Test
public void test6(){
    //练习:获取员工姓名长度大于3的员工姓名
    List<Employee> employeeList = EmployeeData.getEmployees();
    //这里处理后得到的流是姓名流,就是对字符串操作的流
    //因为经过map处理后,得到的是一个String,所以是一个String流
    Stream<String> nameStream = employeeList.stream().map(Employee::getName);
    nameStream.filter(name -> name.length() > 3).forEach(System.out::println);
}

flatMap(Function f) —— 接收一个函数作为参数,将流中的每一个值都换成另外一个流,然后把所有流连接成一个流

比如现在有这样的一个集合List< String > list = Arrays.asList(“aa”, “bb”, “cc”, “dd”);

需要将集合中的每个字符串用字符的形式输出

如果用map,是这样实现的

  • 首先通过map获得每个字符流的流(套娃)
  • 然后两次forEach,输出
@Test
public void test6(){
    //练习
    Stream<Stream<Character>> streamStream = list.stream().map(s -> fromStringToStream(s));
    streamStream.forEach(s -> {
        s.forEach(System.out::println);
    });

    System.out.println("---------------------------------------------------");
}

/**
     * 将字符串中的多个字符构成的集合转换成对应的Stream实例
     */
private static Stream<Character> fromStringToStream(String str){
    ArrayList<Character> list = new ArrayList<>();
    for (char c : str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}

但是如果使用flatMap则比较简单了

@Test
public void test6(){
    list.stream().flatMap(StreamAPITest::fromStringToStream).forEach(System.out::println);
}

/**
     * 将字符串中的多个字符构成的集合转换成对应的Stream实例
     */
private static Stream<Character> fromStringToStream(String str){
    ArrayList<Character> list = new ArrayList<>();
    for (char c : str.toCharArray()) {
        list.add(c);
    }
    return list.stream();
}

7.3.3 排序

排序的话就比较简单了,排序分为自然排序和定制排序

自然排序:sorted() —— 产生一个新流,其中按照自然顺序排序

@Test
public void test7(){
    //自然排序
    List<Integer> list = Arrays.asList(12, 43, 55, 0, 51, 100, -1);
    list.stream().sorted().forEach(System.out::println);
}

定制排序:sorted(Comparator com)——产生一个新流,其中按照比较器排序

@Test
public void test7(){
    //定制排序
    List<Employee> employees = EmployeeData.getEmployees();
    //按照年龄排序
    employees.stream().sorted((e1, e2) -> {
        return Integer.compare(e1.getAge(), e2.getAge());
    }).forEach(System.out::println);
}

7.4 终止操作

终止操作分为三种:

  • 匹配与查找
  • 归约
  • 收集

7.4.1 匹配与查找

allMatch(Predicate p) —— 检查是否匹配所有元素

例子:是否所有员工年龄都大于18

如果是,则返回true;否则返回false

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
    //allMatch(Predicate p) —— 检查是否匹配所有元素
    //练习:是否所有员工的年龄都大于18
    boolean allMatch = employees.stream().allMatch(employee -> employee.getAge() > 18);
    System.out.println(allMatch);
}

anyMatch(Predicate p) —— 检查是否至少匹配一个元素

只要有一个符合,则返回true

例子:是否存在员工工资大于9000

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
	//anyMatch(Predicate p) —— 检查是否至少匹配一个元素
    boolean anyMatch = employees.stream().anyMatch(employee -> employee.getSalary() > 9000);
    System.out.println(anyMatch);
}

noneMatch(Predicate p) —— 检查是否没有匹配的元素

如果有匹配的元素,则返回false;否则返回true

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
    //startsWith —— 以什么开头
    //如果有则为false,否则为true
    boolean noneMatch = employees.stream().noneMatch(employee -> employee.getName().startsWith("张"));
    System.out.println(noneMatch);
}

findFirst —— 返回第一个元素

这个就是返回集合中第一个元素

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
    Optional<Employee> employee = employees.stream().findFirst();
    System.out.println(employee);
}

findAny —— 返回当前流中的任意元素

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
    Optional<Employee> employee1 = employees.stream().findAny();
    System.out.println(employee1);
}

count —— 返回流中元素的总个数

例子:这里返回的是工资大于5000的人数

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
    long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
    System.out.println(count);
}

max(Comparator c) —— 返回流中最大值

注意:如果是对象的话,必须要细节到哪个成员变量最大值,否则报错

例子:返回工资的最大值

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<Integer> salaryStream = employees.stream().map(employee2 -> employee2.getSalary());
    Optional<Integer> max = salaryStream.max(Integer::compare);
    System.out.println(max);
}

min(Comparator c) —— 返回流中最小值

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
    Optional<Employee> min = employees.stream().min((e1, e2) -> Integer.compare(e1.getSalary(), e2.getSalary()));
    System.out.println(min);
}

forEach(Consumer c) —— 内部迭代

@Test
public void test8(){
    List<Employee> employees = EmployeeData.getEmployees();
	employees.stream().forEach(System.out::println);
}

7.4.2 归约

reduce(T identity, BinaryOperator) —— 可以将流中元素反复结合起来,得到一个值。返回T

identity:初始值

@Test
public void test9(){
    //练习,计算1-10的和
    //下面0的意思是0 + 1 + 2.......
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Integer sum = list.stream().reduce(0, Integer::sum);
    System.out.println(sum);
}

reduce(T identity, BinaryOperator) —— 可以将流中元素反复结合起来,得到一个值。返回Optional< T >

下面两种求和的方法都可以,如果不知道就用第二种

@Test
public void test9(){
  	//计算公司所有员工工资的总和
    List<Employee> employees = EmployeeData.getEmployees();
    // Optional<Integer> reduce = employees.stream().map(employee -> employee.getSalary()).reduce(Integer::sum);
    Optional<Integer> reduce = employees.stream().map(employee -> employee.getSalary()).reduce((d1, d2) -> d1 + d2);
    System.out.println(reduce);
}

7.4.3 收集

collect(Collectors c) —— 将流转换成其他形式。接收一个Collectors接口的实现,用于给Stream中元素做汇总的方法

例子:查找工资大于6000的员工,结果返回为一个List或Set

@Test
public void test10(){
    //collect(Collectors c) —— 将流转换成其他形式。接收一个Collectors接口的实现,用于给Stream中元素做汇总的方法
    List<Employee> employees = EmployeeData.getEmployees();
    List<Employee> collect = employees.stream().filter(employee -> employee.getSalary() > 6000).collect(Collectors.toList());
    System.out.println(collect);
}

8. Optional类

Optional< T >类是一个容器类,它可以保存类型为T的值,代表该值存在。或者仅仅保存null,代表该值不存在。原本null表示一个值不存在,现在Optional可以更好地表达这个概念。并且可以避免空指针异常


8.1 创建Optional类对象的方法

Optional.of(T t):创建一个Optional实例,t必须非空

如果是非空,会报空指针异常

@Test
public void test01(){
    Girl girl = new Girl();
    Optional<Girl> optionalGirl = Optional.of(girl);
    System.out.println(optionalGirl);
}

如果想创建一个空的实例,可以使用以下方法

Optional.empty():创建一个空的Optional实例

@Test
public void test02(){
    Optional<Object> empty = Optional.empty();
    System.out.println(empty);
}

Optional.ofNullable(T t):t可以为空

而这个方法,相当于集成了上面两种方法,因为t既可以是空,也可以不是空

@Test
public void test03(){
    Girl girl = new Girl();
    girl = null;
    Optional<Girl> optionalGirl = Optional.ofNullable(girl);
    System.out.println(optionalGirl);
}

8.2 Optional类的使用

public class Boy {
    private Girl girl;

    public Girl getGirl() {
        return girl;
    }

    public void setGirl(Girl girl) {
        this.girl = girl;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "girl=" + girl +
                '}';
    }
public class Girl {
    private String name;

    public Girl(String name) {
        this.name = name;
    }

    public Girl() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                '}';
    }
}
private String getGirlName(Boy boy){
    return boy.getGirl().getName();
}

@Test
public void test04(){
    Boy boy = new Boy();
    String girlName = getGirlName(boy);
    System.out.println(girlName);
}

类似上面的代码,当我们执行测试类的时候,就会出现空指针异常,因为虽然boy的成员变量girl时空的,而调用的getGirlName方法需要从boy中的成员变量girl获取其名字,但是girl为空,所有报空指针异常。

避免空指针异常,我们可以将getGirlName进行以下的优化

private String getGirlName(Boy boy){
    if (boy != null){
        Girl girl = boy.getGirl();
        if (girl != null){
            return girl.getName();
        }
    }
    return null;
}

同时我们可以使用Optional类中的orElse(T t)来解决这个问题

orElese(T t):如果当前的Optional内部封装的t是非空的,则返回内部的t

如果内部的t是空的,则返回orElese()方法中的参数

private String getGirlName(Boy boy){
    
    Optional<Boy> boyOptional = Optional.ofNullable(boy);
    
    Boy boy1 = boyOptional.orElse(new Boy(new Girl("Java")));
    
    //经过上面处理,boy1一定非空
    Girl girl = boy1.getGirl();
    
    Optional<Girl> girl1 = Optional.ofNullable(girl);
    
    Girl girl2 = girl1.orElse(new Girl("Spring"));
    
    //girl2一定非空
    return girl2.getName();
}

如果想从Optional中拿出数据,可以调用get方法

T get():如果调用这个方法需要很注意,因为如果对象不包含值,则会抛出异常

其源码如下

public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

因此当我们明确知道不是空的话,可以使用Optional.of(T t)与T get()搭配使用

如果我们不明确知道是不是空,可以搭配Optional.ofNullAble(T t)与T orElse(T other)使用

当然,也可以通过调用isPresent方法来判断是否为空


  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起名方面没有灵感

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值