JAVA8实战 - Optional工具类(上)

简介: JAVA8实战 - Optional工具类(上)

前言



没错,这又是一个新的专栏,JAVA8可以说是JAVA划时代的一个版本,几乎是让JAVA焕发了第三春(第二春在JDK5),当然里面的新特性也是十分重要的,虽然Java现在都已经到了10几的版本,但是国内多数使用的版本还是JAVA8,所以这个系列将会围绕Java8的新特性和相关工具做一些总结。希望对大家日常学习和工作中有所帮助。


概述:



  1. 日常工作学习我们大致是如何规避空指针的。
  2. 关于Optional的系统介绍,常见的使用和处理方法
  3. Optional的使用场景以及一些小型案例代码
  4. 来看看《Effective Java》这个作者如何看待Optional这个工具类>


思维导图:



地址:www.mubucm.com/doc/2qS40EL…


网络异常,图片无法展示
|


空指针规避



在讲述Optional之前,我们来看下通常情况下我们是如何防止空指针的。


字符串equals


字符串的操作是最常见的操作,使用字符串的equals方法很有可能抛出空指针异常,比如像下面的代码,如果a变量为Null,则毫无疑问会抛出空指针异常:


a.equals("aaa");
复制代码


建议:使用Objects.equals()或者使用其他工具类方法替代,或者确保obj.equals(target)的obj对象不会为null,比如"test".equals(target)

比如我们使用下面的方法保证equals的时候一致:


public Tank createTank(String check){
    Tank tank = null;
    if(Objects.equals(check, "my")){
        tank = new MyTank();
    }else if(Objects.equals(check, "mouse")){
        tank = new MouseTank();
    }else if (Objects.equals(check, "big")){
        tank = new BigTank();
    }else {
        throw new UnsupportedOperationException("unsupport");
    }
    return tank;
}
复制代码


变量 == 操作


变量的==操作也是用的十分多,通常情况下和null搭配的比较多,我们通常需要注意下面这些事项:


  1. 确保比较类型一致,比如最经典的Integerint比较在超过127的时候为false的问题。
  2. 使用框架工具类的equals() 进行替代
  3. 使用Objects.equals()方法替代

特别强调一下Integer==操作的一些陷阱,特别注意最后一个打印是False,具体的原因有网上很多资料,这里就不啰嗦了:


public static void main(String[] args) {
        Integer a = 1;
        Integer b = 256;
        System.out.println(a == null);
        System.out.println(a == b);
        System.out.println(a == 1);
        System.out.println(b == 256);
        System.out.println(b == 257);
 }/*运行结果:
    false
    false
    true
    true
    false
    */
复制代码


集合元素为null


如果在一个List或者Set中存在Null元素,那么遍历的时候也很容易出现空指针的问题,通常情况下我们可以使用Stream.filter进行过滤,比如像下面这样,这里使用了StringUtils::isNotBlank来判断是否为空字符串并过滤掉所有的空字符串和Null元素:


@Test
public void test2(){
    List<String> list = Arrays.asList("1", null, "2", "", "3");
    System.out.println(list.size());
    List<String> collect = list.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
    System.out.println(collect.size());
}/*运行结果
    5
    3
    */
复制代码


最终的建议如下:

  • 元素null多数情况不常见,但是null的集合对象比较常见
  • 可以编写工具类方法对于集合的内容进行null排除,或者使用lambada表达式处理


map的元素值为null


map也是容易出现null的,比如下面这种情况,一旦get()的返回结果为null,就会出现空指针的异常情况:


map.get("user").getName()
复制代码


建议:

  1. 使用MapUtils获取元素
  2. 每次获取之前需要判断是否为空

第一条建议使用MapUtils,代码都比较简单,唯一需要注意的是使用的时候小心自动装箱的性能和效率问题:


@Test
public void test1(){
    Map<String, Object> keyVal = new HashMap<>();
    keyVal.put("name","value");
    keyVal.put("yes", new Object());
    keyVal.put("intval1", 1);
    Object val1 = MapUtils.getObject(null, "yes");
    Object val2 = MapUtils.getObject(keyVal, "yes");
    String str1 = MapUtils.getString(keyVal, "name");
    int int1 = MapUtils.getInteger(keyVal, "intval1");
    System.out.println(val1);
    System.out.println(val2);
    System.out.println(str1);
    System.out.println(int1);
}
复制代码


