在逛 programcreek 的時候,我發現了一些專注基礎但不容忽視的主題。比如說:Java 的可變參數究竟是怎么一回事?像這類靈魂拷問的主題,非常值得深入地研究一下。
我以前很不重視基礎,覺得不就那么回事嘛,會用就行了。就比如說今天這個主題,管它可變不可變呢,不就是個參數嘛,還能有多大學問——抱着這種態度,我一直橫行江湖近十載(苦笑)。可等到讀者找我提一些基礎的問題時,我幾乎回答不上來,感覺知識是散的,或者是浮於表面的。幸好最近一段時間,我開始幡然醒悟,開始不放過任何一個細節,漸漸地,有點“知識儲備”了。
好了,牛逼吹完,讓我們來步入正題。Java 的可變參數究竟是怎么一回事?
可變參數是 Java 1.5 的時候引入的功能,它允許方法使用任意多個、類型相同(is-a
)的值作為參數。就像下面這樣。
public static void main(String[] args) {
print("沉");
print("沉", "默");
print("沉", "默", "王");
print("沉", "默", "王", "二");
}
public static void print(String... strs) {
for (String s : strs)
System.out.print(s);
System.out.println();
}
靜態方法 print()
就使用了可變參數,所以 print("沉")
可以,print("沉", "默")
也可以,甚至 3 個、 4 個或者更多個字符串都可以作為參數傳遞給 print()
方法。
說到可變參數,我想起來阿里巴巴開發手冊上有這樣一條規約。

意思就是盡量不要使用可變參數,如果要用的話,可變參數必須要在參數列表的最后一位。既然坑位有限,只能在最后,那么可變參數就只能有一個(悠着點,悠着點)。如果可變參數不在最后一位,IDE 就會提示對應的錯誤,如下圖所示。

那可變參數是怎么工作的呢?
原理也很簡單。當使用可變參數的時候,實際上是先創建了一個數組,該數組的大小就是可變參數的個數,然后將參數放入數組當中,再將數組傳遞給被調用的方法。
這就是為什么可以使用數組作為參數來調用帶有可變參數的方法的根本原因。代碼如下所示。
public static void main(String[] args) {
print(new String[]{"沉"});
print(new String[]{"沉", "默"});
print(new String[]{"沉", "默", "王"});
print(new String[]{"沉", "默", "王", "二"});
}
public static void print(String... strs) {
for (String s : strs)
System.out.print(s);
System.out.println();
}
那如果方法的參數是一個數組,然后像使用可變參數那樣去調用方法的時候,能行得通嗎?大家感興趣的話,不妨試一試(行不通,噓)。
那一般什么時候使用可變參數呢?
可變參數,可變參數,顧名思義,當一個方法需要處理任意多個相同類型的對象時,就可以定義可變參數。Java 中有一個很好的例子,就是 String 類的 format()
方法,就像下面這樣。
System.out.println(String.format("年紀是: %d", 18));
System.out.println(String.format("年紀是: %d 名字是: %s", 18, "沉默王二"));
PS:%d
表示將整數格式化為 10 進制整數,%s
表示輸出字符串。
如果不使用可變參數,那需要格式化的參數就必須使用“+”號操作符拼接起來了。麻煩也就惹禍上身了。
在實際的項目代碼中,開源包 slf4j.jar 的日志輸出就經常要用到可變參數(log4j 就沒法使用可變參數,日志中需要記錄多個參數時就痛苦不堪了)。就像下面這樣。
protected Logger logger = LoggerFactory.getLogger(getClass());
logger.debug("名字是{}", mem.getName());
logger.debug("名字是{},年紀是{}", mem.getName(), mem.getAge());
查看源碼就可以發現,debug()
方法使用的可變參數。
public void debug(String format, Object... arguments);
那在使用可變參數的時候有什么注意事項嗎?
有的,有的。我們要避免重載帶有可變參數的方法——這樣很容易讓編譯器陷入自我懷疑中。
public static void main(String[] args) {
print(null);
}
public static void print(String... strs) {
for (String a : strs)
System.out.print(a);
System.out.println();
}
public static void print(Integer... ints) {
for (Integer i : ints)
System.out.print(i);
System.out.println();
}
這時候,編譯器完全不知道該調用哪個 print()
方法,print(String... strs)
還是 print(Integer... ints)
,傻傻分不清。

假如真的需要重載帶有可變參數的方法,就必須在調用方法的時候給出明確的指示,不要讓編譯器去猜。
public static void main(String[] args) {
String [] strs = null;
print(strs);
Integer [] ints = null;
print(ints);
}
public static void print(String... strs) {
}
public static void print(Integer... ints) {
}
上面這段代碼是可以編譯通過的。因為編譯器知道實參是 String 類型還是 Integer 類型,只不過為了運行時不拋出 NullPointerException
,兩個 print()
方法的內部要做好判空的操作。
好了,各位讀者朋友們,以上就是本文的全部內容了。能看到這里的都是最優秀的程序員,升職加薪就是你了👍。如果覺得不過癮,還想看到更多,我再給大家推薦幾篇。
Java 如何獲取數組和字符串的長度?length 還是 length()?
原創不易,如果覺得有點用的話,請不要吝嗇你手中點贊的權力;如果想要第一時間看到二哥更新的文章,請掃描下方的二維碼,關注沉默王二公眾號。我們下篇文章見!
