zoukankan      html  css  js  c++  java
  • Java 中 String 对 null 对象的容错处理!

    作者:肖汉松
    blog.xiaohansong.com/2016/03/13/null-in-java-string/

    最近在读《Thinking in Java》,看到这样一段话:

    _Primitives that are fields in a class are automatically initialized to zero, as noted in the Everything Is an Object chapter. _

    _But the object references are initialized to null, and if you try to call methods for any of them, you’ll get an exception-a runtime error. _

    Conveniently, you can still print a null reference without throwing an exception.

    大意是:原生类型会被自动初始化为 0,但是对象引用会被初始化为 null,如果你尝试调用该对象的方法,就会抛出空指针异常。通常,你可以打印一个 null 对象而不会抛出异常。

    第一句相信大家都会容易理解,这是类型初始化的基础知识,但是第二句就让我很疑惑:为什么打印一个 null 对象不会抛出异常?带着这个疑问,我开始了解惑之旅。下面我将详细阐述我解决这个问题的思路,并且深入 JDK 源码找到问题的答案。

    解决问题的过程

    可以发现,其实这个问题有几种情况,所以我们分类讨论各种情况,看最后能不能得到答案。

    首先,我们把这个问题分解为三个小问题,逐一解决。

    第一个问题

    直接打印 null 的 String 对象,会得到什么结果?

    String s = null;  
    System.out.print(s);  
    

    运行的结果是

    null  
    

    果然如书上说的没有抛出异常,而是打印了null。显然问题的线索在于print函数的源码中。我们找到print的源码:

    public void print(String s) {  
        if (s == null) {  
            s = "null";  
        }  
        write(s);  
    }  
    

    看到源码才发现原来就只是加了一句判断而已,简单粗暴,可能你对 JDK 的简单实现有点失望了。放心,第一个问题只是开胃菜而已,大餐还在后面。

    第二个问题

    打印一个 null 的非 String 对象,例如说 Integer:

    Integer i = null;  
    System.out.print(i);  
    

    运行的结果不出意料:

    null  
    

    我们再去看看print的源码:

    public void print(Object obj) {     write(String.valueOf(obj)); }

    有点不一样的了,看来秘密藏在valueOf里面。

    public static String valueOf(Object obj) {  
        return (obj == null) ? "null" : obj.toString();  
    }  
    

    看到这里,我们终于发现了打印 null 对象不会抛出异常的秘密。print方法对 String 对象和非 String 对象分开进行处理。

    String 对象:直接判断是否为 null,如果为 null 给 null 对象赋值为”null”。

    非 String 对象:通过调用String.valueOf方法,如果是 null 对象,就返回”null”,否则调用对象的toString方法。

    通过上面的处理,可以保证打印 null 对象不会出错。

    到这里,本文就应该结束了。
    什么?说好的大餐呢?上面还不够塞牙缝呢。
    开玩笑啦。下面我们来探讨第三个问题。

    第三个问题(隐藏的大餐)

    null 对象与字符串拼接会得到什么结果?

    String s = null;  
    s = s + "!";  
    System.out.print(s);'  
    

    结果可能你也猜到了:

    null!  
    

    为什么呢?跟踪代码运行可以发现,这回跟print没有什么关系。但是上面的代码就调用了print函数,不是它会是谁呢?+的嫌疑最大,但是+又不是函数,我们怎么看到它的源代码?这种情况,唯一的解释就是编译器动了手脚,天网恢恢,疏而不漏,找不到源代码,我们可以去看看编译器生成的字节码。

    L0  
     LINENUMBER 27 L0  
     ACONST_NULL  
     ASTORE 1  
    L1  
     LINENUMBER 28 L1  
     NEW java/lang/StringBuilder  
     DUP  
     INVOKESPECIAL java/lang/StringBuilder. ()V  
     ALOAD 1  
     INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;  
     LDC "!"  
     INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;  
     INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;  
     ASTORE 1  
    L2  
     LINENUMBER 29 L2  
     GETSTATIC java/lang/System.out : Ljava/io/PrintStream;  
     ALOAD 1  
     INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V  
    

    看了上面的字节码是不是一头雾水?这里我们就要扯开话题,来侃侃+字符串拼接的原理了。

    编译器对字符串相加会进行优化,首先实例化一个StringBuilder,然后把相加的字符串按顺序append,最后调用toString返回一个String对象。不信你们看看上面的字节码是不是出现了StringBuilder。详细的解释参考这篇文章 Java细节:字符串的拼接。

    String s = "a" + "b";  
    

    等价于

    StringBuilder sb = new StringBuilder();  
    sb.append("a");  
    sb.append("b");  
    String s = sb.toString();  
    

    再回到我们的问题,现在我们知道秘密在StringBuilder.append函数的源码中。

    //针对 String 对象  
    public AbstractStringBuilder append(String str) {  
        if (str == null)  
            return appendNull();  
        int len = str.length();  
        ensureCapacityInternal(count + len);  
        str.getChars(0, len, value, count);  
        count += len;  
        return this;  
    }  
    
    //针对非 String 对象  
    public AbstractStringBuilder append(Object obj) {  
        return append(String.valueOf(obj));  
    }  
    
    private AbstractStringBuilder appendNull() {  
        int c = count;  
        ensureCapacityInternal(c + 4);  
        final char[] value = this.value;  
        value[c++] = 'n';  
        value[c++] = 'u';  
        value[c++] = 'l';  
        value[c++] = 'l';  
        count = c;  
        return this;  
    }  
    

    现在我们恍然大悟,append函数如果判断对象为 null,就会调用appendNull,填充”null”。

    总结

    上面我们讨论了三个问题,由此引出 Java 中 String 对 null 对象的容错处理。上面的例子没有覆盖所有的处理情况,算是抛砖引玉。

    如何让程序中的 null 对象在我们的控制之中,是我们编程的时候需要时刻注意的事情。

    推荐去我的博客阅读更多:

    1.Java JVM、集合、多线程、新特性系列教程

    2.Spring MVC、Spring Boot、Spring Cloud 系列教程

    3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

    4.Java、后端、架构、阿里巴巴等大厂最新面试题

    觉得不错,别忘了点赞+转发哦!

  • 相关阅读:
    创建Hive/hbase相关联的表异常
    CDH5.2+CM5.2+impala2+Spark1.1 集群搭建基础环境准备
    【JavaWeb】(10)微信公众号开发进阶
    Ambari-stack介绍
    OSGi中的ServletContext
    笔试面试1 用C实现C库函数itoa, atoi
    SGU 114. Telecasting station 三分or找中位数
    face++实现人脸识别
    磁盘接口与磁盘扫描
    CSDN开源夏令营 百度数据可视化实践 ECharts(4)
  • 原文地址:https://www.cnblogs.com/javastack/p/12922647.html
Copyright © 2011-2022 走看看