类型强转为null


还有一种比较常见的情况就是json转换为null,如果传入空字符串,会导致转化的结果是一个Null值,所以在转化的地方要么对于字符串做判断是否为空的操作,或者对于转换后的对象进行判空,比如下面的代码就需要对于JSON进行处理:


@Test
public void test3(){
    String str = "{\"name\":\"123\"}";
    String str2 = "{\"email\":\"123\"}";
    String str3 = "";
    User map = JSON.parseObject(str, User.class);
    User email = JSON.parseObject(str2, User.class);
    User user2 = JSONObject.parseObject(str3, User.class);
    System.out.println(Objects.isNull(map));
    System.out.println(Objects.isNull(email));
    System.out.println(Objects.isNull(user2));
}/*
true
true
false
*/
复制代码


看了这么多案例,可以发现日常生活中规避空指针是一件非常烦的事情,特别是存在多层嵌套的对象,基本会出现多层的If/else判断,这样会造成代码复杂度增加并且让代码变得十分臃肿,接下来我们就来看下JAVA8是如何使用Optional工具来简化这些操作的。


什么是Optional?



简单介绍


Java8之后新增的一个工具类,在包java.util.Optional<T>,他的作用类似于一个包装器,负责把我们需要操作的对象包装到一个黑盒中,我们可以通过黑盒安全的操作对象的内容。


案例对象:


这里简单构建了两个案例对象进行处理:


static class User{
    private String name;
    private int age;
    private Car car;
    public Car getCar() {
        return car;
    }
    // Tip: 兼容序列化
    public Optional<String> getPersonCarName(){
        return Optional.ofNullable(car.getCarName());
    }
    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;
    }
}
class Car{
    private String carName;
    private String color;
    public String getCarName() {
        return carName;
    }
    public void setCarName(String carName) {
        this.carName = carName;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}
复制代码


Option的三种初始化方式:


/**
     * optional 工具类的初始化方法
     * 1. 介绍三种构造方式
     * 2. 主要区别是初始化传入参数是否允许为null
     * Optional.of 不允许为空
     * Optional.ofNullable 允许为空
     * Optional.empty 构建空Optional对象
     */
@Test
public void testInit() {
    // 这种构造方式不能为null,否则会空指针异常
    Optional<Object> notNull = Optional.of(new Object());
    // 允许为空
    Optional<Object> nullAble = Optional.ofNullable(null);
    // 这种方式是返回一个空Optional,等效Optional.ofNullable(null)
    Optional<Object> empty = Optional.empty();
}
复制代码


