乐趣区

关于java8:JAVA8实战-Optional工具类

前言

​ 没错,这又是一个新的专栏,JAVA8 能够说是 JAVA 划时代的一个版本,简直是让 JAVA 焕发了第三春(第二春在 JDK5),当然外面的新个性也是非常重要的,尽管 Java 当初都曾经到了 10 几的版本,然而国内少数应用的版本还是 JAVA8,所以这个系列将会围绕 Java8 的新个性和相干工具做一些总结。心愿对大家日常学习和工作中有所帮忙。

概述:

  1. 日常工作学习咱们大抵是如何躲避空指针的。
  2. 对于 Optional 的零碎介绍,常见的应用和解决办法
  3. Optional 的应用场景以及一些小型案例代码
  4. 来看看《Effective Java》这个作者如何对待 Optional 这个工具类 >

空指针躲避

​ 在讲述 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
    */

Optional 的应用场景

​ <s> 从集体角度来说其实应用不是很不便 </s>,上面说下 Optional 的工具应用场景:

1. 封装可能为 Null 的值

​ 还是和所说的 Map 相似,当咱们应用上面的操作获取值的时候,就很容易引发 null:

User user = (User)map.get("user");

​ 于是,Optional 的用途就派上了:

Optional<Object> value = Optional.ofNullable(map.get("user"));

2. 异样和 optional 的比照

​ 通常状况下咱们会应用捕捉异样的形式进行异样的解决,上面是一个常见的字符串转 Int 的办法,个别状况下咱们都会用 try/catch 避免空指针或者转化异样,除非咱们能够保证数据的准确性:

