Java中對於多個返回參數的選項是有限制的.一種方法只能返回一個對象,數組或原始函數,和其他語言不同的是它不會提供一種簡易方式來消耗方法調用中的參數.實際上我們的選擇是返回一個對象數組,一個集合,僅為返回的參數創建一個類,或者最終將其發送到你打算替換的對象中.所有這些方法都存在缺陷:
使用對象數組
如果我們能夠幸運地獲取一套同類的返回參數,那麼對象的數組就會是一個帶有例外的選項,當我們打開這些對象時需要能分辨出每個參數.從另一方面來說,如果我們正在返回多個參數的不同類型,我們需要使用所有超類對象的數組--最有可能的就是對象本身.然後我們要拋出每一個打開的參數.我們已經丟失了類型安全性且返回參數命令出錯的機會也可能增加.
使用集合
與使用數組的方法類似,我們或許也能創造一個集合來實現返回.在集合之上使用數組主要是因為創建集合所需要代碼的數量要比這段代碼短:
List< Object> retVal = new ArrayList< Object>(); retVal.add(string1); retVal.add(num2); retVal.add(object3); return retVal; |
而創建集合的代碼要高於使用數組初始化設置:
return new Object[] {string1, num2, object3}
事實上在數組之上使用集合沒有什麼真正的優勢,除非我們要使用映射以便通過名稱或其他要素來返回值.
首次創建Java時,其簡單程度是對日趨複雜的c 的一種顛覆.指針和內存管理被簡化了,包括去除參數間接,常量,函數指針以及其他功能強大但容易混淆的性能.在c 中,我們可以用值或參照傳遞參數,這樣可以對方法中的參照進行重新分配,並為你提供參數或返回更多值的方式.
使用JavaBeans
C 也支持的structs允許簡單結構化的數據包.當然,Java類可以簡單完成雙倍於structs的任務,但通常習慣以大量模板來擴大源碼.
使用類和JavaBeans慣例的時候還存在一個問題,即對象本質上是易變的.這些對象有可能被方法的調用者和方法調用的類共享,這樣就會導致易變狀態的共享,而這中情況無益於多線程系統.
在Java中,你能用值傳遞的只剩下方法參數了,還不能是outparams,同時方法只能返回一個參數.看一下任意代碼庫就會發現大量的實例,不過卻不是十分有效.
改進Java Beans方式
那麼應該怎樣做呢?Java類選項事實上才是解決方案的關鍵以及對其方式進行改進.這些類可以成為structs更好的替代物.
讓我們為返回的類確定兩個參數:名稱和出生日期:
public class PersonNameDOB { private String name; private Date dob; public Date getDob() { return dob; } public void setDob(Date dob) { this.dob = dob; } public String getName() { return name; } public void setName(String name) { this.name = name; } } |
顯然這是一個人為的例子,我們有機會擁有一個已經定義的Person類.大家肯定也會有類似的例子,需要從方法中返回兩個不同的對象,但是卻沒有已經為其定義的類,或者是返回的類夾帶多餘的信息,或許是比這更糟的情況.例如,如果有人調用了你的方法來使用或修改返回對象中的值.
上述情況所需代碼更多.因此我們可以做一些簡單的修改:
public class PersonNameDOB { public final String name; public final Date dob; public PersonNameDOB(String name, Date dob) { this.name = name; this.dob = dob; } } |
其結果要短一些且更適合這項任務.值已經返回了,因此不需要setters了,我們只要在返回對象建成后創建值就可以了.它們不需要更改,由於它們位於構造器中,因此具有決定性作用.現在任務已經完成,將類的屬性設為公開也沒有風險了.同理,可以處理getters了,其結果更短,更易於使用.
PersonNameDOB personNameDOB = SSNLookup.lookupBySSN("123-45-6789"); System.out.println(personNameDOB.name); System.out.println(personNameDOB.dob); lookupBySSN方法: public PersonNameDOB lookupBySSN(String ssn) { ... Find the person record in the DB, etc. ... return new PersonNameDOB(person.getName(), person.getDOB()); } |
如果這些太顯而易見,請耐心看下去.我喜歡用這個方法來簡化對象的返回.這種類型是安全的,因此在返回后不需要將對象拋出數組.而屬性的修改意味著這些返回的對象不會被濫用--它們只是為了傳輸數據.
為了安全起見,建議你複製對象或使用不可改變的對象以應付值的意外修改.在我們的例子中,字元串是不可改變的,但是日期可以複製:
public PersonNameDOB lookupBySSN(String ssn) { ... Find the person record in the DB, etc. ... return new PersonNameDOB(person.getName(), new Date(person.getDOB().getTime())); } |
這可以阻止調用者做接下來的操作:
PersonNameDOB personNameDOB = SSNLookup.lookupBySSN("123-45-6789"); personNameDOB.dob.setTime(0); |
成對的需求
以上的模式是筆者經常在Java應用程序介面調用中用來替代structs的方法,但是如果我們只是想返回兩個類型對象,這些就還不夠.看上去是唾手可得的東西其實仍然從JavaSE標準分配中神秘失蹤,而這就是被原始化的Pair類.看看我們如何從上述模式來建立Pair類.
,值要比名稱和出生日期都普遍.最普遍的是在將域名定為first和second:
public class Pair { public final String first; public final Date second; public Pair(String first, Date second) { this.first = first; this.second = second; } } |
現在擁有了一個可以返回Strings和Dates對的通用類,但還不包括其他類型,將其擴展為通用類型:
public class Pair< A, B> { public final A first; public final B second; public Pair(A first, B second) { this.first = first; this.second = second; } } |
這樣就好多了.沒必要擔心代表了一對返回類型快捷方式的通配符.這個類現在可以作為通用類型對來使用,如:
public static Pair< String, Date> lookupBySSN(String ssn) { // find the person in the DB.... return new Pair(person.getName(), new Date(person.getDOB().getTime())); } |
開始使用:
Pair< String, Date> personNameDOB = SSNLookup.lookupBySSN("123-45-6789"); System.out.println(personNameDOB.first); System.out.println(personNameDOB.second); |
1. 我們不想其他人擴展或更改Pair類,因為這可能破壞類的最初意圖.
2. 新的new Pair()是可以的,但是看上去有些不雅,我們可以改進一下.
3. Pair對於返回值是很有效的,但是如果要作為映射中的關鍵要素就有些勉為其難.
4. 擁有用於調試或其他toString()用途的Pair字元串代表形式是非常好的.
5. ,允許Pair和包含的對象可被序列化,其內容也被假定為可以序列化了.
因此,讓我們看看它是如何改變Pair執行的:
public final class Pair< A,B> implements Serializable { private static final long serialVersionUID = 1L; // shouldn't // need to change public final A first; public final B second; private Pair (A first, B second) { this.first = first; this.second = second; } public static < A,B> Pair< A,B> of (A first, B second) { return new Pair< A,B>(first,second); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Pair other = (Pair) obj; if (this.first != other.first && (this.first == null || !this.first.equals(other.first))) { return false; } if (this.second != other.second && (this.second == null || !this.second.equals(other.second))) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 37 * hash (this.first != null ? this.first.hashCode() : 0); hash = 37 * hash (this.second != null ? this.second.hashCode() : 0); return hash; } @Override public String toString () { return String.format("Pair[%s,%s]", first,second); } } |
return Pair.of(person.getName(), new Date(person.getDOB().getTime()));
Pair類已經完成,因此沒有人可以取代它或改變類的原始意圖.它看上去很嚴格,但卻可以用於廣泛的使用.如果有人想讓Pair以不同方式運作,就應該寫出適合自己的執行代碼,並對其進行求證.
equals()和hash()方法意味著這個類不止可以用來返回值.它們還可以成為映射的關鍵.此處對使用該模式返回對象給出的建議是讓IDE為我們創建equals和hashCode方法.IDE往往都有可插入的模板,其中包含已定義的最佳方法,因此我們只需讓NetBeans創建就可以了.
toString()給出了pair的合適形式,如 "Pair[FredJones,Sun Mar 22 12:55:44 PDT 2009]".這對於調試彈出很有用.
這一個類現在執行Serializable.相信此處我們的選擇令人懷疑,但是集合是可序列化的,Pair應該也可以.如果Pair中的類不能序列化那麼就如果集合中的類不能序列化一樣糟糕,而Pair不應該阻止類中間的序列化.
完成了嗎?
既是又不是.這個類現在在Navigenics中使用.毫無疑問在其他Java應用程序中也存在類似的執行.那我們完成了沒有呢?從"最具預見性"的角度來講,我們完成了.從"任何時候都需要"的角度來說恐怕還沒完成.
例如,這類Pairs不能做比較.增加一個compareTo()方法使之可比,但是要注意應對複雜的通用類設計.通常,比較第一個值很容易,如果它們相等,就要比較第二個值.這可能是最合適的行為,但是Pair的每次使用都正確嗎?我們要檢查一下作比較的類是否具有可比性,如果不具備,該怎麼辦?
結論
為Java創建一個Pair類是有些難的,在簡單通用的類庫執行中,類庫要求在少量功能前提下不改變Java語言.通用性是很麻煩的一件事.不過,本人相信本文描述的Pair類至少是SE庫中標準化Pair執行的良好開端.但願在Java 7中會增加這一項.儘管它對於其他語言中的Tuple來說是顯得有些貧乏,但是加入標準的Pair有望為我們帶來大量實用性.這種可讀性的改進以及模板代碼的刪除都旨在減少Coin項目的語言變化.
[火星人 ] 為Java創建Pair類已經有694次圍觀