我在代碼走查時,發現下面的代碼。其中Line133行的StringUtils.isEmpty(levyId+"")
引起了我的注意。levyId是Long,你這樣判斷Long是否爲null,靠譜嗎?
答案是:不靠譜!
當levyId是null時,levyId+""
的值是什麼? 是字符串null喲~~ 顯然,StringUtils.isEmpty("null")
是false。所以,還是老老實實地用 levyId==null
來判斷Long是否爲null吧。
那麼,爲什麼levyId+""
在levyId是null時的值是字符串null?
在Java中,當你對一個對象使用加號 "+" 進行字符串連接時,實際上是先調用了String#valueOf方法將對象轉換爲字符串。
走,帶你去司空見慣的java.lang.String小星球,看看它的靜態成員valueOf方法。可以看到,valueOf有許多重載,除了valueOf(Object)以外,其他valueOf重載都是基於int/long/char/boolean等基本類型參數的。
對於非基本類型,即引用類型對象,就要用valueOf(Object)
了。來看看這個重載方法的實現。此刻,你應該知道levyId+""
當levyId爲null時的值是字符串null的原因了吧!
// in java.lang.String /** * Returns the string representation of the {@code Object} argument. * * @param obj an {@code Object}. * @return if the argument is {@code null}, then a string equal to * {@code "null"}; otherwise, the value of * {@code obj.toString()} is returned. * @see java.lang.Object#toString() */ public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
使用“+”拼接對象與字符串,首先調用了String#value將對象轉換爲字符串。怎麼得出的這個結論?
下面代碼:
TestObjectStringJoin.java源碼 |
compile生成的二進制文件TestObjectStringJoin.class |
package jstudy; public class TestObjectStringJoin { public static void main(String[] args) { Long l = null; System.out.println(l + "abc"); } }
|
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package jstudy; public class TestObjectStringJoin { public TestObjectStringJoin() { } public static void main(String[] args) { Long l = null; System.out.println(l + "abc"); } }
|
使用javap -v來解析生成的TestObjectStringJoin.class文件裏的字節碼,重點看main方法(如下)。注意到其中有定義StringBuilder對象,並調用了StringBuilder#append(Object)方法。
D:\workspace\sboot\jstudy\target\test-classes\jstudy>javap -v TestObjectStringJoin.class Classfile /D:/workspace/sboot/jstudy/target/test-classes/jstudy/TestObjectStringJoin.class Last modified 2023-6-5; size 858 bytes MD5 checksum 02a2129f4d9044c32a956d668b575a20 Compiled from "TestObjectStringJoin.java" public class jstudy.TestObjectStringJoin ... { ... public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: aconst_null 1: astore_1 2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 5: new #3 // class java/lang/StringBuilder 8: dup 9: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 12: aload_1 13: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder; 16: ldc #6 // String abc 18: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return LineNumberTable: line 5: 0 line 6: 2 line 7: 27 LocalVariableTable: Start Length Slot Name Signature 0 28 0 args [Ljava/lang/String; 2 26 1 l Ljava/lang/Long; MethodParameters: Name Flags args } SourceFile: "TestObjectStringJoin.java"
總結一下就是,在使用加號 "+" 進行字符串連接時, Java 編譯器在編譯期間會自動插入代碼。在編譯時,Java 編譯器會將加號 "+" 操作符轉換爲使用 `StringBuilder` 或 `StringBuffer` 類的 `append()` 方法來連接字符串。類似於String#valueOf,StringBuilder#append也有許多重載,不同重載的參數包括int/long/char/boolean等基本類型/String/Object。重點來了,重載的append(Object)會調用另一個重載append(String),這期間會調用String.valueOf(Object)將對象轉換成String。關於String#valueOf(Object)方法,上面已經解釋了,它會在對象爲null時返回null字符串。見下圖所示源碼。
下面是與ChatGPT的對話,它對這個知識點的回答並不能令我滿意。anyway,我爲全世界的程序員出了一把力,訓練了一下超強的AI大腦。