Java基础-Java8新特性

1|0一、Lambda 表达式

在了解 Lambda 之前,首先回顾以下Java的方法。

Java的方法分为实例方法,例如:Integerequals()方法:

public final class Integer{ boolean equals(Object o){...} }

还有静态方法,例如:IntegerparseInt()的方法:

public final class Integer{ public static int parseInt(String s){...} }

无论是实例方法还是静态方法,本质上都相当于过程式语言的函数。例如 C 函数:

char* strcpy(char* dest, char* src);

只不过 Java 的实例方法隐含地传入了一个this变量,即实例方法总是有一个隐含参数this

函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是 Lambda 演算,所以我们经常把支持函数式编程的编码风格称为 Lambda 表达式。

1|11. Lambda 表达式及优点

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。

Java程序中,我们遇到了许多单方法的接口,即:一个接口只定义了一个方法:

  • Comparator
  • Comsumer
  • Runnable
  • Callable

Runnable为例,当我们想要创建新的线程,或者要调用run()方法时,需要创建Runnable实例,以匿名类方式编写如下:

// 匿名内部类 Runnable r1 = new Runnable(){ @Override public void run(){ System.out.println("Hello World!");} };

上方写法比较繁琐,将单方法的接口转换成Lambda表达式,为:

Runnable r1 = () -> System.out.println("Hello Lambda");

1|22. Lambda 语法

在 Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 ->, 该操作符被称为Lambda 操作符箭头操作符。它将 Lambda 分为两个部分:

  • ->左侧:指定了Lambda表达式需要的参数列表 (接口中的抽象方法的形参列表)。
  • ->右侧:指定了Lambda体,是抽象方法的实现逻辑,也即Lambda表达式要执行的功能。(其实就是重写抽象方法的方法体)

1|0语法格式

  1. 没有参数、没有返回值

    Runnable r1 = () -> { System.out.println("Hello Lambda!");};
  2. 需要一个参数、但是没有返回值

    Consumer<String> consumer = (String s) -> {System.out.println(s); };
  3. 数据类型可以省略,因为可以有编译器推断出,称为类型推断

    Consumer<String> con = (s) -> {System.out.println(s); }; // 之后的那个 `String` 可以不写,由于类型推断可以自动识别 ArrayList<String> list = new ArrayList<String>(); // 数组中 int[] arr = new int[]{1,2,3}; // 也可以写成,因为类型推断,不需要再写 `new int[]` int[] arr = {1,2,3};
  4. 若只需要一个参数时,参数的小括号可以省略

    Consumer<String> con1 = s -> {System.out.println(s); };
  5. 若需要两个或两个以上的参数,多条执行语句,并且有返回值

    Comparator<Integer> com2 = (o1, o2) -> {System.out.println(o1); System.out.println(o2); return o1.compareTo(o2); };
  6. Lambda体只有一条语句时,return{}若有,都省略

    Comparator<Integer> com3 = (o1, o2) -> o1.compareTo(o2);

Lambda表达式的本质:作为接口的实例(失去了接口就失去了意义)。

而且这个接口只有一个抽象方法,叫做函数式接口(单方法接口)

1|33. 类型推断

上述Lambda表达式中的参数类型都是由编译器推断得出的。

Lambda 表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。

Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的类型推断

image-20200827214203511

2|0二、函数式接口

2|1什么是函数式接口

