從原理上搞懂可變參數(shù),就靠它了
為了讓鐵粉們能白票到阿里云的服務(wù)器,老王當了整整兩天的客服,真正體驗到了什么叫做“為人民群眾謀福利”的不易和辛酸。正在他眼睛紅腫打算要休息之際,小二跑過來問他:“Java 的可變參數(shù)究竟是怎么一回事?”老王一下子又清醒了,他愛 Java,他愛傳道解惑,他愛這群尊敬他的讀者。
PS:star 這種事,只能求,不求沒效果,鐵子們,《Java 程序員進階之路》在 GitHub 上已經(jīng)收獲了 514 枚星標,鐵子們趕緊去點點了,沖 600 star!
https://github.com/itwanger/toBeBetterJavaer
可變參數(shù)是 Java 1.5 的時候引入的功能,它允許方法使用任意多個、類型相同(is-a)的值作為參數(shù)。就像下面這樣。
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();
}
靜態(tài)方法 print() 就使用了可變參數(shù),所以 print("沉") 可以,print("沉", "默") 也可以,甚至 3 個、 4 個或者更多個字符串都可以作為參數(shù)傳遞給 print() 方法。
說到可變參數(shù),我想起來阿里巴巴開發(fā)手冊上有這樣一條規(guī)約。

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

可變參數(shù)看起來就像是個語法糖,它背后究竟隱藏了什么呢?老王想要一探究竟,它在追求真理這條路上一直很執(zhí)著。
其實也很簡單。當使用可變參數(shù)的時候,實際上是先創(chuàng)建了一個數(shù)組,該數(shù)組的大小就是可變參數(shù)的個數(shù),然后將參數(shù)放入數(shù)組當中,再將數(shù)組傳遞給被調(diào)用的方法。
這就是為什么可以使用數(shù)組作為參數(shù)來調(diào)用帶有可變參數(shù)的方法的根本原因。代碼如下所示。
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();
}
那如果方法的參數(shù)是一個數(shù)組,然后像使用可變參數(shù)那樣去調(diào)用方法的時候,能行得通嗎?
留個思考題,大家也可以去試一試
那一般什么時候使用可變參數(shù)呢?
可變參數(shù),可變參數(shù),顧名思義,當一個方法需要處理任意多個相同類型的對象時,就可以定義可變參數(shù)。Java 中有一個很好的例子,就是 String 類的 format() 方法,就像下面這樣。
System.out.println(String.format("年紀是:?%d",?18));
System.out.println(String.format("年紀是:?%d?名字是:?%s",?18,?"沉默王二"));
%d 表示將整數(shù)格式化為 10 進制整數(shù),%s 表示輸出字符串。
如果不使用可變參數(shù),那需要格式化的參數(shù)就必須使用“+”號操作符拼接起來了。麻煩也就惹上身了。
在實際的項目代碼中,開源包 slf4j.jar 的日志輸出就經(jīng)常要用到可變參數(shù)(log4j 就沒法使用可變參數(shù),日志中需要記錄多個參數(shù)時就痛苦不堪了)。就像下面這樣。
protected?Logger?logger?=?LoggerFactory.getLogger(getClass());
logger.debug("名字是{}",?mem.getName());
logger.debug("名字是{},年紀是{}",?mem.getName(),?mem.getAge());
查看源碼就可以發(fā)現(xiàn),debug() 方法使用了可變參數(shù)。
public?void?debug(String?format,?Object...?arguments);
那在使用可變參數(shù)的時候有什么注意事項嗎?
有的。我們要避免重載帶有可變參數(shù)的方法——這樣很容易讓編譯器陷入自我懷疑中。
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();
}
這時候,編譯器完全不知道該調(diào)用哪個 print() 方法,print(String... strs) 還是 print(Integer... ints),傻傻分不清。

假如真的需要重載帶有可變參數(shù)的方法,就必須在調(diào)用方法的時候給出明確的指示,不要讓編譯器去猜。
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)?{
}
上面這段代碼是可以編譯通過的。因為編譯器知道參數(shù)是 String 類型還是 Integer 類型,只不過為了運行時不拋出 NullPointerException,兩個 print() 方法的內(nèi)部要做好判空操作。

這是《Java 程序員進階之路》專欄的第 65 篇。Java 程序員進階之路,風趣幽默、通俗易懂,對 Java 初學者極度友好和舒適??,內(nèi)容包括但不限于 Java 語法、Java 集合框架、Java IO、Java 并發(fā)編程、Java 虛擬機等核心知識點。
點擊上方名片,發(fā)送消息「03」 就可以獲取《Java 程序員進階之路》的 PDF 版了,一起成為更好的 Java 工程師。

