我有一个朋友写出了17种触发NPE的代码!避免这些坑 - 阿里技术

阿里妹导读

我有一个朋友,写代码的时候常常遭到NPE背刺,痛定思痛,总结了NPE出没的17个场景,哪一个你还没有遇到过?

虽然无法统计出Java程序员写的最多的异常是哪一个,NullPointerException(NPE)它一定会榜上有名。无论是初次涉足编程世界的新手,还是在代码海洋中久经风浪的老鸟,几乎都写过NPE异常。要防范它,不在高超的编码技巧,在细。

我有一个朋友,写代码的时候常常遭到NPE背刺,痛定思痛,总结了NPE出没的17个场景,哪一个你还没有遇到过?

1.访问空对象的实例变量或者调用空对象的实例方法

从其它方法(如远程hsf方法或者从DB)返回的结果,不做控制判断,直接访问变量或者方法,可能会NPE。

反例


public class Test { 
    public String a; 

    public String getA() { 
        return a; 
    } 

    public static void main(String[] args) { 
        Test test = getFromSomeMethod(); 
        //访问属性 NPE  
        System.out.println(test.a); 
        //访问方法 NPE 
        System.out.println(test.getA()); 
    } 

    private static Test getFromSomeMethod() { 
        return null; 
    } 
} 

2.访问或修改空数组的元素

反例


public class Test { 
    public static void main(String[] args) { 
        int[] numbers = getFromSomeMethod(); 
        //访问 NPE 
        int number = numbers[0]; 
        //修改 NPE 
        numbers[0] =1; 
    } 
    private static int[] getFromSomeMethod() { 
        return null; 
    } 
} 

3.未初始化的对象数组中的元素默认是null

反例


public class Test { 
    private String a; 

    public String getA() { 
        return a; 
    } 

    public static void main(String[] args) { 
        Test[] testArr = new Test[2]; 
        //NPE 
        testArr[0].getA(); 
    } 

} 

4.throw一个null 出去

在Java中,通常不会故意使用 throw null 这种表达,因为它实际上没有任何有用的用途。根据Java语言规范,throw 关键字后面应该跟随一个可抛出的对象(Throwable 类或其子类的实例),而 null 并不是一个可抛出的对象。如果你执行 throw null ,将会得到一个NPE

猜想可能出现的场景:

  • 代码错误:throw null 可能是代码编写错误或者不完整的异常处理。例如,可能打算抛出一个实际的异常对象,但误写成了 null
  • 测试代码:在单元测试中,有时可能会故意使用throw null
    , 来确保他们的异常处理代码能够妥善处理意外情况。(不推荐)

反例


public class Test { 
    public static void main(String[] args) { 
        getFromSomeMethod(); 
    } 

    private static int getFromSomeMethod() { 
        try { 
            int a = 1/0; 
        }catch (Exception e){ 
            //NPE 
            throw null; 
        } 
        return 0; 
    } 
} 

5.在null 引用上进行synchronized同步

反例


public class Test { 
    public static void main(String[] args) { 
        Test test = getFromSomeMethod(); 
        //NPE 
        synchronized (test){ 

        } 
    } 
    private static Test getFromSomeMethod() { 
        return null; 
    } 
} 

6.在自动拆箱过程中遇到null

自动装箱不会遇到Null的问题,因为这个过程是把基本类型的值转换成包装类对象,基本类型的值是没有Null
的。

反例


public class Test { 
    public static void main(String[] args) { 
        Integer integer = getFromSomeMethod(); 
        //NPE 
        if (integer > 1) { 

        } 
    } 
    private static Integer getFromSomeMethod() { 
        return null; 
    } 

} 

定义方法返回一个int 作为出参,但实际return 一个null,也会NPE。

反例


 public static void main(String[] args) { 
      getFromSomeMethod(); 

  } 
  private static int getFromSomeMethod() { 
      Integer a = null; 
      // NPE 
      return a; 
  } 

7.从集合/Map中获取null元素并直接使用

从集合/map中获取元素并使用时,建议对Null进行检查,以避免潜在的NPE,特别是在那些隐式触发自动拆箱的场景中。

反例


Map<string string=""> map = new HashMap(); 
String value = map.get("key"); 
//NPE 
int length = value.length(); 

</string>

8.方法链调用中上一步骤返回null

反例


Test test = new Test().getA().getB().getC(); 

9.枚举的valueOf方法使用null

反例


Enum enum = Enum.valueOf(null); 

10.集合操作不支持null元素

HashSet 、LinkedHashSet 都只允许添加一个null。后续无论添加多少null元素,都会被忽视。

TreeSet 不允许添加null值,排序集合依赖元素直接的比较操作,而null元素不能与其它对象进行比较,会抛出NPE
;

反例


Set<string> set = new TreeSet(); 
set.add(null); 

</string>

11.多线程环境中无适当同步可能导致不一致状态

示例


public class Test implements Runnable { 
    private static String sharedResource; 

    public void run() { 
        sharedResource = "sharedResource"; 
    } 

    public static void main(String[] args) throws InterruptedException { 
        Thread thread = new Thread(new Test()); 
        thread.start(); 
        // 在多线程环境中,如果没有适当的同步,这里可能导致NPE 
        //thread.join(); 
        System.out.println(sharedResource.length()); 
    } 
} 

12.依赖注入:注入的对象为null

required属性为false,启动过程中如果没有找到合适的bean,service 会被设置为null。在调用service的任何方法之前都需要判断service是否为null。

示例


@Autowired(required = false) 
private SomeService service; 

13.Lambda表达式或方法引用中目标引用为null

反例


Test test = null; 
Consumer<test> todoSomething = test::someMethod; 

</test>

14.Stream API处理时遇到null元素

反例


list = Arrays.asList("a", null); 
> lengths = list.stream().map(String::length).collect(Collectors.toList()); 

正例


List<string> list = Arrays.asList("a", null); 
List<integer> lengths = list.stream() 
  .filter(Objects::nonNull) // 过滤null 
  .map(String::length).collect(Collectors.toList()); 

</integer></string>

15.使用增强for循环遍历集合时,没有判空

反例


List<string> list = null; 
for (String item : list) { } 

</string>

16.在Junit中使用Mockito时,

错误地使用any()匹配基本数据类型参数

在JUnit4中,使用Mockito框架时,any() 是一个参数匹配器,当与基本数据类型一起使用时,需要使用相应的类型特定的匹配器,例如使用anyInt() 而不是any()。因为any()实际上返回的是null,而null不能自动转换为基本数据类型。

反例


when(service.doSomething(any())).thenReturn("Success"); 
// service.doSomething(int a) 

正例


when(service.doSomething(anyInt())).thenReturn("Success"); 
// service.doSomething(int a) 

17.Optional类的正确使用

Optional类在 Java 8 中被引入,其设计初衷是为了提供一种更优雅的方式来处理可能为Null的值,从而减少空指针异常NPE的发生。

但Optional类设计为减少NPE的可能性,却并不是万能的,比如开发者在使用Optional,不检查是否存在,直接调用Optional.get(),那么会得到一个NoSuchElementException。也仍然存在即使使用了Optional,也可能出现NPE的情况
。你看:

反例


Optional<string> optional = Optional.of(null); 

</string>

正例


Optional<string> optional = Optional.ofNullable(null); 

</string>



8