只包含一个抽象方法的接口,称为函数式接口。

  • 你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常 (即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口。同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

不加@FunctionalInterface,实际上也是函数式接口,

但是当显式的加@FunctionalInterface之后,就不可以声明两个或两个以上个方法。

java.util.function包下定义了 Java 8 的丰富的函数式接口

2|2如何理解函数式接口

简单说:在 Java8 中,Lambda表达式就是一个函数式接口的实例。

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

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

@FunctionalInterface public interface Runnable{ public abstract void run(); }

2|3自定义函数式接口

@FunctionalInterface public interface MyNumber{ public double getValue(); }

在函数式接口中使用泛型:

@FunctionalInterface public interface MyFunction<T>{ public T getValue(T t); }

2|4Lambda 参数作为参数传递

public static String method(MyFunctionInterface mf, String str){ return mf.method(str); } public static void main(String[] args){ String abcd = "abcd"; String string = method(new MyFunctionInterface(){ @Override public String method(String str){ return str.toUpperCase();} }, abcd); System.out.println(string); }

转换成Lambda表达式的形式:

public static void main(String[] args){ String abcd = "abcd"; String string = method(str -> str.toUpperCase(), abcd);}

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

2|5Java 内置的函数式接口

image-20200828102841899

简单的两个例子:

// 定义一个以函数式接口为参数的方法 public void happyTime(double money, Consumer<Double> consumer){consumer.accept(money); } @Test public void test(){ happyTime(500, new Consumer<Double>(){ @Override public void accept(Double money){ System.out.println("消费:" + money);} }); // 转换成 Lambda 表达式的形式 happyTime(500, x -> System.out.println("消费:" + x));}
public List<String> filterString(List<String> list, Predicate<String> predicate){ ArrayList<String> filter = new ArrayList<>(); for (String str : list){ if (predicate.test(str)){filter.add(str); } } return filter; } @Test public void test(){ List<String> list = Arrays.asList("beijing","tianjin","nanjing","dongjing","xijing","pujing"); List<String> jing = filterString(list, new Predicate<String>(){ @Override public boolean test(String s){ return s.contaions("jing");} }); // 转换成 Lambda 的形式 jing = filterString(list, str -> str.contains("jing")); jing.forEach(System.out::println); }

其余的函数式接口,相当于是以上四种的变种:

image-20200828102905090

3|0三、方法引用和构造器引用

3|1方法引用

1|0对方法引用的理解

方法引用可以看作是Lambda表达式深层次的表达。

换句话说:方法引用本质上就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的另一种写法。

具体来说:什么时候可以使用方法引用?

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
  • 所谓方法引用,是指如果某个方法的参数列表和返回值和函数式接口的抽象方法恰好一致,就可以直接传入方法引用。

1|0方法引用的语法要求及格式:

方法引用的格式:使用操作符::将类(或对象)与方法名分隔开来。即:类(或对象)::方法名.

方法引用主要有三种使用情况:

  • 情况一:对象:: 实例方法名
  • 情况二:类:: 静态方法名
  • 情况三:类:: 实例方法名

方法引用的使用要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致

简单几个小例子:

Consumer<String> con = (x) -> System.out.println(x); // 等同于: Consumer<String> con2 = System.out::println; //--------------------------------------------------------- Comparator<Integer> com = (x, y) -> Integer.compare(x, y); // 等同于 Comparator<Integer> com2 = Integer::compare; int value = com.compare(12,21); //--------------------------------------------------------- BiPredicate<String, String> bp = (x, y) -> x.equals(y); // 等同于 BiPredicate<String, String> bp1 = String::equals; boolean flag = bp1.test("hello", "hi");

1|0方法引用的简单使用

使用Lambda表达式,我们可以不必编写FunctionalInterface接口的实现类,从而简化代码,实际上除了Lambda表达式,我们还可以直接传入方法引用,例如:

public class Main{ public static void main(String[] args){String[] array = new String[]{"Apple","Orange","Banana","Lemon"}; Arrays.sort(array, Main::cmp); System.out.println(String.join(",", array));} public static int cmp(String s1, String s2){ return s1.compareTo(s2); } }

上述代码在Arrays.sort()中直接传入了静态方法cmp的引用,用Main::cmp表示,其中Main是类名,cmp是静态方法名,属于类::静态方法名类型。

因为Comparator<String>接口定义的方法是int compare(String, String),和静态方法int cmp(String, String)相比,除了方法名之外,方法参数列表和返回类型相同,因此,我们说两者的签名一致,可以把方法名作为Lambda表达式传入:

Comparator<String> comparator = (o1, o2) -> o1.compareTo(o2); Arrays.sort(array, comparator); // 但实际上 `int cmp(String, String)` 与 `comparator` 的抽象方法的签名相同 Arrays.sort(array, Main::cmp);

在这里,方法签名只看参数类型返回值类型,不看方法名称,也不看类的继承关系。

我们将代码改成如下格式:

public class Main{ public static void main(String[] args){String[] array = new String[]{"Apple","Orange","Banana","Lemon"}; Arrays.sort(array, String::compareTo); System.out.println(String.join(",", array));} }

String::compareTo方法也符合Lambda定义。观察String.compareTo()的方法定义:

public final class String{ public int compareTo(String o){...} }

这个方法的签名只有一个参数,为什么和int Comparator<String>.compare(String, String)能匹配呢?

因为实例方法有一个隐含的this参数,String类的compareTo()方法在实际调用的时候,第一个隐含参数总是传入this,相当于静态方法,所以情况三:类::实例方法,就是说的此类情景:

public static int compareTo(this, String o);

当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数 (或无参数) 时:ClassName::methodName

所以String.compareTo()方法也可以作为方法引入传入。

3|2构造器引用

除了可以引用静态方法实例方法之外,我们还可以引用构造方法

简单看一个小例子:如果要把一个List<String>转换成为List<Person>,传统的做法代码如下:

class Person{ String name; public Person(String name){ this.name = name; } public String toString(){ return "Person:" + this.name; } } List<String> names = List.of("Bob", "Alice", "Tim"); List<Person> persons = new ArrayList<>(); for(String name : names){ persons.add(new Person(name)); }

要更简单地实现StringPerson的转换,我们可以引用Person的构造方法:

public class Main{ public static void main(String[] args){ List<String> names = List.of("Bob","Alice","Tim"); List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList()); System.out.println(persons); } }

构造器引用ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。

可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致,且方法的返回值即为构造器对应类的对象。

Function<Integer, MyClass> function = (n) -> new MyClass(n); // 等同于: Function<Integer, MyClass> function = MyClass::new;

3|3数组引用

数组引用格式type[]::new

Function<Integer, Integer[]> function = (n) -> new Integer[n]; // 等同于: Function<Integer, Integer[]> function = Integer[]::new;

4|0四、Stream API

4|1什么是 Stream

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

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

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

也可以使用Stream API来并行执行操作,简言之:Stream API提供了一种高效且易于使用的处理数据的方式。

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

4|2为什么要使用 Stream

实际开发中,项目中多数数据源都是来自于MySQL、Oracle等,但现在数据源可以更多了,有MongDB、Redis,而这些NoSQL的数据就需要 Java 层面去处理。

StreamCollection集合的区别:

  • Collection是一种静态的内存数据结构,而Stream是有关计算的。
  • 前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
  1. Stream自己不会存储元素
  2. Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
  3. Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行

4|3Stream 的操作三个步骤:

image-20200829153656343

1|01. 创建 Stream 流

我们通过四种方法可以获得Stream流:

  1. 通过集合创建Stream流,Java8 中的Collection接口被扩展,提供了两个获取流的方法:

    default Stream<E> stream(): 返回一个顺序流; default Stream<E> parallelStream(): 返回一个并行流;
    // 通过集合创建 Stream List<Employee> employees = EmployeeData.getEmployees(); //default Stream<E> stream(): 返回一个顺序流 Stream<Employee> stream = employees.stream(); //default Stream<E> parallelStream(): 返回一个并行流 Stream<Employee> parallelStream = employees.parallelStream();
  2. 通过数据创建Stream流,Java8 中的Arrays的静态方法stream()可以获取数组流:

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

    重载形式,能够处理对应的基本类型的数组:

    public static IntStream stream(int[] array); public static LongStream stream(long[] array); public static DoubleStream stream(double[] array);
    // 基本数据类型 int[] arr = new int[]{1,2,3,4,5,6}; IntStream intStream = Arrays.stream(arr); // 其余类型采用泛型 Employee e1 = new Employee(1001, "Tom"); Employee e2 = new Employee(1002, "Jerry"); Employee[] arr1 = new Employee[]{e1, e2}; Stream<Employee> stream = Arrays.stream(arr1);
  3. 通过Streamof()方法,通过显示值创建一个流,它可以接收任意数量的参数:

    public static<T> Stream<T> of(T... values): 返回一个流
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6);

    创建Stream最简单的方式,传入可变参数即可创建一个能输出确定元素的Stream流。但实际上没有什么实际用途,但是在测试的时候很方便。

  4. 通过调用静态方法Stream.iterate()Stream.generate()创建无限流:

    // 迭代 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); // 生成 public static<T> Stream<T> generate(Supplier<T> s);
    @Override public void test(){ // 迭代 Stream<Integer> stream = Stream.iterate(0, x -> x + 2); stream.limit(10).forEach(System.out::println); // 生成 Stream<Double> stream1 = Stream.generate(Math::random); stream1.limit(10).forEach(System.out::println); }

1|02. 中间操作

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

1|01. 筛选与切片

filter(Predicate p):接收 `Lambda`,从流中排除某些元素; distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素; limit(long maxSize):截断流,使其元素不超过给定数量; skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。
@Test public void test1(){List<Employee> list = EmployeeData.getEmployees(); Stream<Employee> employeeStream = list.stream(); //filter(Predicate p) 接收 Lambda , 从流中排除某些元素 // 查询员工表中薪资大于 7000 的员工信息 employeeStream.filter(e -> e.getSalary() > 7000).forEach(System.out::println); System.out.println("-----------------------------------------------"); //limit(long maxSize) 截断流,使其元素不超过给定数量 // 不能再这样使用,因为上方执行了终止操作,不能再返回中间操作 //employeeStream.limit(3).forEach(System.out::println); list.stream().limit(3).forEach(System.out::println); System.out.println("-----------------------------------------------"); //skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(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); System.out.println("-----------------------------------------------");}

1|02. 映射

map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
@Test public void test2(){ //map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 List<String> strings = Arrays.asList("aa", "bb", "cc"); strings.stream().map(String::toUpperCase).forEach(System.out::println); System.out.println(strings); System.out.println("------------------------------------------------"); List<Employee> employees = EmployeeData.getEmployees(); employees.stream().filter(x -> x.getName().length() > 3).map(Employee::getName).forEach(System.out::println); // 练习: System.out.println("------------------------------------------------"); Stream<Stream<Character>> streamStream = strings.stream().map(StreamAPITest2::fromStringToStream); streamStream.forEach(s ->{s.forEach(System.out::println); }); System.out.println("------------------------------------------------"); //flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 Stream<Character> characterStream = strings.stream().flatMap(StreamAPITest2::fromStringToStream); characterStream.forEach(System.out::println); } /** * 将 String 字符串转换成为 Character 的 stream 流 */ public static Stream<Character> fromStringToStream(String str){ ArrayList<Character> list = new ArrayList<>(); for (Character c : str.toCharArray()){list.add(c); } return list.stream();}

1|03. 排序

sorted():产生一个新流,其中按自然顺序排序 sorted(Comparator com):产生一个新流,其中按比较器顺序排序
@Test public void test3(){ //sorted() 产生一个新流,其中按 ` 自然顺序 ` 排序 List<Integer> list = Arrays.asList(1, 3, 2, 7, 9, 5, 6); list.stream().sorted().forEach(System.out::println); // 抛异常,employee 类没有实现 Comparable 接口 List<Employee> employees = EmployeeData.getEmployees(); //employees.stream().sorted().forEach(System.out::println); //sorted(Comparator com) 产生一个新流,其中 ` 按比较器顺序 ` 排序 employees.stream().sorted((e1, e2) -> { int ageValue = Integer.compare(e1.getAge(), e2.getAge()); if (ageValue != 0){ return ageValue; }else{ return -Double.compare(e1.getSalary(), e2.getSalary());} }).forEach(System.out::println); System.out.println("--------------------------------------"); employees.stream().sorted(Comparator.comparingInt(Employee::getAge)).forEach(System.out::println); }

1|03. 终止操作(终端操作)

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

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

1|01. 匹配与查找

allMatch(Predicate p): 检查是否匹配所有元素 anyMatch(Predicate p): 检查是否至少匹配一个元素 noneMatch(Predicate p): 检查是否没有匹配所有元素 findFirst(): 返回第一个元素 findAny(): 返回当前流中的任意元素 count(): 返回流中元素总数 max(Comparator c): 返回流中最大值 min(Comparator c): 返回流中最小值 forEach(Consumer c): 内部迭代 (使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
//allMatch(Predicate p): 检查是否匹配所有元素 // 练习: 是否所有的员工年龄都大于 18 岁 List<Employee> employees = EmployeeData.getEmployees(); boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18); System.out.println(allMatch); //anyMatch(Predicate p): 检查是否至少匹配一个元素 // 是否存在员工的工资 >10000 boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000); System.out.println(anyMatch); //noneMatch(Predicate p): 检查是否没有匹配所有元素 // 是否存在员工姓 ` 雷 ` boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷")); System.out.println(noneMatch); //findFirst(): 返回第一个元素 Optional<Employee> employee = employees.stream().findFirst(); System.out.println(employee); //findAny(): 返回当前流中的任意元素 Optional<Employee> employee1 = employees.parallelStream().findAny(); System.out.println(employee1); //count(): 返回流中元素总数 long count = employees.size(); long count = employees.stream().filter(e -> e.getSalary() > 5000).count(); System.out.println(count); //max(Comparator c): 返回流中最大值 // 返回最高的工资 Optional<Double> maxSalary = employees.stream().map(Employee::getSalary).max(Double::compareTo); System.out.println(maxSalary); //min(Comparator c): 返回流中最小值 // 返回工资最低的员工 Optional<Employee> minSalaryEmployee = employees.stream().min(Comparator.comparingDouble(Employee::getSalary)); System.out.println(minSalaryEmployee); //forEach(Consumer c): 内部迭代 employees.stream().forEach(System.out::println); // 是集合的遍历操作 employees.forEach(System.out::println);

1|02. 规约

reduce(T iden, BinaryOperator b): 可以将流中元素反复结合起来,得到一个值。返回 T reduce(BinaryOperator b): 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
//reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 T // 练习:计算 1-10 的自然数的和 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(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional<T> // 练习:计算公司中所有员工的工资总和 List<Employee> employees = EmployeeData.getEmployees(); Optional<Double> salarySum = employees.stream().map(Employee::getSalary).reduce(Double::sum); System.out.println(salarySum);

1|03. 收集

collect(Collector c): 将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法
  • Collector接口中方法的实现决定了如何对流执行收集的操作 ( 如收集到List、Set、Map)。
  • 另外,Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

image-20200829164151866

image-20200829164425731

// 查找工资大于 6000 的员工,返回一个 list List<Employee> employees = EmployeeData.getEmployees(); List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList()); employeeList.forEach(System.out::println);

4|4并行流

将一个内容分为多个数据块,使用不同的线程分别处理每个数据块的流,并且含有工作窃取,即一个核对应的任务完成后,会从其他核偷取任务来执行,大大提高了执行效率。

  • 使用parallel切换为并行流

  • 使用sequential切换为串行流

在之前有一个ForkJoin框架,这个而框架就实现了对一个任务的拆分和合并,大大提高了处理速度。

在 Java8 之后我们可以使用并行流来处理 ( 底层仍然是ForkJoin框架 )

Instant start = Instant.now(); LongStream.range(0, 100000000000L).parallel() .reduce(0, Long::sum); Instant end = Instant.now(); System.out.println(Duration.between(start, end).toMillis());

运算一千亿个数循环相加,用时 19s,CPU 利用率基本占满。

5|0Optional 类

5|1为什么要使用 Optional 类

到目前为止,臭名昭著的空指针异常是导致 Java 应用程序失败的最常见原因。

以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava 通过使用检查空值的方式来防止代码污染。

它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为 Java8 类库的一部分。

5|2什么是 Optional 类

Optional(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。

原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。

Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在,则isPresent()方法会返回true,调用get()方法会返回该对象。

5|3Optional 类提供的方法

Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。

1|0创建 Optional 类对象的方法

Optional.of(T t): 创建一个 Optional 实例,t 必须为非空,否则会报空指针错误; Optional.empty(): 创建一个空的 Optional 实例; Optional.ofNullable(T t): 创建一个 Optional 实例,t 可以为空;
Boy boy = new Boy(); //boy = null; // 创建一个 Optional 实例, t 必须为非空,如果为空会报空指针错误 Optional<Boy> boy1 = Optional.of(boy); System.out.println(boy1); // 创建一个空的 Optional 实例 Optional<Object> empty = Optional.empty(); System.out.println(empty); // 创建 Optional 类对象的方法,t 可以为空 boy = null; Optional<Boy> boy2 = Optional.ofNullable(boy); System.out.println(boy2);

1|0判断 Optional 容器是否包含对象

boolean isPresent(): 判断是否包含对象; void ifPresent(Consumer<? super T> consumer): 如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给他;
Boy boy = new Boy(); boy = null; Optional<Boy> boy2 = Optional.ofNullable(boy); // 该 Optional 类是否包含对象 System.out.println(boy2.isPresent()); // 该 Optional 类是否为空 System.out.println(boy2.isEmpty()); // 当 Optional 不为空时,执行 consumer 的实现代码 boy2.ifPresent(System.out::println);

1|0获取 Optional 容器的对象

T get(): 如果调用对象包含值,返回该值,否则抛异常; T orElse(T other): 如果有值将其返回,否则返回指定的 other 对象; T orElseGet(Supplier<? extends T> other): 如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象; T orElseThrow(Supplier<? extends T> exceptionSupplier): 如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常;
Boy boy = new Boy("guosiliang",20); Optional<Boy> boy1 = Optional.ofNullable(boy); //T get(): 如果调用对象包含值,返回该值,如果为空,则抛出异常 System.out.println(boy1.get()); System.out.println("--------------------------"); //T orElse(T other): other 算是备胎,如果有值将其返回,如果没有值则返回 other Boy boy2 = null; Optional<Boy> boy21 = Optional.ofNullable(boy2); Boy boy3 = boy21.orElse(boy); System.out.println(boy3); System.out.println("--------------------------"); //T orElseGet(Supplier<? extends T> other): 如果有值将其返回,否则返回由 Supplier 接口实现提供的对象 Optional<Boy> boy22 = Optional.ofNullable(boy2); Boy boy4 = boy22.orElseGet(Boy::new); System.out.println(boy4); System.out.println("--------------------------"); //T orElseThrow(Supplier<? extends X> exceptionSupplier): 如果有值将其返回,否则抛出由 Supplier 接口实现提供的异常 boy2 = new Boy(); Optional<Boy> boy23 = Optional.ofNullable(boy2); Boy boy5 = null; try { boy5 = boy23.orElseThrow(Exception::new);} catch (Exception e) {e.printStackTrace(); }


EOF

本文作者SalemG
本文链接https://www.cnblogs.com/guosiliang/p/13583711.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!