String拼接出现null?你看到的分析可是错的

简介: String拼接出现null?你看到的分析可是错的

前言

String类型真是个神奇的存在,动不动就会出现一些迷惑人的错误。今天看到一篇文中提到当String的值为null时,进行字符串相加拼接,会出现把null当做字符串拼接的现象。


比如下面这段代码:


String s = null;

s = s + "hello";

System.out.println(s + " world");

1

2

3

你预期的结果可能是“hello world”,但实际的结果是“nullhello world”,神奇吧。


其实这倒没什么,实践一下就可以看到结果。但当你好奇心作祟,在网上搜为什么时,你看到的答案可能是错的。


我在搜索时,看到访问量上万的文章给出的解释竟然错误的。为了排除一些误导,特意为大家分析一下原因。


错误的原因分析

如果对上述问题进行搜索,你可能看到的答案是:


s + " world" 等价于 s = String.valueOf(s)+"word";

1

然后附带valueOf方法:


public static String valueOf(Object obj) {

 return (obj == null) ? "null" : obj.toString();

}

1

2

3

你信了吗?如果信了可能真的就错了。下面我们就来分析分析为什么错了。


Java编译器的优化

我们知道,当我们写下面的代码时Java编译器会为我们做一些优化:


String a = "Hello ";

String b = "World";

System.out.println(a + b);

1

2

3

如何优化的?上面这段代码经过编译器优化之后,等价于:


StringBuilder sb = new StringBuilder();

sb.append("Hello ");

sb.append("World");

String result = sb.toString();

System.out.println(result);

1

2

3

4

5

也就是说,加号操作会被优化基于StringBuilder的操作,而并不是上面提到的String.valueOf操作。


那么,上面为null的情况也就等价于下面的操作了:


StringBuilder sb = new StringBuilder(null);

sb.append("hello");

sb.append(" world");

String result = sb.toString();

System.out.println(result);

1

2

3

4

5

此时,我们再看一下StringBuilder(null)这个构造方法的底层实现,最终调到它的父类AbstractStringBuilder中的append方法:


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;
}

对应的appendNull方法实现为:

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;
}

在appendNull方法中就是将null当做字符串“null”来处理了。这也就是为什么会在拼接中出现null的原因。

字节码追踪

针对上述示例,如果你想看编译器是如何处理的,可以通过javap -c 命令来查看对应字节码:image.png通过字节码可以看出,基本上与上面的分析的一致。所以说,尽信书不如无书。


拓展问题

解决了上述问题,再来看看,如果我们单纯的就打印null是怎么输出的?


String s = null;

System.out.println(s);

1

2

执行上述程序,控制台打印null,这个null是哪儿来的呢?直接看println的底层实现:


public void print(String s) {

   if (s == null) {

       s = "null";

   }

   write(s);

}

1

2

3

4

5

6

最终调用到了print方法,如果为null,则打印null字符串。


支持,还没有出现最初的valueOf方法,那么valueOf方法在什么场景下会用到呢?在对象为Object类型时:


Object s = null;

String s1 = String.valueOf(s);

System.out.println(s1);

1

2

3

也就是说在明确调用valueOf方法时,此时s1的值直接是null字符串。


再拓展一下,针对一些基础类型的包装类,比如Integer、Double等:


Integer i = null;

System.out.println(i);

1

2

上述代码的处理又不太一样,println方法实现如下:


public void println(Object x) {

   String s = String.valueOf(x);

   synchronized (this) {

       print(s);

       newLine();

   }

}

1

2

3

4

5

6

7

也就是说先对对应的Object对象调用valueOf,回到上面的示例,如果Object为null,该方法返回null字符串,后续打印机直接为null。


小结

字符串拼接是很常见的问题,一不小心会出现将null给拼接上的情况。而这状况的出现又牵扯到Java编译器的优化,是不是很有意思?而且正如最开始所述,当我们在网络上搜索资料时也要辨证的去看待答案的准确性。


面试系列

《面试题:聊聊TCP的粘包、拆包以及解决方案》

《面试题:重写equals方法为什么通常会重写hashcode方法?》

《面试官:如何找出字符串中无重复最长子串?》

《还不懂Java的泛型?只用这一篇文章,保证你面试对答如流》

《面试题:将字符串反转的8种方法,你能想到几种?》



目录
相关文章
|
存储 自然语言处理 安全
C++ STL标准库 《string原理与实战分析》
C++ STL标准库 《string原理与实战分析》
263 0
|
缓存 Java
Java中循环创建String对象的内存管理分析
Java中循环创建String对象的内存管理分析
139 2
|
C++ 容器
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
C++字符串string容器(构造、赋值、拼接、查找、替换、比较、存取、插入、删除、子串)
242 1
|
安全 Java API
JavaSE——常用API进阶一(3/3)-StringBuilder(构造器、拼接内容、反转操作、其他操作),StringBuffer,StringJoiner
JavaSE——常用API进阶一(3/3)-StringBuilder(构造器、拼接内容、反转操作、其他操作),StringBuffer,StringJoiner
111 1
Dart中的String类型定义与拼接
Dart中的String类型定义与拼接
185 0
|
安全 Java 数据安全/隐私保护
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(二)
112 0
|
JSON 安全 Java
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(一)
Java基础4-一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!(一)
177 0
|
存储 C++ 索引
C++中的string容器及字符串拼接操作讲解
C++中的string容器及字符串拼接操作讲解
600 3
|
机器学习/深度学习 人工智能
CF1451C String Equality(数学细心分析,万物皆有规律)
CF1451C String Equality(数学细心分析,万物皆有规律)
124 0