zoukankan      html  css  js  c++  java
  • Java随谈(二)对空指针异常的碎碎念

    本文适合对 Java 空指针痛彻心扉的人阅读,推荐阅读时间25分钟。

    若有一些Java8 函数式编程的基础可以当成对基础知识的巩固。

    一、万恶的null

    今天,我们简单谈谈null的问题。因为null是无的意思,引用null值会让计算机无法处理。在Java语言中对null值的引用也是如此——会导致NPE。

    二、null引起的空指针异常

    NullPointerException(NPE)是在Java里普遍存在的异常,通常通过下面两种方式引起。

    1. 调用null对象的实例方法
    2. 访问或修改null对象的字段

    那么,如何处理空指针异常异常呢?

    三、程序员的应对方式

    (1)最初,一些程序员通过自己的编程经验,在某些出现过NPE的地方进行参数校验——null值判断(方式2),使用方式一 常量.equals(入参) 省略掉null值判断,或者给入参添加默认值。

    //方式1
    public String fooOne(String bar) {
        if ("dev".equals(bar) {
            //正确处理
        }
        else {
            //错误处理
        }
    }
    //方式2 
    public String fooTwo(String bar) {
        if (null != bar && bar.equals("dev")) {
            //正确处理
        }
        else {
            //错误处理
        }
    }
    
    //方式3 添加默认值
    public String fooThree(String bar) {
        if (null == bar) {
            bar = "default value"
        }
        //正确处理
    }

    (2)经过越来越多的代码的编写,有经验的程序员逐步开发了工具包(util),包括很多项目都通用的代码,其中也有null判断的方法。这些工具包中最有名的莫过于 Apache 的commons包和 Google Guava 包

    Apache的公共包isEmpty()方法

    org.apache.commons.lang.StringUtils类中

    public static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

     代码示例

    if (StringUtils.isEmpty(foo)) {
        //错误处理
    }
    //正确处理

    或者采用 Google 的 Guava 的快速失败(fast-fail)机制

    public static <T> T requireNonNull(T obj) {
        //判断参数为null 则抛出异常信息
        if (obj == null)
            throw new NullPointerException();
        //参数不为null,则返回参数本身
        return obj;
    }

    四、Java注解

    在Java引入了注解机制之后,程序员可以通过语法层面控制入参的空指针异常。

    从语法方面控制可以让用户编写代码时,可以将注意力尽可能放于自己的业务代码中,而不是语言的语法。

    用于对象的校验常用的是javax.validation包,包含了校验空值,null值,长度等等。

    javax.validation包简介

    @Data
    public class Bar {
        @NotNull
        private String name;
    }
    
    
    //另一个类的某个方法
    public foo(@Valid Bar bar) {
        //直接处理,无需写校验name属性的代码

    当执行foo(bar) 方法时,会先校验bar.name 是否为空, 若name为空,则会抛出org.springframework.web.bind.MethodArgumentNotValidException异常(例子是spring项目,不同项目可能导致异常不一致),并在日志中会有以人类可读的日志提示。

    五、Java Objects类

    而Java7引入Java.util.Objects类,相当于大一统了第三方库的一些常规处理(可以类比为游戏的mod由于过于优秀被游戏转“正”了)

    比如快速失败机制

    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
    public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
        if (obj == null)
            throw new NullPointerException(messageSupplier.get());
        return obj;
    }

    校验null

    public static boolean isNull(Object obj) {
        return obj == null;
    }
    public static boolean nonNull(Object obj) {
        return obj != null;
    }

    无需校验null的equal

    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

     有了这个类,就无需引用各种第三方的校验包了。

    六、Java Optional类

    这是在Java8加入的包装类,可以有效地处理null(通过Optional.empty包装类来替代null,避免空指针异常)。

    需要注意的是,包装类不可避免的生成了很多包装对象,在一些性能要求比较高的情况下,可以使用OptionalInt、OptionalDouble等来进行替代。

    首先先贴一下Optional的代码

    package java.util;
    
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Predicate;
    import java.util.function.Supplier;
    
    public final class Optional<T> {
    
        private static final Optional<?> EMPTY = new Optional<>();
    
        private final T value;
    
        private Optional() {
            this.value = null;
        }
    
        public static<T> Optional<T> empty() {
            @SuppressWarnings("unchecked")
            Optional<T> t = (Optional<T>) EMPTY;
            return t;
        }
    
        private Optional(T value) {
            this.value = Objects.requireNonNull(value);
        }
    
        public static <T> Optional<T> of(T value) {
            return new Optional<>(value);
        }
    
        public static <T> Optional<T> ofNullable(T value) {
            return value == null ? empty() : of(value);
        }
    
        public T get() {
            if (value == null) {
                throw new NoSuchElementException("No value present");
            }
            return value;
        }
    
        public boolean isPresent() {
            return value != null;
        }
    
        public void ifPresent(Consumer<? super T> consumer) {
            if (value != null)
                consumer.accept(value);
        }
    
        public Optional<T> filter(Predicate<? super T> predicate) {
            Objects.requireNonNull(predicate);
            if (!isPresent())
                return this;
            else
                return predicate.test(value) ? this : empty();
        }
    
        public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
            Objects.requireNonNull(mapper);
            if (!isPresent())
                return empty();
            else {
                return Optional.ofNullable(mapper.apply(value));
            }
        }
    
        public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
            Objects.requireNonNull(mapper);
            if (!isPresent())
                return empty();
            else {
                return Objects.requireNonNull(mapper.apply(value));
            }
        }
    
        public T orElse(T other) {
            return value != null ? value : other;
        }
    
        public T orElseGet(Supplier<? extends T> other) {
            return value != null ? value : other.get();
        }
    
        public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
            if (value != null) {
                return value;
            } else {
                throw exceptionSupplier.get();
            }
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
    
            if (!(obj instanceof Optional)) {
                return false;
            }
    
            Optional<?> other = (Optional<?>) obj;
            return Objects.equals(value, other.value);
        }
    
        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }
    
        @Override
        public String toString() {
            return value != null
                ? String.format("Optional[%s]", value)
                : "Optional.empty";
        }
    }

    在 Optional 类中可以看到很多方法,初学者可能会对其中的繁多的代码有些发憷。我们把这个类分为创建,操作,返回和其他四个模块来简单介绍

    (一)创建Optional

    • empty(): 创建一个空的Optional
    • of(value): 将value包装进Optional
    • ofNullable(value): 将value包装进Optional,若为空时自动生成Optional.empty

    (二)操作Optional

    • filter(Predicate): 对Optional中的内容应用于Predicate并返回,若不满足则返回Optional.empty
    • map(Function): 若Optional不为空,应用Function与Optional中的内容,并返回对象,否则,返回Optional.empty
    • flatMap(Function): 若Optional不为空,应用Function与Optional中的内容,否则,返回Optional.empty

    (三)返回

    • orElse(value):如果值存在则直接返回,否则返回 value。
    • orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数返回一个可替代对象。
    • orElseThrow(Supplier):如果值存在直接返回,否则使用 Supplier 函数返回一个异常。

    (四)其他

    • ifPresent(Consumer):当值存在时调用 Consumer。

    注:(Predicate、Function、Supplier和Consumer是函数式接口,和Java8的流式可以一起使用,在今后的Java随谈 stream中会仔细描述)

    //Function: 传入一个T类型返回一个R类型
    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    }
    
    //Consumer: 传入一个T,不返回
    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);
    }
    
    //Predicate: 传入一个T类型,返回布尔值
    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t);
    }
    
    //Supplier: 执行,返回T类型
    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }

    下面是代码示例
    可以看到,使用了Optional类并配合stream,就可以灵活地获取参数的值。 

    public class Test {
        public static void main(String[] args) throws NullPointerException {
            //假设param为函数的入参
            String param = null;
    
            //直接报 java.lang.NullPointerException
    //        String valueOne = Optional.of(param).get();
    //        System.out.println(valueOne);
    
            //若 param 为 null, 将 default value 作为 valueTwo 的值
            String valueTwo = Optional.ofNullable(param).orElse("default value");
            System.out.println(valueTwo);
    
            //若 param 为 null, 则执行方法并将返回值作为 valueThree 的值
            String valueThree = Optional.ofNullable(param).orElseGet(() -> {
                System.out.println("执行方法获取默认值");
                return "default value";
            });
            System.out.println(valueThree);
    
            //若 param 为 null,则抛出异常
            String valueFour = Optional.ofNullable(param).orElseThrow(() -> new RuntimeException("抛出异常"));
            System.out.println(valueFour);
    
        }
    }
    
    //以下是输出
    ConSole:
    default value 执行方法获取默认值 default value Exception in thread "main" java.lang.RuntimeException: 抛出异常 at com.example.demo.util.annotation.Test.lambda$main$1(Test.java:26) at java.util.Optional.orElseThrow(Optional.java:290) at com.example.util.Test.main(Test.java:26) Process finished with exit code 1

    最佳实践

    1.首先需要判断null首先使用官方库(官方库是每一个Java程序员都必须了解的,降低了沟通、阅读代码的门槛,而且具有持续更新,持续优化的好处)

    2.对于spring web项目的 http请求最好先使用注解进行最初的参数校验。

    3.在和stream的操作中,若可能涉及null值处理,最好使用Optional.ofNullable(),保证代码风格一致。

  • 相关阅读:
    [GDOI2018]滑稽子图
    单位根反演学习笔记
    ODOO/OPENERP的网页模块QWEB简述
    odoo中的QWeb模板引擎
    项目管理)沟通管理
    从vc6升级到vc7的一些问题及解决方法
    vc++ 2005 发布程序
    颜色取反
    几个VC6.0到VC9.0的错误解决方案
    测试计划测试用例
  • 原文地址:https://www.cnblogs.com/kwanwoo/p/13583056.html
Copyright © 2011-2022 走看看