私は最近「Java で考える」を読んでいて、次の一節を目にしました:
最初の文は誰でも簡単に理解できると思いますが、2 番目の文は非常に混乱します。なぜ null オブジェクトを出力しても例外がスローされないのでしょうか。この疑問を念頭に置いて、私は理解の旅を始めました。以下では、この問題を解決するための私のアイデアを詳しく説明し、JDK ソース コードを詳しく調べて問題の答えを見つけます。 問題を解決するプロセス この問題には実際にはいくつかの状況があることがわかり、さまざまな状況を分類して議論し、最終的に答えが得られるかどうかを確認します。 まず、この問題を 3 つの小さな問題に分割し、1 つずつ解決します。 最初の質問 null String オブジェクトを直接出力すると、どのような結果が得られますか?「すべてがオブジェクトである」の章で説明したように、クラス内のフィールドであるプリミティブは自動的にゼロに初期化されますが、オブジェクト参照は初期化されます。便利なことに、例外をスローせずに null 参照を出力することもできます。主な考え方は次のとおりです。ネイティブ型は次のようになります。自動的に初期化される値は 0 ですが、オブジェクト参照は null に初期化され、オブジェクトのメソッドを呼び出そうとすると、null ポインタ例外がスローされます。通常、例外をスローせずに null オブジェクトを出力できます。
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 对象分开进行处理。
String 对象:直接判断是否为 null,如果为 null 给 null 对象赋值为"null"
。
非 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++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; }
再回到我们的问题,现在我们知道秘密在StringBuilder.append
函数的源码中。
现在我们恍然大悟,append
函数如果判断对象为 null,就会调用appendNull
,填充"null"
ソース コードを見たとき、これは単なる判断文であることがわかりました。おそらく、あなたはこの内容に少しがっかりしたでしょう。 JDKの簡単な実装。心配しないでください。最初の質問は単なる前菜です。ごちそうはまだこれからです。
Integer などの null 以外の文字列オブジェクトを出力します:
rrreee実行結果は予想どおりです:
rrreeeprint
のソース コードを見てみましょう: valueOf
に隠されているようです。 🎜rrreee🎜これを見て、例外をスローせずに null オブジェクトを出力する秘密をついに発見しました。 print
メソッドは、String オブジェクトと非 String オブジェクトを個別に処理します。 🎜 として null オブジェクトに代入します。 「null」
。 🎜String.valueOf
メソッドを呼び出すことにより、それが null オブジェクトの場合、"null" を返します。
、それ以外の場合は、オブジェクトの toString
メソッドを呼び出します。 🎜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 サイトの他の関連記事を参照してください。