String str = "s";
try{Integer.parseInt(str);
}catch(Exception e){// ....}

​ 应用 Optional 之后能够进行如下的革新,咱们将失去一个平安的 Optional 进行操作,而不是一个可能存在隐式的 NullPointException 问题代码:

public static Optional<Integer> str2Int(String str) {
    try {return Optional.of(Integer.parseInt(str));
    } catch (NumberFormatException e) {return Optional.empty();
    }
}

3. 倡议将 Optional 封装到一个工具类当中:

​ 比方封装成上面这种的简略办法:

private static Optional<Integer> str2Int(String str) {
    try {return Optional.of(Integer.parseInt(str));
    } catch (NumberFormatException e) {return Optional.empty();
    }
}

4. 实战:读取 Properties 值

​ 这里间接把书里的案例拿来用了,这个办法次要作用是读取一个配置文件的 int 值,当读取不到内容的时候,主动给默认值 0。

private static Optional<Integer> str2Int(String str) {
    try {return Optional.of(Integer.parseInt(str));
    } catch (NumberFormatException e) {return Optional.empty();
    }
}

public static int read(Properties properties, String name) {return Optional.ofNullable(properties.getProperty(name))
        .flatMap(OptionalTest::str2Int)
        .filter(i -> i > 0)
        .orElse(0);
}

5. 其余测试:

​ 上面是集体学习的时候一些简略案例尝试:

/**
     * 理论应用场景
     * 1. 咱们要将一个对象的名称全副对立为大写,避免空指针. 然而理论应用来看还是遇到了不少的问题
     */
    @Test
    public void actualUse() {User user = new User();
        // java.lang.NullPointerException 如果编程习惯不好,这种工具类其实并不能解决问题
//        Optional.ofNullable(user).ifPresent(u->{//            String toLowerCase = u.getName().toLowerCase();
//            u.setName(toLowerCase);
//        });
        // 正确的应用形式应该是如下的模式:// 上面的语句放开正文运行打印后果为:SSS
//        user.setName("sss");
        // 如果为 null 会抛出 java.lang.RuntimeException
//        String s = Optional.ofNullable(user).map(User::getName).map(String::toUpperCase).orElseThrow(RuntimeException::new);
//        System.out.println(s);
        // 咱们也能够用另一种形式
        String s2 = Optional.ofNullable(user).map(User::getName).map(String::toUpperCase).orElse("默认值");
        System.out.println(s2);

        // 咱们要对一个前端传入的值进行 split 或者 substring 的时候
        // 案例数据除开分隔符有差别之外无任何差别
        String tags1 = "标签 1, 标签 2, 标签 3, 标签 4";
        String tags2 = "标签 1,标签 2,标签 3,标签 4";
        String[] strings1 = Optional.of(tags1).map(tg -> tags1.split(",")).get();
        System.out.println(strings1[0]);
        String[] strings2 = Optional.of(tags2).map(tg -> tags2.split(",")).get();
        String[] strings3 = Optional.ofNullable(tags2).map(StringUtils::isNotBlank).map(tg -> tags2.split(",")).orElse(new String[]{"ss"});
        System.out.println(strings2[0]);
        System.out.println(Arrays.toString(strings3));
        // 运行后果,如果此时有值,根本无问题
        // 标签 1
        // 标签 1,标签 2,标签 3,标签 4

        // 如果下面的案例当中,传入的为 null 会如何?
        // 所以咱们须要批改下面的格局,确保不论 tag 的值是否存在,都能够只关怀咱们具体须要操作的数据
        // 如果为 null,则没有任何后果解决。咱们能够应用 map 进行各种操作
//        String tags3 = "null,222";
        String tags3 = "a,b|C|d";
        Optional.ofNullable(tags3).map(tg -> tags3.split(",")).map(tg -> {for (int i = 0; i < tg.length; i++) {tg[i] = tg[i].toUpperCase();}
            return tg;
        }).ifPresent(item -> {for (String s : item) {System.out.println(s);
            }
        });

        // JSON 解析的数据失败或者传入的格局不对导致的 NULL
        System.out.print("\nJSON 解析的数据失败或者传入的格局不对导致的 NULL");
        Object parse = JSON.parseObject("{name:\"13\"}", User.class);
        System.out.println(parse);
        Optional.ofNullable("{age:\"1\"}")
                .map(obj -> JSON.parseObject(obj, User.class))
                .ifPresent(System.out::println);
    }/* 运行后果
    默认值
    标签 1
    标签 1,标签 2,标签 3,标签 4
    A
    B|C|D

    JSON 解析的数据失败或者传入的格局不对导致的 NULLcom.xxx.interview.jdk8.OptionalTest$User@548b7f67
    com.xxx.interview.jdk8.OptionalTest$User@1810399e
    */

《Effective java》第 55 条倡议

​ 感兴趣的能够间接跳转上面这些链接(在线浏览网站貌似点不进去,所以只有在线 PDF 网址了 …)

itmyhome.com

​ 以防万一这里再补一个百度链接(如果公众号无奈点击,请浏览原文获取)

链接:https://pan.baidu.com/s/1kQ8E…
提取码:vkv3

​ 既然提到了 Optional 的用法,这里也一并谈谈对于《Effective java》是如何对待这一个工具类的,这一条的题目是:审慎返回 Optional

​ 从集体的角度来看,Optional 的实质作用是:提供了后果的可扩展性以及提供给调用方更多的可操作性,比方调用方能够应用此来依照以前的判断 null 形式解决if(obj.isPresent()),或者应用旧式的 Lambda 操作,比方像上面这样,如果存在值则进行打印的操作,否则什么事件都不会产生:

Optional.ofNullable("{age:\"1\"}")
                .map(obj -> JSON.parseObject(obj, User.class))
                .ifPresent(System.out::println);

​ 还有一点须要留神的是 get() 这个办法,如果不能确保值的确存在,倡议审慎或者避开这个办法,因为一旦为 null 此办法会抛出一个空指针异样。

​ 另外,在之前讲述的办法:orElseThrow这个办法传入的是一个 异样工厂 而不是真正的异样。

​ 前面次要提到的是一些 Java9 的操作,因为本文只波及 Java8 的版本,所以更高版本的内容能够从《Effective Java》这本书外面看到。

​ 上面是作者对于 optional 的一些集体倡议以及编程禁忌:

几点正告

1. 永远不要应用 Optional 返回 Null

​ 首先,该书作者也是提到了 Optional 在日常的编码工作当中如何应用它来躲避一些可能存在的 null 对象操作,同时提出一个重要的禁忌:永远不要用 Optional 的返回值返回 null,比方咱们将下面练习当中的代码改为上面的形式:

private static Optional<Integer> str2Int(String str) {
    try {return Optional.of(Integer.parseInt(str));
    } catch (NumberFormatException e) {
        // 千万不要这么做
        return null;
    }
}

2. 不要应用包装根本类型的 Optional

​ 设计 Optional 的设计师在思考的时候,为根底类型也设置了专属的 Optional 类,然而作者认为这三个类的设计 很垃圾 ,并且倡议 永远不要返回根本包装类型 ,这里验证了一下,发现少了的确少了不少办法,比方:ofNullable 这个办法,这会间接导致你在编写代码的时候减少不必要的判断,并且无奈相互兼容,我想这也是作者不举荐的起因之一吧。

  • OptionalDouble
  • OptionalInt
  • OptionalLong

3. 不要把 optional 作为映射或者键值

​ 这也很好了解,比方像上面这样:

Map<Optional<String>, Optional<Object>> map = // .....

​ 这并不会让你少些代码,反而会减少代码的了解难度和程序的复杂度,所以不倡议把 Optional 用作任何的键值对或者汇合的元素当中。

什么时候应该应用?

 一句话:** 如果无奈返回后果并且返回后果的客户端必须解决的时候,就应该申明 Optional\<T\>。**


哪些状况不实用?

​ 其实下面的正告也提到了一部分内容:

  1. 十分重视性能的场合:因为 Optional 的封装以及相似“流”的操作须要额定的内存开销,所以不适宜一些非常重视性能的状况。
  2. 须要应用键值对或者汇合元素的场合:起因在上文说了,这里不再赘述。
  3. 包装根本类型的 Optional:设计缺点,比照源代码就晓得了。

总结:

​ 总之 Optional 这个工具还是具备肯定的实用价值的,这里十分喜爱《Effective Java》作者对于这个工具类的一些倡议,能够说是切中时弊,果然大神的眼光和教训是十分独到的。

写到最初

​ 写稿不易,求赞,求珍藏。

​ 最初举荐一下集体的微信公众号:“懒时小窝”。有什么问题能够通过公众号私信和我交换,当然评论的问题看到的也会第一工夫解答。

退出移动版