  1. Optional.of():表示创建一个不允许是空值的Optional,如果传入为Null会抛出异常
  2. Optional.ofNullable():表示传入的内容允许是空,但是实际上和Optional.empty()效果一致。
  3. Optional.empty():创建一个空的Optional。


map - 对象的内容提取和转化:


在进入具体介绍之前先看看汇总的测试代码以及相关说明,也方便节省各位的时间:


/**
     * optional 如何准确的获取对应的值
     * 1. Optional.map 使用map收集某一个对象的值,
     * 2. Optional.flatMap 根据 Optional 的结果获取参数
     * 插曲:map 和 flatMap 的区别
     * 3. Optional.filter 筛选出符合结果的参数
     */
    @Test
    public void testGetOptionalVal(){
        User user = new User();
        Optional<User> notNull = Optional.ofNullable(user);
        Integer age = notNull.map(User::getAge).orElse(22);
        String name = notNull.map(User::getName).orElse("myname");
        System.out.println(age);
        // Optional.map 收集某一个对象的值
        System.out.println("Optional.map 收集某一个对象的值:"+ age);
        System.out.println("Optional.map 收集某一个对象的值:"+ name);
        // Optional.flagMap 获取多层Optional迭代对象:
        Optional<String> s = notNull.flatMap(User::getPersonCarName);
        Boolean aBoolean = s.map(String::trim).map(StringUtils::isNotBlank).get();
        System.out.println(aBoolean);
        // Optional.map 收集某一个对象的值
        User u1 =new User();
        u1.setName("小王");
        u1.setAge(11);
        User u2 =new User();
        List<User> userLists = new ArrayList<>();
        userLists.add(u1);
        userLists.add(u2);
        Optional<List<User>> notNull2 = Optional.of(userLists);
        // 针对对象集合,使用flagMap获取关联数据
        Optional<User> user1 = notNull2.flatMap(item -> Optional.ofNullable(item.get(0)));
        user1.ifPresent(u-> System.out.println("针对对象集合,使用flagMap获取关联数据 => "+u.getName()));
        // flatMap 的使用场景
        List<String> stringList = new ArrayList<>();
        stringList.add("name1");
        stringList.add("testone");
        stringList.add("other");
        // 使用flatMap处理Optional的返回结果
        Optional<String> stringOptional = Optional.of(u1).flatMap(u -> Optional.ofNullable(u1.getName()));
        System.out.println("flatMap" + stringOptional);
        // 对比:map和flatMap的区别
        // map 方法签名 Function<? super T, ? extends U> mapper
        Optional<String> map = notNull.map(User::getName);
        // flatmap 的方法签名: Function<? super T, Optional<U>> mapper
        Optional<String> stringOptional1 = notNull.flatMap(u -> Optional.ofNullable(u.getName()));
        // 虽然从结果来看两者的结果没有什么区别
        // map => Optional.empty
        //  flatMap => Optional.empty
        // 但是可以明显的看到
        // flatMap Function的返回值为 Optional 类型 Optional<String>
        // map 的 Function返回值为 具体返回类型 String + Optional 的自动封装 Option<String>
        System.out.println("map => " + map);
        System.out.println("flatMap => " + stringOptional1);
        // filter 方法使用
        Optional<String> optional = Optional.of("testNAME");
        String result = optional.filter(str -> str.contains("test")).orElse("not found");
        System.out.println(result);
    }
复制代码


上面提到map获取值的方式map.get("user").getName()这种方式很容易导致空指针异常。对于Optional,我们可以使用map()操作来进行规避,这里可以把Optional想象为一个特殊的集合数据,如果包含一个值,map就会帮我们把流里面的数据进行转化,如果没有值就什么都不做,当然如果我们没有值,可以像案例一样使用orElse()在没有值的时候返回这个值作为默认参数,最终的代码效果如下:


Integer age = notNull.map(User::getAge).orElse(22);
String name = notNull.map(User::getName).orElse("myname");
复制代码


但是,我们也可以看到map也是存在局限性的,对于单个对象操作十分方便,但是一旦遇到多层Optional嵌套。比如Optional处理Optional的处理结果,就需要使用**Optional.flatMap()**的操作。


对比:map和flatMap的区别? 从代码里可以看到,map主要针对的是单一对象结果进行处理,比如我们将对象传给方法引用String::trim进行trim()的操作,这里不需要去思考底层的操作逻辑,只需要知道当遇到Optional需要处理多层的Optional嵌套的时候,就需要使用Optional.flagMap

如果还是很难以理解的话,也可以想象为一个套娃一样套着多层黑盒的操作,我们可以使用flatMap安全的取出属于最底层对象的属性,如果还是不好理解,可以想象为安全的做下面代码的操作:

map.get("user").get("car").getCarName()


插曲:Optional的序列化问题


书中讨论了Optional的序列化问题,书中特别强调:如果你应用的某些字段需要序列化,使用Optional操作有可能发生失效,这里给了一个建议如果一定需要序列化的方式处理的话,可以按照下面的方法处理:


public Optional<String> getPersonCarName(){
    return Optional.ofNullable(car.getCarName());
}
复制代码


解引用Optional对象方法


下面整合了关于Optional大部分常见操作。


/**
     * optional 校验对象属性等是否存在
     * 1. Optional.isPresent 校验对象是否存在,存在返回true
     * 2. Optional.orElse 如果为空返回默认值,不为空不做处理
     * 3. Optional.get 对象必须存在
     * 4. Optional.orElseGet 通过方法提供值
     * 5. Optional.orElseThrow 如果获取为null,抛出指定异常
     * 6. Optional.isPresent 使用ifPresent()来进行对象操作,存在则操作,否则不操作
     * 7. Optional.filter 操作,可以过滤出符合条件的内容
*/
@Test
public void testOptionalValExists() {
    // 对象属性是否存在
    Optional<Object> notNull = Optional.of(new Integer(4));
    boolean present = notNull.isPresent();
    System.out.println("notNull 值是否不为空 " + present);
    Optional<Object> nullAble = Optional.ofNullable("sss");
    System.out.println("nullAble 是否不为空 "+ nullAble.isPresent());
    // Optional.orElse - 如果值存在,返回它,否则返回默认值
    Optional<Object> integerNull = Optional.ofNullable(null);
    Object o = integerNull.orElse("22");
    System.out.println("o 否则返回默认值 " + o);
    //Optional.get - 获取值,值需要存在
    Optional<Object> integerNull2 = Optional.ofNullable(null);
    // 抛出异常 java.util.NoSuchElementException: No value present
    // 来源:throw new NoSuchElementException("No value present");
    // Object o1 = integerNull2.get();
    Optional<Object> integerNull3 = Optional.ofNullable(12321);
    System.out.println("Optional.get 必须存在"+ integerNull3.get());
    //通过方法提供值
    Optional<Object> integerNull4 = Optional.ofNullable(12321);
    Object o1 = integerNull4.orElseGet(() -> String.valueOf(22));
    System.out.println("Optional.orElseGet 通过方法提供值" + o1);
    // 如果获取为null,抛出指定异常
    Optional<Object> integerNull5 = Optional.ofNullable(null);
    // java.lang.RuntimeException: 当前运行代码有误 如果需要抛出异常,请放开下面的代码
    //        Object orElseThrow = integerNull5.orElseThrow(() -> new RuntimeException("当前运行代码有误"));
    //        System.out.println("Optional.orElseThrow 自定义异常" + orElseThrow);
    // Optional.isPresent 使用ifPresent()来进行对象操作,存在则操作,否则不操作
    integerNull5.ifPresent((item) -> {
        System.err.println("Optional.isPresent 如果存在对象,执行如下操作");
    });
    // filter 方法使用
    Optional<String> optional = Optional.of("testNAME");
    String result = optional.filter(str -> str.contains("test")).orElse("not found");
    System.out.println("Optional.filter 过滤出符合条件的对象: " + result);
}/*运行结果:
        notNull 值是否不为空 true
        nullAble 是否不为空 true
        o 否则返回默认值 22
        Optional.get 必须存在12321
        Optional.orElseGet 通过方法提供值12321
        Optional.orElseThrow 自定义异常 当前运行代码有误
        java.lang.RuntimeException: 当前运行代码有误
        Optional.isPresent 如果存在对象,执行如下操作
        Optional.filter 过滤出符合条件的对象: testNAME
    */
复制代码



相关文章
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
63 2
|
9天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
2月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
2月前
|
开发框架 Java 程序员
揭开Java反射的神秘面纱:从原理到实战应用!
本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
47 1
|
2月前
|
算法 搜索推荐 Java
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
这篇文章介绍了如何使用Java后端技术,结合Graphics2D和Echarts等工具,生成包含个性化信息和图表的海报,并提供了详细的代码实现和GitHub项目链接。
111 0
java 后端 使用 Graphics2D 制作海报,画echarts图,带工具类,各种细节:如头像切割成圆形,文字换行算法(完美实验success),解决画上文字、图片后不清晰问题
|
2月前
|
Java
Java 些许公共工具类
Java 些许公共工具类
16 1
|
3月前
|
Java 数据中心 微服务
Java高级知识:线程池隔离与信号量隔离的实战应用
在Java并发编程中,线程池隔离与信号量隔离是两种常用的资源隔离技术,它们在提高系统稳定性、防止系统过载方面发挥着重要作用。
53 0
|
3月前
|
消息中间件 缓存 Java
RocketMQ的JAVA落地实战
RocketMQ作为一款高性能、高可靠、高实时、分布式特点的消息中间件,其核心作用主要体现在异步处理、削峰填谷以及系统解耦三个方面。
178 0
|
机器人 Java
深度解析 Java 的 Optional 类(下)
深度解析 Java 的 Optional 类(下)
116 0
深度解析 Java 的 Optional 类(下)
|
Java
深度解析 Java 的 Optional 类(上)
深度解析 Java 的 Optional 类(上)
136 0
深度解析 Java 的 Optional 类(上)