JDK8新特性
1. 接口相关
- JDK8开始,接口中可以书写普通方法,在返回值类型之前,访问权限修饰符之后,使用default修饰
public interface A{
void m1();
void m2();
public default void m3(){
}
}
- JDK8开始,接口中可以书写静态方法了
public interface B{
public static void m1(){
}
public static void main(String [] args){
}
}
2.集合类相关
- ArrayList 初始容量改为了0 ,之前版本初始化长度为10
- HashMap 数据结构改为了 数组 + 单向链表 + 红黑树 ,之前版本为数组+ 单向链表
3. lambda表达式
lambda表达式
函数式编程 代表语言 Haskell 长期以来 Java语言臃肿的语法结构 一直属于一个缺点
Oracle公司在JDK1.8引入了函数式编程的新特性,有以下两个好处
1.优化现有的Java语法啰嗦,臃肿的写法
2.吸纳更多的开发人员使用Java
函数式编程属于SAM(Single Abstract Method)接口的一个语法糖,
只有一个抽象方法的接口,即可以称之为函数式接口,可以使用注解@Functionalinterface修饰
注意:lambda表达式只能用于接口 并且接口中只能有一个抽象方法
函数式编程主要用于优化之前匿名内部类的写法
lambda表达式格式:(形参列表)->方法体
/**
* @author WHD
* @description TODO
* @date 2023/12/5 9:14
* lambda表达式
*
* 函数式编程 代表语言 Haskell 长期以来 Java语言臃肿的语法结构 一直属于一个缺点
* Oracle公司在JDK1.8引入了函数式编程的新特性,有以下两个好处
* 1.优化现有的Java语法啰嗦,臃肿的写法
* 2.吸纳更多的开发人员使用Java
*
* 函数式编程属于SAM(Single Abstract Method)接口的一个语法糖,
* 只有一个抽象方法的接口,即可以称之为函数式接口,可以使用注解@Functionalinterface修饰
*
* 注意:lambda表达式只能用于接口 并且接口中只能有一个抽象方法
*
* 函数式编程主要用于优化之前匿名内部类的写法
*
* lambda表达式格式:(形参列表)->方法体
*/
public class Note {
public static void main(String[] args) {
B b1 = new B() {
@Override
public void m1() {
System.out.println("匿名内部类的写法重写B接口m1方法");
}
};
b1.m1();
// 有多个参数 有返回值 且方法体有多条语句
E e1 = (a,b)->{
int sum;
sum = a + b;
return sum;
};
System.out.println(e1);
// 无参 无返回值 且方法体只有一条语句
B b2 = ()-> System.out.println("lambda表达式的方式重写B接口m1方法");
b2.m1();
// 有一个参数 没有返回值 且方法体只有一条语句
C c1 = (int a)-> System.out.println("lambda表达式重写C接口m1方法" + a);
c1.m1(100);
C c2 = (b)-> System.out.println("lambda表达式重写C接口m1方法" + b);
c2.m1(200);
// 有多个参数 有返回值 且方法体只有一条语句
D d1 = (int a,int b)-> a + b;
System.out.println(d1.m1(100, 200));
F f1 = new F() {
@Override
void m1() {
}
};
// F f2 = ()-> System.out.println("abc");
}
}
abstract class F{
abstract void m1();
}
interface E{
int m1(int a,int b);
}
interface D{
int m1(int a,int b);
}
interface C{
void m1(int a);
}
@FunctionalInterface
interface B{
void m1();
}
4. 方法引用
方法引用 : Method Reference 是在lambda表达式的基础之上 又一种新的语法格式
针对函数式接口中的抽象方法,不自己编写方法体实现,而是引用一个已经存在的方法的方法体
作为函数式接口中唯一一个抽象方法的方法体
构造方法引用 类名 :: new;
实例方法引用 对象名 :: 方法名;
静态方法引用 类名 :: 方法名;
/**
* @author WHD
* @description TODO
* @date 2023/12/5 10:13
* 方法引用 : Method Reference 是在lambda表达式的基础之上 又一种新的语法格式
* 针对函数式接口中的抽象方法,不自己编写方法体实现,而是引用一个已经存在的方法的方法体
* 作为函数式接口中唯一一个抽象方法的方法体
*
*
*
* 构造方法引用 类名 :: new;
* 实例方法引用 对象名 :: 方法名;
* 静态方法引用 类名 :: 方法名;
*
*/
public class Note {
public static void main(String[] args) {
A a1 = Book :: new;
a1.m1();
System.out.println("a1 = " + a1);
B b1 = Book :: new;
b1.m1("abc", 100, "hello world");
System.out.println("b1 = " + b1);
System.out.println("--------------------------------------------------");
C c1 = System.out :: println;
c1.m1("hello world");
System.out.println("--------------------------------------------------");
String str1 = "abc hello world";
D<String,Boolean> d1 = str1 :: startsWith;
System.out.println(d1.m1("123"));
System.out.println("--------------------------------------------------");
D<Integer,Character> d2 = str1 :: charAt;
System.out.println(d2.m1(1));
System.out.println("--------------------------------------------------");
D<String,String> d3 = str1 ::concat;
System.out.println(d3.m1("世界你好"));
System.out.println("--------------------------------------------------");
D<Boolean,String> d4 = String :: valueOf;
System.out.println(d4.m1(true).length());
D<Double,Double> d5 = Math :: ceil;
System.out.println(d5.m1(2.5));
D<Double,Double> d6 = Math :: abs;
System.out.println(d6.m1(-123.0));
D<Double,Double> d7 = Math :: floor;
System.out.println(d7.m1(2.5));
D<Double,Long> d8 = Math :: round;
System.out.println(d8.m1(3.5));
}
}
interface D<P,R>{
R m1(P p);
}
interface C{
void m1(String str);
}
interface B{
void m1(String a, double b, String c);
}
interface A{
void m1();
}
class Book{
String author;
double price;
String bookName;
public Book() {
System.out.println("Book类无参构造方法");
}
public Book(String author, double price, String bookName) {
System.out.println("Book类有参构造方法");
this.author = author;
this.price = price;
this.bookName = bookName;
}
}
JDK在java.util.function 这个包中提供了大量的函数式接口
这些接口可以分为四种类型
Consumer 消费型接口 void accept(T t) 只接受参数没有返回值
Function<T,R> 功能型接口 R apply(T t) 有参数 有返回值
Predicate 断言型接口 boolean test(T t) 有参数 返回值固定为boolean类型
Supplier 供给型接口 T get() 没有参数 有返回值
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* @author WHD
* @description TODO
* @date 2023/12/5 10:51
* JDK在java.util.function 这个包中提供了大量的函数式接口
* 这些接口可以分为四种类型
* Consumer<T> 消费型接口 void accept(T t) 只接受参数没有返回值
* Function<T,R> 功能型接口 R apply(T t) 有参数 有返回值
* Predicate<T> 断言型接口 boolean test(T t) 有参数 返回值固定为boolean类型
* Supplier<T> 供给型接口 T get() 没有参数 有返回值
*/
public class TestFunctionalInterface {
public static void main(String[] args) {
Consumer<Integer> consumer = System.out::println;
consumer.accept(100);
// -------------------------------------------------------------------
Function<String,Integer> function1 = Integer :: parseInt;
System.out.println(function1.apply("123"));
Function<Double,String> function2 = String :: valueOf;
System.out.println(function2.apply(25.5));
String str1 = "abc hello world";
Function<String,Boolean> function3 = str1 ::equals;
System.out.println(function3.apply("abc hello world"));
// -------------------------------------------------------------------
Predicate<String> predicate1 = str1 :: startsWith;
System.out.println(predicate1.test("abc"));
Predicate<String> predicate2 = str1 :: endsWith;
System.out.println(predicate2.test("abc"));
// -------------------------------------------------------------------
Supplier<Double> supplier1 = Math :: random;
System.out.println(supplier1.get());
}
}
5.Stream流式编程
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,负责存储数据,Stream流讲的是计算,负责处理数据!”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。每次处理都会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
5.1 创建方式
创建 Stream方式一:通过集合Java8 中的 Collection 接口被扩展,提供了两个获取流的方法: default Stream stream() : 返回一个顺序流
创建 Stream方式二:通过数组Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
static Stream stream(T[] array): 返回一个流
创建 Stream方式三:通过Stream的of()可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
public static Stream of(T... values) : 返回一个流
创建 Stream方式四:创建无限流(了解)可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
public static Stream generate(Supplier s)
5.2 中间操作
| 方 法 | 描 述 |
|---|---|
| filter(Predicate p) | 保存符合指定条件的元素 |
| distinct() | 筛选,通过流所生成元素的equals() 去除重复元素 |
| limit(long maxSize) | 保留指定个数的前 |
| skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
| sorted() | 产生一个新流,其中按自然顺序排序 |
| map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
| flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
5.3 终止操作
| 方法 | 描述 |
|---|---|
| boolean allMatch(Predicate p) | 检查是否匹配所有元素 |
| boolean anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
| boolean noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
| Optional findFirst() | 返回第一个元素 |
| long count() | 返回流中元素总数 |
| Optional max() | 返回流中最大值 |
| Optional min() | 返回流中最小值 |
| void forEach(Consumer c) | 迭代 |
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author WHD
* @description TODO
* @date 2023/12/5 14:06
*/
public class TestStream {
static List<Student> list = new ArrayList<>();
// 1.查找地址为深圳的人员信息
// 2.查找地址为深圳 并且 性别为男 的人员信息
// 3.查找地址为深圳 并且 性别为男 并且 成绩小于80 的人员信息
@Test
public void createStream(){
// 创建Stream方式1 通过Collection接口中的普通方法 stream()
Stream<Student> stream = list.stream();
// 创建 Stream方式二:通过数组
IntStream stream1 = Arrays.stream(new int[]{1,2,3,4,5});
// 创建 Stream方式三:通过Stream的of()
Stream<Character> a = Stream.of('a', 'b', 'c', 'd');
// 创建 Stream方式四:创建无限流(了解)
// Stream<Double> generate = Stream.generate(Math::random);
//
// generate.forEach(System.out::println);
Stream.generate(Math :: random).limit(5).forEach(System.out::println);
}
@Test
public void filter1(){
// 1.查找地址为深圳的人员信息
list.stream().filter((stu)->stu.getAddress().equals("深圳")).forEach(System.out::println);
}
@Test
public void filter2(){
// 2.查找地址为深圳 并且 性别为男 的人员信息
list.stream().filter((stu)->stu.getAddress().equals("深圳")).filter((stu)->stu.getSex() == '男').forEach(System.out::println);
}
@Test
public void filter3(){
// 3.查找地址为深圳 并且 性别为男 并且 成绩小于80 的人员信息
list.stream().filter((stu)->stu.getAddress().equals("深圳")).filter((stu)->stu.getSex() == '男').filter((stu)->stu.getScore() < 80);
}
@Test
public void distinct(){
// 去除集合中的重复元素 重复的元素只展示一个
list.stream().distinct().forEach(System.out::println);
}
@Test
public void limit(){
// 保留集合中的前5个元素
list.stream().limit(5).forEach(System.out::println);
System.out.println("--------------------------------------------");
// 查找地址为 广州的人员信息 保留前1个
list.stream().filter((stu)->stu.getAddress().equals("广州")).limit(1).forEach(System.out::println);
}
@Test
public void skip(){
// 跳过指定个数的元素
list.stream().skip(5).forEach(System.out::println);
}
@Test
public void sorted(){
// 针对集合中的元素 按照年龄排序
list.stream().sorted().forEach(System.out::println);
}
@Test
public void sorted1(){
list.stream().sorted((s1,s2)->(int)(s1.getScore() - s2.getScore())).forEach(System.out::println);
}
@Test
public void map(){
// 将一个流中的元素经过加工处理 映射为新的元素
Stream.of("a","b","c").map((str)-> str.toUpperCase()).forEach(System.out::println);
}
@Test
public void flatMap(){
// 每个字符串对象调用buildStringToCharacterStream()方法 会得到一个流
// 调用flatMap方法 将多个流整合为一个流
// 具体应用场景:
// 查询年龄大于20的人
// 查询分数大于80的人
// 查询性别为男的人
Stream.of("hello","world","abc").flatMap(TestStream::buildStringToCharacterStream).limit(3).forEach(System.out::println);
}
public static Stream<Character> buildStringToCharacterStream(String str){
List<Character> list = new ArrayList<>();
for(Character ch : str.toCharArray()){
list.add(ch);
}
return list.stream();
}
static{
Student stu1 = new Student("赵四", 15, '女', "深圳", 80);
Student stu2 = new Student("富贵", 25, '男', "广州", 90);
Student stu3 = new Student("小宝", 15, '男', "深圳", 50);
Student stu4 = new Student("刘能", 35, '男', "广州", 80);
Student stu5 = new Student("大拿", 25, '男', "东莞", 8);
Student stu6 = new Student("广坤", 55, '男', "东莞", 85);
Student stu7 = new Student("玉田", 45, '女', "东莞", 66);
Student stu8 = new Student("刘英", 55, '女', "云南", 55);
Student stu9 = new Student("刘英", 45, '女', "惠州", 55);
Student stu10 = new Student("刘英", 65, '女', "惠州", 55);
list.add(stu1);
list.add(stu2);
list.add(stu3);
list.add(stu4);
list.add(stu5);
list.add(stu6);
list.add(stu7);
list.add(stu8);
list.add(stu9);
list.add(stu10);
}
@Test
public void allMatch(){
// boolean allMatch(Predicate p) 判断是否所有元素都符合某个条件
// 判断是否所有人员都是成年人
boolean b1 = list.stream().allMatch(stu -> stu.getAge() >= 18);
System.out.println("b1 = " + b1);
// 判断地址为深圳 并且 性别为男的人员信息 是否分数都大于50
boolean b2 = list.stream().
filter(student -> student.getAddress().equals("深圳")).
filter(student -> student.getSex() == '男').allMatch(student -> student.getScore() > 40);
System.out.println("b2 = " + b2);
}
@Test
public void anyMatch(){
// 判断流中的元素是否至少有一个匹配 anyMatch(Predicate p)
// 判断集合中是否包含至少一个女性
boolean b = list.stream().anyMatch(student -> student.getSex() == '女');
System.out.println("b = " + b);
// 判断集合中是否包含至少一个分数大于95的人员
boolean b1 = list.stream().anyMatch(student -> student.getScore() > 95);
System.out.println("b1 = " + b1);
}
@Test
public void noneMatch(){
// 判断流中的元素是否都不匹配 noneMatch(Predicate p)
// 判断集合中是否不包含地址为云南的人员信息
boolean b1 = list.stream().noneMatch(student -> student.getAddress().equals("云南"));
System.out.println("b1 = " + b1);
}
@Test
public void findFirst(){
// 返回流中的第一个元素
Optional<Student> first = list.stream().findFirst();
System.out.println("first = " + first);
// 查找第一个 年龄大于30 并且 地址信息不为空 并且性别为女
Optional<Student> first1 = list.stream().filter(student -> student.getAge() > 30).filter(student -> student.getAddress() != null).filter(student -> student.getSex() == '女').findFirst();
System.out.println("first1 = " + first1);
}
@Test
public void count(){
// count() 统计流中元素的个数
long count = list.stream().count();
System.out.println("count = " + count);
// 统计 地址信息为东莞 并且 分数 大于60的人员个数
long count1 = list.stream().filter(student -> student.getAddress().equals("东莞")).filter(student -> student.getScore() > 60).count();
System.out.println("count1 = " + count1);
}
@Test
public void max(){
// max() 返回流中最大的元素
Optional<Student> max1 = list.stream().max((s1, s2) -> s1.getAge() - s2.getAge());
System.out.println("max1 = " + max1);
// min() 返回流中最小的元素
Optional<Student> min = list.stream().min((s1, s2) -> s1.getAge() - s2.getAge());
System.out.println("min = " + min);
}
}
import java.util.Objects;
/**
* @author WHD
* @description TODO
* @date 2023/12/5 14:06
*/
public class Student implements Comparable<Student> {
private String name;
private int age;
private char sex;
private String address;
private double score;
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 char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public Student() {
}
public Student(String name, int age, char sex, String address, double score) {
this.name = name;
this.age = age;
this.sex = sex;
this.address = address;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
", address='" + address + '\'' +
", score=" + score +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
if (sex != student.sex) return false;
if (Double.compare(student.score, score) != 0) return false;
if (!Objects.equals(name, student.name)) return false;
return Objects.equals(address, student.address);
}
@Override
public int hashCode() {
int result;
long temp;
result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
result = 31 * result + (int) sex;
result = 31 * result + (address != null ? address.hashCode() : 0);
temp = Double.doubleToLongBits(score);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public int compareTo(Student stu) {
return this.getAge() - stu.getAge();
}
}
6.Optional类
到目前为止,空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
| 方法 | 描述 |
|---|---|
| of(Object obj) | 根据传入对象获取一个Optional对象,此对象不能为null |
| empty() | 包装一个保存有null的Optional对象 |
| ofNullable(Object obj) | 根据传入对象获取一个Optional对象,此对象可以为null |
| get() | 获取Optional中保存的对象,如果为null,则报空指针异常 |
| isPresent() | 表示判断Optional是否为null,为null结果为false,不为null结果为true |
| ifPresent(Consumer<? super T> consumer) | 如果Optional对象中的对象不为null,则消费此对象,否则不消费 |
| orElse(T other) | 如果当前Optional对象中保存对象为null,则使用传入对象 |
| orElseGet(Supplier<? extends T> other) | 如果当前Optional对象中为null则获取到另外一个对象,否则不获取 |
| orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果当前Optional对象中为null则抛出异常,否则不抛出 |
import org.junit.Test;
import java.util.Optional;
import java.util.OptionalInt;
/**
* @author WHD
* @description TODO
* @date 2023/12/6 9:23
* Optional类可以保存一个为空 或者 不为空的对象
* 通过一些方法体现值是否为空 代码阅读性更强 避免了繁琐的 非空判断的过程 使代码更加简洁
*/
public class TestOptional {
@Test
public void of(){
// of(T t) 根据传入对象获取一个Optional对象,此对象不能为null
String str1 = "abc";
String str2 = null;
Optional<String> op1 = Optional.of(str1);
System.out.println("op1 = " + op1);
Optional<String> op2 = Optional.of(str2);
System.out.println("op2 = " + op2);
}
@Test
public void empty(){
// empty() 包装一个保存有null的Optional对象
Optional<Object> op1 = Optional.empty();
System.out.println("op1 = " + op1);
}
@Test
public void ofNullable(){
// 根据传入对象获取一个Optional对象,此对象可以为null
String str1 = "abc";
String str2 = null;
Optional<String> op1 = Optional.ofNullable(str1);
System.out.println("op1 = " + op1);
Optional<String> op2 = Optional.ofNullable(str2);
System.out.println("op2 = " + op2);
}
@Test
public void get(){
// get() 获取Optional对象中的对象 如果为空 则抛出空指针异常 NPE异常 NullPointerException
Optional<String> op1 = Optional.of("abc");
System.out.println(op1.get());
Optional<Character> op3 = Optional.ofNullable('A');
System.out.println(op3.get());
Optional<Object> op2 = Optional.empty();
System.out.println(op2.get());
}
@Test
public void isPresent(){
// isPresent() 表示判断Optional是否为null,为null结果为false,不为null结果为true
Optional<String> op1 = Optional.of("abc");
System.out.println(op1.isPresent());
Optional<Object> op2 = Optional.empty();
System.out.println(op2.isPresent());
Optional<Integer> op3 = Optional.ofNullable(123);
System.out.println(op3.isPresent());
}
@Test
public void ifPresent(){
// ifPresent(Consumer<? super T> consumer) 如果Optional保存的对象不为空 则消费 为空 则不消费
Optional<Object> op1 = Optional.of(3.5);
op1.ifPresent(System.out::println);
Optional<Object> op2 = Optional.empty();
op2.ifPresent(System.out::println);
Optional<Character> op3 = Optional.ofNullable('a');
op3.ifPresent(System.out::println);
}
@Test
public void orElse(){
// orElse(T other) 如果Optional对象中保存的对象为空 则使用传入对象 否则不使用
Optional<Character> op1 = Optional.of('A');
Character ch1 = op1.orElse('B');
System.out.println("character = " + ch1);
Optional<Object> op2 = Optional.empty();
Object abc = op2.orElse("abc");
System.out.println("abc = " + abc);
Optional<Object> op3 = Optional.ofNullable(null);
Object str = op3.orElse("hello world");
System.out.println("str = " + str);
}
@Test
public void orElseGet(){
// orElseGet(Supplier<? extends T> other)
// 如果当前Optional对象中为null则获取到另外一个对象,否则不获取
Optional<String> op1 = Optional.of("abc");
String str1 = "hello world";
System.out.println(op1.orElseGet(() -> str1.toString()));
System.out.println("---------------------------------------");
Optional<Object> op2 = Optional.empty();
System.out.println(op2.orElseGet(() -> str1.toUpperCase()));
System.out.println("---------------------------------------");
Optional<Object> op3 = Optional.ofNullable(null);
System.out.println(op3.orElseGet(Math::random));
}
@Test
public void orElseThrow(){
// orElseThrow(Supplier<? extends X> exceptionSupplier)
// 如果Optional保存的为空 则抛出异常
// 如果不为空 则抛异常
Optional<String> op1 = Optional.of("abc");
op1.orElseThrow(()->new RuntimeException("对象不能为空1"));
Optional<Object> op3 = Optional.ofNullable(null);
op3.orElseThrow(()->new RuntimeException("对象不能为空2"));
Optional<Object> op2 = Optional.empty();
op2.orElseThrow(()->new RuntimeException("对象不能为空3"));
}
}