`

[转]Java String 对 null 对象的容错处理

 
阅读更多

转载自 : http://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 对象分开进行处理。

  1. String 对象:直接判断是否为 null,如果为 null 给 null 对象赋值为"null"
  2. 非 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.<init> ()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 对象在我们的控制之中,是我们编程的时候需要时刻注意的事情。

 

 

 

分享到:
评论

相关推荐

    java类型转换(转)[文].pdf

    `Object.toString()`适合获取对象的自定义表示,类型转换适用于已知可转换类型的对象,而`String.valueOf(Object)`则在处理可能为`null`的对象时提供了一定的容错性。同时,`Integer.parseInt()`和`Integer.valueOf...

    JAVA问答题.pdf

    此外,Java 还提供了异常处理机制,通过 `try-catch` 块来捕获和处理异常,从而提高代码的容错能力。 总的来说,Java 语言提供了丰富的特性和工具,如访问修饰符、匿名内部类、内部类、逻辑运算符、集合框架、断言...

    java 生成二维码 ZXing

    Java 生成二维码是一种常见的数据编码需求,ZXing(Zebra Crossing)是一个开源的、多格式的一维/二维条码图像处理库,它支持多种条码和二维码的生成与解码。在Java中利用ZXing库生成二维码,可以方便地将文本、链接...

    java生成带Logo二维码

    在实际应用中,我们还需要考虑错误处理、二维码格式转换、容错级别调整等因素,以满足不同场景的需求。在进行这些操作时,确保遵循最佳实践,比如合理设置容错级别以提高二维码的抗损能力,以及在生成带Logo的二维码...

    Java 二维码生成、解析

    Java 二维码生成与解析是Java开发中常见的功能需求,尤其在移动互联网时代,二维码被广泛应用于数据交换、链接跳转、支付凭证等场景。本文将详细介绍如何使用Google的ZXing库来实现Java环境下的二维码生成与解析。 ...

    Java连接FastDFS上传图片

    Java连接FastDFS上传图片是一项常见的任务,特别是在分布式存储系统中。FastDFS是一个开源的高性能、轻量级的分布式文件系统,适用于互联网行业的大型网站或应用,用于存储和共享大量小文件,如图片、文档等。Java...

    Java访问Hadoop集群源码

    在Java编程环境中,访问Hadoop集群是一项常见的任务,特别是在大数据处理和分析的场景下。Hadoop是一个开源框架,主要用于存储和处理大规模数据集。本文将深入探讨如何利用Java API来与Hadoop集群进行交互,包括读取...

    IBM Java英文面试题(附参考答案).doc

    不可变对象是创建后其状态不能改变的对象,例如Java中的String。 13. **如何编写排序程序?** 可以使用Java内置的`Arrays.sort()`或Collections的`sort()`方法对数组或集合进行排序,也可以自定义排序算法,如...

    Java后台生成二维码工具类

    在学习和使用这个Java后台生成二维码工具类时,可以尝试不同的输入参数,观察生成的二维码效果,并了解不同错误纠正级别对二维码容错能力的影响。通过实践,你将更好地掌握Java生成二维码的技术。

    Java实现二维码QRCode的编码和解码

    在实际应用中,我们还需要考虑错误处理和容错机制,因为二维码在被拍摄或打印后可能会出现部分损坏。通过选择不同的错误校正级别(例如,这里使用了ErrorCorrectionLevel.H),我们可以提高二维码在损坏情况下的...

    rabbitmq HelloWorld java 代码

    以上就是关于RabbitMQ HelloWorld Java代码的详细解析,希望对你理解RabbitMQ的基础使用有所帮助。在实际开发中,你可以根据项目需求调整这些基础示例,例如使用不同的交换机类型、路由键,或者实现更复杂的消费逻辑...

    rabbitmq 工作队列 java 实现

    在IT行业中,消息队列(Message Queue)是一种重要的中间件技术,用于解耦应用程序的不同组件,提高系统的可扩展性和容错性。RabbitMQ作为一款广泛使用的开源消息代理,它支持多种消息协议,包括AMQP(Advanced ...

    java 二维码

    要生成一个基本的二维码,我们需要创建一个`BitMatrix`对象,然后使用`QRCodeWriter`将其编码为二维码格式。以下是一个简单的示例: ```java import com.google.zxing.BarcodeFormat; import ...

    Java实现高可定制的二维码生成+logo(完美呈现)

    这通常需要对生成的二维码图像进行处理,例如使用Java的`java.awt`包中的图像处理类。以下是一个基本的实现: ```java import java.awt.*; import java.awt.image.BufferedImage; // ...其他导入语句... // ...

    川大计算机复试专业课关于鱼群,蚂蚁算法及AI等简介[借鉴].pdf

    - C 和 C++ 通过字符数组(char array)表示字符串,以空字符 `NULL` 结束,有时会使用 `string` 类或 `stringstream` 来处理。 - Java 有内置的 `String` 类和 `StringBuilder` 类,它们是对象,提供了丰富的字符...

    java生成二维码(中间带logo)

    ZXing是一个多格式的、开源的条码图像处理库,支持生成和读取多种条码和二维码。以下是如何使用ZXing库生成二维码的步骤: 1. **添加依赖**:在你的项目中,首先需要引入ZXing库。如果是Maven项目,可以在pom.xml...

    Java二维码简单例子

    在实际开发过程中,确保对这些库的API有深入理解,并结合项目的具体需求进行适当调整,才能生成满足业务需求的高质量二维码。同时,处理好Logo的大小和位置,可以使得二维码更加美观且具有品牌识别度。

    5第五章知识点整理1

    异常,也称为例外,是程序在运行时遇到的一种特殊错误对象,它提供了一种程序容错机制。当遇到异常时,程序可以尝试处理错误并从中恢复,或者将错误信息传递给调用者,甚至终止程序。 Java提供了丰富的异常类,每一...

    Java集成RabbitMq

    在本文中,我们将深入探讨如何将Java应用程序与RabbitMQ集成,以实现高效的消息传递和异步通信。RabbitMQ是一个开源的消息代理和队列服务器,它基于AMQP(Advanced Message Queuing Protocol)协议,允许不同应用...

Global site tag (gtag.js) - Google Analytics