JAVA更多的類謎題71:進口稅

字號:

在5.0版中,Java平臺引入了大量的可以使操作數(shù)組變得更加容易的工具。下面這個謎題使用了變量參數(shù)、自動包裝、靜態(tài)導(dǎo)入(請查看http://java.sun.com/j2se/5.0/docs/guide/language [Java-5.0])以及便捷方法Arrays.toString(請查看謎題60)。那么,這個程序會打印什么呢?
    import static java.util.Arrays.toString;
    class ImportDuty {
     public static void main(String[ ] args) {
     printArgs(1, 2, 3, 4, 5);
     }
     static void printArgs(Object... args) {
     System.out.println(toString(args));
     }
    }
    你可能會期望該程序打印[1,2,3,4,5],實際上它確實會這么做,只要它能編譯。令人沮喪的是,看起來編譯器找不到恰當(dāng)?shù)膖oString方法:
    ImportDuty.java:9:Object.toString()can’t be applied to(Object[])
     System.out.println(toString(args));
     ^
    是不是編譯器的理解力太差了?為什么它會嘗試著去應(yīng)用Object.toString()呢?它與調(diào)用參數(shù)列表并不匹配,而Arrays.toString(Object[ ])卻可以完全匹配。
    編譯器在選擇在運行期將被調(diào)用的方法時,所作的第一件事就是在肯定能找到該方法的范圍內(nèi)挑選[JLS 15.12.1]。編譯器將在包含了具有恰當(dāng)名字的方法的最小閉合范圍內(nèi)進行挑選,在我們的程序中,這個范圍就是ImportDuty類,它包含了從Object繼承而來的toString方法。在這個范圍中沒有任何可以應(yīng)用于toString(args)調(diào)用的方法,因此編譯器必須拒絕該程序。
    換句話說,我們想要的toString方法沒有在調(diào)用點所處的范圍內(nèi)。導(dǎo)入的toString方法被ImportDuty從Object那里繼承而來的具有相同名字的方法所遮蔽(shade)了[JLS 6.3.1]。遮蔽與遮掩(謎題68)非常相像,二者的關(guān)鍵區(qū)別是一個聲明只能遮蔽類型相同的另一個聲明:一個類型聲明可以遮蔽另一個類型聲明,一個變量聲明可以遮蔽另一個變量聲明,一個方法聲明可以遮蔽另一個方法聲明。與其形成對照的是,變量聲明可以遮掩類型和包聲明,而類型聲明也可以遮掩包聲明。
    當(dāng)一個聲明遮蔽了另一個聲明時,簡單名將引用到遮蔽聲明中的實體。在本例中,toString引用的是從Object繼承而來的toString方法。簡單地說,本身就屬于某個范圍的成員在該范圍內(nèi)與靜態(tài)導(dǎo)入相比具有優(yōu)先權(quán)。這導(dǎo)致的后果之一就是與Object的方法具有相同名字的靜態(tài)方法不能通過靜態(tài)導(dǎo)入工具而得到使用。
    既然你不能對Arrays.toString使用靜態(tài)導(dǎo)入,那么你就應(yīng)該用一個普通的導(dǎo)入聲明來代替。下面就是Arrays.toString應(yīng)該被正確使用的方式:
    import java.util.Arrays;
    class ImportDuty {
     static void printArgs(Object... args) {
     System.out.println(Arrays.toString(args));
     }
    }
    如果你特別強烈地想避免顯式地限定Arrays.toString調(diào)用,那么你可以編寫你自己的私有靜態(tài)轉(zhuǎn)發(fā)方法:
    private static String toString(Object[] a) {
     return Arrays.toString(a);
    }
    靜態(tài)導(dǎo)入工具所專門針對的情況是:程序中會重復(fù)地使用另一個類的靜態(tài)元素,而每一次用到的時候都進行限定又會使程序變得亂成一鍋粥。在這類情況中,靜態(tài)導(dǎo)入工具可以顯著地提高可讀性。這比通過實現(xiàn)接口來繼承其常量要安全得多,而實現(xiàn)接口這種做法是你從來都不應(yīng)該采用的 [EJ Item 17]。然而,濫用靜態(tài)導(dǎo)入工具也會損害可讀性,因為這會使得靜態(tài)成員的類在何處被使用顯得非常不清晰。應(yīng)該有節(jié)制地使用靜態(tài)導(dǎo)入,只有在非常需要的情況下才應(yīng)該使用它們。
    對API設(shè)計者來說,要意識到當(dāng)某個方法的名字已經(jīng)出現(xiàn)在某個作用域內(nèi)時,靜態(tài)導(dǎo)入工具并不能被有效地作用于該方法上。這意味著靜態(tài)導(dǎo)入不能用于那些與通用接口中的方法共享方法名的靜態(tài)方法,而且也從來不能用于那些與Object中的方法共享方法名的靜態(tài)方法。再次說明一下,本謎題所要說明的仍然是你在覆寫之外的情況中使用名字重用通常都會產(chǎn)生混亂。我們通過重載、隱藏和遮掩看清楚了這一點,現(xiàn)在我們又通過遮蔽看到了同樣的問題。