文章目录

  • NPE
  • 如何防止 NPE
  • 如何优化判空 ——Optional
  • 实例化
  • isPresent & get
  • ifPresent
  • filter
  • orElse & orElseThrow
  • map & flatMap
  • 重构


NPE

NullPointerException(NPE):空指针异常。

先抛出我理解的为何会发生异常:

当空对象尝试调用方法的时候,就会发生 NPE

public class ForCheckAnyObject {

    public static void main(String args[]) {
        //初始化一个空的 Map 对象
        Map<String, Object> map = new HashMap<>();
        String a = (String) map.get("a");
        System.out.println(a);

        //这里为了演示空对象调用方法,随便用了 String 的一个方法
        String A = a.toLowerCase();
        System.out.println(A);
    }
}

null
Exception in thread "main" java.lang.NullPointerException
	at testAll.ForCheckAnyObject.main(ForCheckAnyObject.java:35)
    
//其中的第 35 行,就是 String A = a.toLowerCase();

Java 是面对对象语言,我们要调用一个类中的方法,必须要对其进行实例化,用这个类的对象去调用这个方法。当这个对象为空的时候,而我们又尝试去调用这个对象所能调用的方法,那么就会报 NPE

如何防止 NPE

我以前(JDK8以前)写代码的时候,拿到一个对象,先去做判空操作(这当然是在几十次的教训下,形成的习惯):

if(a!=null){
    //操作
}

要知道,我们写业务代码,获得数据库返回的数据,我们需要某一个字段,很可能有几个层级才能取到(而像上面这样的,当然很好,判断一下就可以了)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npuxRmVQ-1578572765914)(./imageAll/多层对象取值.png)]

按照我们刚开始写代码的性格,一定是下面这样:只会考虑到都有值的情况。

//当然这里的 Class 是班级的意思
University.getColleage().getClass().getName();

当然这样遇到有空值的时候,就会报 NPE 了。

所以我们会进行改进,在报错之前进行判断,不会出错后直接中断程序:

University.getColleage().getClass().getName();
      if(University!=null){
          Colleage colleage=University.getColleage();
          if(colleage!=null){
              Class class=colleage.getClass();
              if(class!=null){
                  String name=class.getName();
           }
      }
}

上面的代码,安全性来讲是没啥问题了,但是代码很臃肿,不易阅读。

如何优化判空 ——Optional

JDK8 引入了 Optional 类, 依靠 Optional 类提供 API,我们可以写出既安全又具有阅读性的代码。

实例化

在 Java 中我们像用一个类当中的方法,那么我们首先需要对其进行实例化:

  • new,因为 Optional 类中的两个构造函数都是 private 权限,所以不能使用 new 关键字初始化对象

如何实例化对象:

  • Optional.of(obj);
  • Optional.ofNullable(obj)
  • Optional.empty()
//JDK8 中 Optional#ofNullable 中的源码
public static <T> Optional<T> ofNullable(T value) 
{    
    return value == null ? empty() : of(value);
}

对,就是看到的这样,ofNullable 会分别调用 of 和 empty 方法。

当不确定对象是否为空,就使用 ofNullable ,因为使用 of 会报空指针。

其实看到这里,在日常的开发中,想要实例化 Optional 就可以直接 ofNullable。忘掉其他两个方法都可以

isPresent & get

我们会用 isPresent 是否为 null,用 get 方法取获取里面的值

List<Map<String,Object>> list=new ArrayList<>();
Map<String,Object> map=new HashMap<>();
map.put("a","12");
map.put("b","34");
list.add(map);

Optional<List<Map<String,Object>>> optionalMapList=Optional.ofNullable(list);
if(optionalMapList.isPresent()){
     System.out.println(optionalMapList.get());
}

//[{a=12, b=34}]
//是判断 null 的,而不是判断空对象的|注意 null 和空对象的区别
public boolean isPresent() {
     return value != null;
}

这里的判空好像和原来直接判空并没有什么太大的区别,还要去加入新的类,反而有点麻烦了。

下面接着介绍(会涉及Lambda和Stream API):

ifPresent

注意上面是 isPresent(判空),这从命名上也很好理解,要不然怎么说我们在开发的时候,方法、类等命名也很重要呢。

List<Map<String,Object>> list=new ArrayList<>();
        Map<String,Object> map=new HashMap<>();
        map.put("a","12");
        map.put("b","34");
        list.add(map);

        Optional<List<Map<String,Object>>> optionalMapList=Optional.ofNullable(list);
        optionalMapList.ifPresent(System.out::println);

这里涉及到了 Java8 新的特性 Lambda 的方法引用

使用 ifPresent 我们不用显式的进行检查,如果 list 为空,那么就不会输出。

filter

当我们需要满足一定条件的才执行操作的时候:

List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map = new HashMap<>();
        map.put("a", "12");
        map.put("b", "34");
        list.add(map);
        System.out.println(list);

        Optional<List<Map<String, Object>>> optionalMapList = Optional.ofNullable(list);
        optionalMapList.filter(a->"12".equals(a.get(1).get("a"))).
                ifPresent(a-> System.out.println("AAAA"));

filter 方法将会判断对象是否符合条件。如果不符合条件,将会返回一个空的 Optional

orElse & orElseThrow

当一个对象为 null 时,业务上通常可以设置一个默认值或者抛出一个内部异常,记录失败原因,快速失败.从而使流程继续下去。

//设置一个默认值
String a = list.get(0) != null ? (String) list.get(0).get("a") : "unknown";
//抛出一个内部异常
if( list.get(0).get("a")==null){
     throw new RuntimeException();
}
list=null;
        Optional<List<Map<String, Object>>> optionalMapList = Optional.ofNullable(list);

		//设置默认值
        List a1=optionalMapList.orElse(new ArrayList(Collections.singleton("unknown")));
        System.out.println(a1.toString());
        //抛出一个内部异常
		String a2= (String)optionalMapList.orElseThrow(RuntimeException::new).get(0).get("a");
        System.out.println(a2);

map & flatMap

熟悉 Java8 Stream 同学的应该了解,Stream#map 方法可以将当前对象转化为另外一个对象, Optional#map 方法也与之类似。

map 方法可以将原先 Optional<List<Map<String, Object>>> 转变成 Optional,此时 Optional 内部对象变成 Object类型。如果转化之前 Optional 对象为空,则什么也不会发生。

List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map = new HashMap<>();
        map.put("a", "12");
        map.put("b", "34");
        list.add(map);
Optional<List<Map<String, Object>>> optionalMapList = Optional.ofNullable(list);


Optional<Object> s= optionalMapList.map(m->m.get(0).get("a"));
System.out.println(s.toString());
//Optional[12]

Optional<Object> s = optionalMapList.flatMap(e->Optional.ofNullable(e.get(0).get("a")));
System.out.println(s.toString());
//Optional[12]

上面两种:map、flatMap 的输出结果一样。

flatMap 的参数是 Optional类型,需要自己再次封装一下,map 方法,则直接使用即可。

重构

以后判空条件过多,那么就尝试着使用 Optional 类去重写它,还能顺带学习一波 Lambda 就不亏