在Java1.5中創建可變參數[Varargs]
方法重載是Java和其他面向對象語言最具特色的特性之一。當許多人可能認為Java的優勢是它的類型,或者是它所帶的API庫,其實讓相同的方法名與各種各樣可接受的參數搭配也是一件很好的事。Guitar guitar = new Guitar("Bourgeois", "Country Boy Deluxe",
GuitarWood.MAHOGANY, GuitarWood.ADIRONDACK,1.718);
Guitar guitar = new Guitar("Martin", "HD-28");
Guitar guitar = new Guitar("Collings", "CW-28"
GuitarWood.BRAZILIAN_ROSEWOOD, GuitarWood.ADIRONDACK,1.718,
GuitarInlay.NO_INLAY, GuitarInlay.NO_INLAY);
This
code calls three versions of the constructor of a (fictional) Guitar
class, meaning that information can be supplied when it’s
available,rather than forcing a user to know everything about their
guitar at one time (many professionals couldn’t tell you their guitar’s
width at the nut).
Here are the constructors used:
public Guitar(String builder, String model) {
}
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,
float nutWidth) {
}
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,
float nutWidth,
GuitarInlay fretboardInlay, GuitarInlay topInlay) {
}
這段代碼調用了Guitar類中三個版本的構造器,意味著當信息可見時,這些信息會被支持,而不是迫使每一個使用者每一次都要去了解關于Guitar類的所有知識。許多專家不會在關鍵時候告訴你他們的Guitar的內容。下面是用到的構造器:
public Guitar(String builder, String model) {
}
public Guitar(String builder, String model,GuitarWood backSidesWood, GuitarWood topWood,float nutWidth) {
}
public Guitar(String builder, String model,GuitarWood backSidesWood, GuitarWood topWood,float nutWidth,
GuitarInlay fretboardInlay, GuitarInlay topInlay) {
}
然而,當你想要去增加無限的信息時,事情開始變得有一點不是那么有用了。例如:假設你想允許在這個構造器中增加額外的未指明的特性。下面就是一些可能的調用的例子:
Guitar guitar = new Guitar("Collings", "CW-28"
GuitarWood.BRAZILIAN_ROSEWOOD, GuitarWood.ADIRONDACK,1.718,
GuitarInlay.NO_INLAY, GuitarInlay.NO_INLAY,"Enlarged Soundhole", "No Popsicle Brace");
Guitar
guitar = new Guitar("Martin", "HD-28V","Hot-rodded by Dan Lashbrook",
"Fossil Ivory Nut","Fossil Ivory Saddle", "Low-profile bridge pins");
對于這兩個單獨的情況,你不得不去增加一個構造器來接受兩個額外的字符串,另外一個構造器來接受四個額外的字符串。試圖將這些相似的版本應用于早已重載的構造器。根據這樣的話,你最終會得到20或30個那樣愚蠢的構造器的版本!
原因在于我們常稱做的可變參數??勺儏凳荰iger的增加的另一個特性,它用一種相當巧妙的方法徹底地解決了這兒提出的問題。這一章講述了這種相對簡單的特性的各個方面。這將會使你迅速寫出更好、更整潔、更靈活的代碼。
創建一個可變長度的參數列表
可變參數使得你可以指定某方法來接受多個同一類型的參數,而且并不要求事先確定參數的數量(在編譯或運行時)。
這就是Tiger的一個集成部分。事實上,正是因為Java語言的一些新特性組合在一起才表現出了可變參數的特性。
我如何去實現呢?
首先,你要習慣的書寫省略號(……)。這三個小點是可變參數的關鍵,你將會經常鍵入它們。下面是Guitar類的構造器使用可變參數來接受不確定數量字符串的一個例子:
public Guitar(String builder, String model, String……features);
參數String…… features 表明任何數量的字符串都可能被接受。 所以,下面所有的調用都合法的。
Guitar guitar = new Guitar("Martin", "HD-28V","Hot-rodded by Dan
Lashbrook", "Fossil Ivory Nut","Fossil Ivory Saddle", "Low-profile
bridge pins");
Guitar guitar = new Guitar("Bourgeois", "OMC","Incredible flamed maple bindings on this one.");
Guitar guitar = new Guitar("Collings", "OM-42","Once owned by Steve Kaufman--one of a kind");
You could add the same variable-length argument to the other constructors:
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,float nutWidth, String... features)
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,float nutWidth,
GuitarInlay fretboardInlay,GuitarInlay topInlay,String... features)
例5-1描寫了一個把所有的這些特性放在一起的簡單類,甚至使用XX來一起傳遞一些可變參數。
Example 5-1. Using varargs in constructors
package com.oreilly.tiger.ch05;
public class Guitar {
private String builder;
private String model;
private float nutWidth;
private GuitarWood backSidesWood;
private GuitarWood topWood;
private GuitarInlay fretboardInlay;
private GuitarInlay topInlay;
private static final float DEFAULT_NUT_WIDTH = 1.6875f;
public Guitar(String builder, String model, String... features) {
this(builder, model, null, null, DEFAULT_NUT_WIDTH, null, null, features);
}
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,
float nutWidth, String... features) {
this(builder, model, backSidesWood, topWood, nutWidth, null, null, features);
}
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,float nutWidth,
GuitarInlay fretboardInlay, GuitarInlay topInlay,String... features) {
this.builder = builder;
this.model = model;
this.backSidesWood = backSidesWood;
this.topWood = topWood;
this.nutWidth = nutWidth;
this.fretboardInlay = fretboardInlay;
this.topInlay = topInlay;
}
}
剛才發生了什么?
當你指定了一個可變長度參數列表,Java編譯器實際上讀入 “create an array of type <參數類型>”。你鍵入:
public Guitar(String builder, String model, String…… features)
然而:編譯器解釋這些為:
public Guitar(String builder, String model, String[] features)
這意味著重復參數列表變得簡單(這將在“重復可變長度參數列表”里講述),這與你需要完成的其他程序設計目標是一樣。
你可以像使用數組一樣來使用可變參數。
然而,這同樣存在一些限制。第一,在每個方法中,你只可以使用一次省略號。所以,下面的書寫是不合法的:
public Guitar(String builder, String model,String…… features, float…… stringHeights)
另外,省略號必須作為方法的最后一個參數。
如果你不需要傳遞任何可變參數呢?
那沒關系,你只需要以舊的方式調用構造器:
Guitar guitar = new Guitar("Martin", "D-18");
我們再仔細看看,雖然程序中沒有與下面代碼相匹配的構造器:
public Guitar(String builder, String model)
那么,代碼到底傳遞了什么呢?作為可變參數的特例,在參數中不傳遞東西是一個合法的選項。所以,當你看到 String…… features,你應該把它認為是零個或者更多個String參數。這省卻你再去創建另一個不帶可變參數構造器的麻煩。
重復可變長度參數類表
所有這些可變參數是很好的。但是實際上,如果你不在你的方法中使用它們的話,他們顯然僅僅是吸引眼球的東西或是窗戶的裝飾品而已。
然而,你可以像你使用數組一樣來使用可變參數,你會覺得這種用法很簡單。
那我怎么來使用可變參數呢?
首先你要確保閱讀了“創建一個可變長度的參數列表”,你會從中了解到可變參數方法最重要的東西,那就是我們把可變參數當作數組來看待。
所以,繼續前面的例子,你可以寫出下面的代碼:
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,float nutWidth,
GuitarInlay fretboardInlay, GuitarInlay topInlay,String... features) {
this.builder = builder;
this.model = model;
this.backSidesWood = backSidesWood;
this.topWood = topWood;
this.nutWidth = nutWidth;
this.fretboardInlay = fretboardInlay;
this.topInlay = topInlay;
for (String feature : features) {
System.out.println(feature);
}
}
上面的這段代碼看上是不是不是那么的有吸引力?但這確實體現了可變參數的精髓。作為另一個例子,下面這個簡單的方法從一組數字中計算出最大值:
public static int max(int first, int... rest) {
int max = first;
for (int i : rest) {
if (i > max)
max = i;
}
return max;
}
是不是,夠簡單吧?
那么如何存儲可變長度參數呢?
正因為Java編譯器把這些看作數組,所以數組顯然是一個存儲的好選擇,這將在下面的例5-2中體現。
Example 5-2. 存儲作為成員變量的可變參數
package com.oreilly.tiger.ch05;
public class Guitar {
private String builder;
private String model;
private float nutWidth;
private GuitarWood backSidesWood;
private GuitarWood topWood;
private GuitarInlay fretboardInlay;
private GuitarInlay topInlay;
private String[] features;
private static final float DEFAULT_NUT_WIDTH = 1.6875f;
public Guitar(String builder, String model, String... features) {
this(builder, model, null, null, DEFAULT_NUT_WIDTH, null, null, features);
}
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,
float nutWidth, String... features) {
this(builder, model, backSidesWood, topWood, nutWidth, null, null, features);
}
public Guitar(String builder, String model,
GuitarWood backSidesWood, GuitarWood topWood,
float nutWidth,
GuitarInlay fretboardInlay, GuitarInlay topInlay,
String... features) {
this.builder = builder;
this.model = model;
this.backSidesWood = backSidesWood;
this.topWood = topWood;
this.nutWidth = nutWidth;
this.fretboardInlay = fretboardInlay;
this.topInlay = topInlay;
this.features = features;
}
}
你可以簡單地在Java的Collection類中存儲這些可變參數。
//變量聲明
private List features;
//在方法中或是構造器中的書寫
this.features = java.util.Arrays.asList(features);
允許零長度的參數列表
可變參數的一個顯著的特性是可變長度參數可以接受零到N個參數。這就意味著你可以調用這些方法中的一個方法而不傳遞任何參數,程序同樣可以運行。從另一方面來說,這又意味著,作為一個程序員,你最好意識到你必須防范這種情況的發生。
如何實現它呢?
記得在“重復可變長度參數類表”中,你讀到過下面這個簡單的方法:
public static int max(int first, int... rest) {
int max = first;
for (int i : rest) {
if (i > max)
max = i;
}
return max;
}
你可以以多種形式來調用這個方法:
int max = MathUtils.max(1, 4);
int max = MathUtils.max(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int max = MathUtils.max(18, 8, 4, 2, 1, 0);
有一點不是那么令人滿意的地方是,在很多情況下,你要傳遞的數字已經存儲在數組里,或是至少是在某些集成的形式中:
//用這種方法來取得數字
int[] numbers = getListOfNumbers( );
要把這些數字傳遞給max()方法是不可能的。你需要檢查list的長度,從中截取掉第一個對象(如果存在第一個對象的話),然后檢查類型來確 保是int型。完成了這些,你才可以帶著數組中剩余的部分一起傳遞進入方法。而這數組中剩余的部分還要重復,或者要人工地轉化為適合的格式。總之,這個過 程會很辛苦,你需要做許多瑣碎的事情。仔細想想,你要記得編譯器是將這個方法解釋為下面的語句:public static int max(int first, int[] rest)
所以,你可以做些調整,把max()方法改寫成下面這個樣子:
public static int max(int... values) {
int max = Integer.MIN_VALUE;
for (int i : values) {
if (i > max)
max = i;
}
return
你現在已經定義了一個可以很容易接受數組的方法。
//用這種方法來取得數字
int[] numbers = getListOfNumbers( );
int max = MathUtils.max(numbers);
當接受單一的可變長度參數時,你使用這種方法會很簡單。但是,如果在最好的情況下,你傳遞了一個零長度的數組進去,這就會帶來問題,你會得到難 以預料的結果。為了解決這個問題,你需要一個小的錯誤檢查。例5-3是MathUtils類的完整代碼列表,在這里是一個功能更強的MathUtil類。
例5-3 處理零參數的方法
package com.oreilly.tiger.ch05;
public class MathUtils {
public static int max(int... values) {
if (values.length == 0) {
throw new IllegalArgumentException("No values supplied.");
}
任何時候,你都可能會要處理零長度的參數列表,這時你就需要執行這類的錯誤檢查。通常,一個功能強大的IllegalArgumentException類是一個好的選擇。
int max = Integer.MIN_VALUE;
for (int i : values) {
if (i > max)
max = i;
}
return max;
}
}
那么關于調用同樣的方法來處理通常參數不是數組的方法,又會如何呢?這當然是完全合法的。下面的代碼都是合法調用max()方法的手段:
int max = MathUtils.max(myArray);
int max = MathUtils.max(new int[] { 2, 4, 6, 8 });
int max = MathUtils.max(2, 4, 6, 8);
int max = MathUtils.max(0);
int max = MathUtils.max( );
指定對象參數,而非基本類型
在第四章中我們談到,Tiger通過拆箱增加了一系列的新特征。你可以在處理可變參數時,在你的方法接受的參數中使用對象包裝類。
如何實現?
你一定記得在Java中所有的類最終都是java.lang.Object的子類。這就意味著任何對象可以被轉化成一個Object對象。更進 一步說,因為像int和short這樣的基本類型會自動轉化成他們對應的對象包裝類(就像Integer和Short),任何Java類型可以被轉化成一 個Object對象。
所以,如果你需要你的可變參數方法可以接受最多種參數的類型,那么你可以將Object類型作為參數的類型。更好的是,為了達到多重功能,絕大多數情況下都會使用Object對象。例如,寫個用來打印方法。
private String print(Object... values) {
StringBuilder sb = new StringBuilder( );
for (Object o : values) {
sb.append(o)
.append(" ");
}
return sb.toString( );
}
這兒最簡單的意思是打印出所有的東西。然而,這個方法更通用的定義是下面的樣子:
private String print(String... values) {
StringBuilder sb = new StringBuilder( );
for (Object o : values) {
sb.append(o)
.append(" ");
}
return sb.toString( );
}
這個方法的問題是方法自身不能接受字符串,整數,浮點數,數組和其他的類型數據,而這些數據你都想要正常的打印出來。通過使用Object這個更為通用的類型,你可以來打印所有的一切。
private String print(Object... values) {
StringBuilder sb = new StringBuilder( );
for (Object o : values) {
sb.append(o)
.append(" ");
}
return sb.toString( );
}
避免數組自動轉化
Tiger增加了各種類型的自動轉化和便利,這些東西在絕大多數的情況下是很好用的。不幸的是,有些時候所有的這些東西會變成你的障礙。其中一 種情況是,在可變參數方法中將多個Object對象轉化為Object[]數組對象,你會發現在個別的情況下,你需要用Java來書寫。
如何實現?
在將要仔細討論這件事情前,你要確信自己理解這個問題。Java新的printf()方法是一個很好的便利,舉這個方法作個例子:
System.out.printf("The balance of %s's account is $%(,6.2f\n",account.getOwner()。getFullName( ),account.getBalance( ));
如果你看一下Java文檔中關于printf()方法的說明,你就會看到它是一個可變參數的方法。它有兩個參數:一個是用于設置字符串格式的String類型變量,另一個是所有要傳遞進字符串的Object對象:
PrintStream printf(String format, Object…… args)
現在,你可以把上面的代碼默認為下面的形式:
PrintStream printf(String format, Object[] args)
兩種書寫是不是完全相同呢?大多數情況下是相同的??紤]一下下面的代碼:
Object[] objectArray = getObjectArrayFromSomewhereElse( );out.printf("Description of object array: %s\n", obj);
這是乎有點牽強,然而要把它看作是為了自省的代碼而付出的正常開銷。比起其它代碼,這樣寫要簡潔的多。如果你正在編寫一個代碼分析工具,或者一 個集成開發環境,或者其他可能使用reflection或簡單API來判斷出應用程序會需要何種對象的東西,這些馬上會成為一個通用的案例。這兒,你不是 真正關心對象數組的內容,就像你同樣不會去關心數組自身一樣。它是什么類型?它的內存地址是多少?它的字符串代表什么意思?請緊記所有這些問題都是和數組 本身有關的,和數組的內容無關。例如:我們來看看下面的數組代碼:
public Object[] getObjectArrayFromSomewhereElse( ) {return new String[] {"Hello", "to", "all", "of", "you"};}
在這種情況下,你肯能會寫一些像下面一樣的代碼來回答某些關于數組的問題:
out.printf("Description of object array: %s\n", obj);
然而,輸出結果并不是你所期望的那樣:
run-ch05:[echo] Running Chapter 5 examples from Java Tiger: A Developer's Notebook[echo] Running VarargsTester……[java] Hello
這倒是怎么回事?這就不是你想看到的結果。然而,編譯器做了它應該做的,它把在printf()方法里的Object……轉換為 Object[].實際上,當編譯器得到你方法的調用時,它看到的參數是Object[].所以編譯器不是把這個數組看作一個Object對象本身,而是 把它分成不同的部分。這樣被傳遞給字符串格式 (%s)的就是第一個參數部分“Hello”字符串,所以結果“Hello”就顯示出來了。
仔細看看這件事,你需要去告訴編譯器你要把整個對象數組obj看作是一個簡單的對象,而不是一組參數。請看下面奇特的代碼:
out.printf("Description of object array: %s\n", new Object[] { obj });
作為選擇,還有一種更為簡單的方法:
out.printf("Description of object array: %s\n", (Object)obj);
在上面兩種書寫情況下,編譯器不再認為是對象的數組,而是直接認為是一個簡單的Object對象,而這個Object對象又恰好是一個對象數組。那么結果就如你所愿(至少在這種簡單的應用下):
run-ch05:[echo] Running Chapter 5 examples from Java Tiger: A Developer's Notebook[echo] Running VarargsTester……[java] [Ljava.lang.String;@c44b88
看到結果,你肯能會感到有點錯亂。這大概是基于reflection或者其他自省代碼需要的結果。
posted on 2008-08-07 18:12 gembin 閱讀(347) 評論(0) 編輯 收藏 所屬分類: JavaSE