对于 Java 参数传入 null 判断思考

370 阅读6分钟

以前发在 CSDN 上, blog.csdn.net/u014443348/…

掘金氛围感觉更好一点,打算慢慢转过来。

对于 Java 参数传入 null 判断思考


最近重温了《Effective Java》之后,在日常工作中写代码时感觉也会慢一点,多一点思考这样是不是比较合理。

在一个常见的情况下迟疑了下:在定义方法时,经常需要传入非基本类型的参数。


//定义一个简单的类
class Hello{
    public void say(){
        System.out.print("Hello");
    }
}

//类似这种情况,需要传入一个对象进行它的操作
public void test01(Hello hello) {
    hello.say();
}

按理说我们会在这里进行一个判断,判断是否是空,以防我们在后续使用它的方法时出现空指针。就像这样进行一次判断。

	
//类似这样的情况,传一个简单的参数
//对字符进行判断一下,如果无效就不进行下一步操作
public void test01(Hello hello) {
    if(hello == null){
        ……
        return;
    }
    hello.say();
}

一:按照一些编程书中的意见,我们最好不要做这些容错机制,如果是第一次调用该方法时,参数传入错误就直接报错,而不是后面发现没有输出 “Hello” 这个字符的时候再来寻找哪里的问题,这样就减少了调试的成本。

二:但是这样一想,要是这段代码自己测着没什么问题,其他懂代码的同事用了之后肯定也会懂。但如果这段代码在你自己的程序里,其他应用传入错误参数时,让你的程序崩溃了,虽然是别人的问题,但是你说是你慌一点还是别人慌一点 …… 🙃

所以感觉这还是要区分场景的,比如在前一种情况下,就是需要直接把问题暴露出来,而在后一种情况,类似于需要保证程序的健壮性,那么还是需要做容错的处理。


在写一个工具类的时候想到有些 Java 框架,在内部使用时,就进行了非空判断的操作。
比如 RxJava 中有个判断工具。


//如果不为空则直接返回该对象
public static <T> T checkNull(T t){
    if(t == null)
        throw new NullPointerException("空指针啦,亲");
        
    return t;	
}

//这样写也是可以的,只是在没法嵌套在其他函数参数中。
public static <T> void checkNull(T t){
    if(t == null)
        throw new NullPointerException("空指针啦,亲");
}

//======================= 示范 ===================

//定义一个简单的类
class Hello{
    public void say(){
        System.out.print("Hello");
    }
}

//类似这种情况,需要传入一个对象进行它的操作
public void test01(Hello hello) {
    hello.say();
}

//这种是使用带返回的 checkNull ,可以进行嵌套使用
public void checkNull01(){
    Hello hello = new Hello();
    test01(checkNull(hello));
}

//这种是不带返回的 checkNull ,不能进行嵌套使用,就是多了一行 …… 
public void checkNull02(){
    Hello hello = new Hello();
    checkNull(hello);
    test01(hello);
}

其实这种的好处就是在调试期间,如果出错的话,肯定会第一时间找到出错位置,就是类似上面说的第一种情况。

既然要做这种判断非空的工具,那就做一个通用的方法,再做几个简单封装的方法,是常规思路嘛。比如这里我们可以加入需要的 Exception 类型,出错的 String 提示,大概就要这样调用效果。

	//定义一个简单的类
class Hello{
    public void say(){
        System.out.print("Hello");
    }
}

//测试
public void test01() {
    Hello hello = new Hello();
    hello = checkNull(hello,NullPointerException.class,"空指针啦,亲");
}

第一个是需要检测的对象,第二个是异常的类型,第三个就是提示语。
虽然我们这里是只检测是否为空,按理说就应该抛空指针,你还要抛什么其他的花样 ?好奇的你肯定会问。

虽然直观上是空指针,但是如果你在一个方法中对成员变量进行判断,可能这个对象已经完成使命,执行了一些释放的操作,成员变量已经被置空,那么此时如果抛出 IllegalStateException 加上说明,比如 “ xx 已经被释放了,请检查状态 ” 是不是更合适一点 ?所以还是灵活一点好。(毕竟泛型帅嘛,啊哈哈哈)


//通用的接口
public static <T> T checkNull(T t,Class<? extends Exception> eClazz,String msg){
    if(t == null){
        try {
            Constructor<? extends Exception> eCons = eClazz.getDeclaredConstructor(String.class);
            eCons.setAccessible(true);
            throw eCons.newInstance(msg);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return t;
}

第一个参数是对于对象的判断,第二个参数是采用泛型限定了 Exception 类及其子类,第三个参数针对错误的提示。

因为前面的一些限定,所以后续的四个 Exception 中没有再次抛出(比如大部分 Exception 子类,都有单个 String 的构造函数;虽然都是 public,此处也加上了访问权限设置 ,所以忽略了这些情况)

编译器没有提示错误,仿佛没有什么不对,于是编译跑起来试试,毕竟看着对和跑得起来是两回事 …… 果然,编译的途中出错了(男人的直觉 😂)。

错误: 未报告的异常错误CAP#1; 必须对其进行捕获或声明以便抛出
其中, CAP#1是新类型变量:
CAP#1从? extends Exception的捕获扩展Exception

说实话这个真的有点摸不着的头脑,感觉逻辑上也没错啊,这究竟是怎么肥事 …… 🤔

直接搜这个错误,还没什么参考,于是把几个关键字都试了一下,看了别人的意见有点启发。

Exception 分为 编译期的错误 与 运行时的错误 ,像 NullPointerException 这种就属于运行时的错误,如果是 Exception 、 IOException 这种一定是需要自己处理了之后才能通过编译的。

处理的方式就是两种,正如报错的提示:必须对其进行捕捉或者声明以便抛出。


//声明:当其他方法调用此方法时需要它处理异常
public void say() throws Exception {
    ……
}

//捕捉:这个方法内部已经处理了,其他方法调用时不需要处理
public void say(){
    try{
        ……
    }catch(Exception e){
        ……
    }
}

现在我们来做个试验,测试下是不是正如我们猜想的那种:这里需要抛出一个 RuntimeException ,而不是 Exception。


public void test01(){
    throw new Expcetion("试一试,好像不行");
}

提示: Unhandled Exception: java.lang.Exception

如果我们按照两种处理异常的方式处理一下,那么就没有问题了,也能顺利通过编译。

当然,这不是我们要的结果,此处我们想要的就是在运行时,遇到问题直接抛出(就是这么刚)。

那我们把泛型限定条件改一下: Class<? extends Exception> 改为 Class<? extends RuntimeException>

然后再进行编译就没问题了,当然 Constructor 对应的泛型限定符也需要对应的修改。


分享下平时思考的笔记,如有不对之处,欢迎指出讨论。