ホームページ  >  記事  >  Java  >  Java String による null オブジェクトのフォールトトレラントな処理の概要

Java String による null オブジェクトのフォールトトレラントな処理の概要

黄舟
黄舟オリジナル
2017-03-31 11:07:061542ブラウズ

前書き

私は最近「Java で考える」を読んでいて、次の一節を目にしました:

「すべてがオブジェクトである」の章で説明したように、クラス内のフィールドであるプリミティブは自動的にゼロに初期化されますが、オブジェクト参照は初期化されます。便利なことに、例外をスローせずに null 参照を出力することもできます。主な考え方は次のとおりです。ネイティブ型は次のようになります。自動的に初期化される値は 0 ですが、オブジェクト参照は null に初期化され、オブジェクトのメソッドを呼び出そうとすると、null ポインタ例外がスローされます。通常、例外をスローせずに null オブジェクトを出力できます。

最初の文は誰でも簡単に理解できると思いますが、2 番目の文は非常に混乱します。なぜ null オブジェクトを出力しても例外がスローされないのでしょうか。この疑問を念頭に置いて、私は理解の旅を始めました。以下では、この問題を解決するための私のアイデアを詳しく説明し、JDK ソース コードを詳しく調べて問題の答えを見つけます。

問題を解決するプロセス

この問題には実際にはいくつかの状況があることがわかり、さまざまな状況を分類して議論し、最終的に答えが得られるかどうかを確認します。

まず、この問題を 3 つの小さな問題に分割し、1 つずつ解決します。

最初の質問

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);
}
null。显然问题的线索在于print函数的源码中。我们找到print的源码:

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

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

第二个问题

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

null

运行的结果不出意料:

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

我们再去看看print的源码:

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

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

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

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

  1. String 对象:直接判断是否为 null,如果为 null 给 null 对象赋值为"null"

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

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

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

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

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

null!

结果可能你也猜到了:

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

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

String s = "a" + "b";
//等价于
StringBuilder sb = new StringBuilder();
sb.append("a");
sb.append("b");
String s = sb.toString();

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

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

//针对 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++] = &#39;n&#39;;
    value[c++] = &#39;u&#39;;
    value[c++] = &#39;l&#39;;
    value[c++] = &#39;l&#39;;
    count = c;
    return this;
}

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

rrreee

现在我们恍然大悟,append函数如果判断对象为 null,就会调用appendNull,填充"null" ソース コードを見たとき、これは単なる判断文であることがわかりました。おそらく、あなたはこの内容に少しがっかりしたでしょう。 JDKの簡単な実装。心配しないでください。最初の質問は単なる前菜です。ごちそうはまだこれからです。

2 番目の質問

Integer などの null 以外の文字列オブジェクトを出力します:

rrreee

実行結果は予想どおりです:

rrreee
print のソース コードを見てみましょう:
rrreee🎜 それは少し違います。秘密は valueOf に隠されているようです。 🎜rrreee🎜これを見て、例外をスローせずに null オブジェクトを出力する秘密をついに発見しました。 print メソッドは、String オブジェクトと非 String オブジェクトを個別に処理します。 🎜
  1. 🎜文字列オブジェクト: null であるかどうかを直接判断し、null の場合は、その値を として null オブジェクトに代入します。 「null」。 🎜
  2. 🎜非文字列オブジェクト: String.valueOf メソッドを呼び出すことにより、それが null オブジェクトの場合、"null" を返します。 、それ以外の場合は、オブジェクトの toString メソッドを呼び出します。 🎜
🎜 上記の処理により、null オブジェクトを出力してもエラーが発生しないことが保証されます。 🎜🎜この記事はここで終わるはずです。 🎜何?約束の夕食はどこですか?歯の間に入れるだけでは不十分です。 🎜冗談です。以下の 3 番目の質問について考えてみましょう。 🎜🎜3番目の質問(隠れたごちそう)🎜🎜null オブジェクトと文字列を連結した結果はどうなりますか? 🎜rrreee🎜あなたは結果を推測したかもしれません: 🎜rrreee🎜なぜですか?コードの実行を追跡すると、今回は print とは何の関係もないことがわかります。しかし、上記のコードは print 関数を呼び出しています。他に誰がいるでしょうか? + が最も疑わしいですが、+ は関数ではありません。そのソース コードを確認するにはどうすればよいでしょうか。この場合、唯一の説明は、コンパイラによって生成されたバイトコードが見つからないということです。 🎜rrreee🎜上記のバイトコードを読んで混乱していませんか?ここでは話題を変えて、+ 文字列の結合の原理について説明します。 🎜🎜コンパイラは、最初に StringBuilder をインスタンス化し、次に追加された文字列を順番に append し、最後に toString を呼び出します。 文字列オブジェクト。私の言うことが信じられない場合は、上記のバイトコードを見て、StringBuilder が表示されるかどうかを確認してください。詳細な説明については、この記事「Java の詳細: 文字列のスプライシング」を参照してください。 🎜rrreee🎜問題に戻りますが、秘密が StringBuilder.append 関数のソース コードにあることがわかりました。 🎜rrreee🎜 ここで、append 関数がオブジェクトが null であると判断した場合、appendNull を呼び出して "null" を埋めることに突然気づきました。 。 🎜🎜まとめ🎜🎜 上記では、Java における String null オブジェクトのフォールトトレラントな処理につながる 3 つの問題について説明しました。上記の例は、すべての処理状況をカバーしているわけではなく、入門的なものとみなされます。 🎜🎜プログラム内の null オブジェクトを制御下に置く方法は、プログラミング時に常に注意を払う必要があるものです。 🎜🎜🎜

以上がJava String による null オブジェクトのフォールトトレラントな処理の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。