J2SE 1.5裡引入了“Static Import”機制,借助這一機制,可以用略掉所在的類或接口名的方式,來使用靜态成員。本文介紹這一機制的使用方法,以及使用過程中的注意事項。
在Java程式中,是不允許定義獨立的函數和常量(當然,準确的說,隻是被final修飾、隻能指派一次的變量)的。即使從它們本身的功能來看,完全不需要依附于什麼東西,也要找個類或接口作為挂靠機關才行(在類裡可以挂靠各種成員,而接口裡則隻能挂靠常量)。
挂靠的方法,是把它們加上static修飾符,定義為這個類或接口的靜态成員。這方面的典型例子是java.lang.Math類——包含了大量的sin、cos這樣的“函數”和PI、E這樣的“常量”。
傳統上,在通路這些挂靠了的函數、變量和常量的時候,需要在前面加上它們挂靠機關的名稱。如果隻是偶爾通路這些東西一下,這樣的寫法可以工作得很好;但是如果要頻繁通路這些成員的話,這樣的寫法就顯得比較羅嗦了。
J2SE 1.5裡引入了“Static Import”機制,借助這一機制,可以用略掉所在的類或接口名的方式,來使用靜态成員。
“靜态導入”或者“靜态成員導入”
Static Import機制常常被直譯成“靜态導入”。但是從含義上看,“靜态成員導入”是更為貼切的譯法。不過考慮到“靜态導入”這說法比較簡短,估計還是會有強大的生命力。
隻包括常量定義的接口
有一種省去常量前的挂靠機關的變通做法:将所有的常量都定義到一個接口裡面,然後讓需要這些常量的類實作這個接口(這樣的接口有一個專門的名目,叫作“Constant Interface”)。
這個方法可以工作。但是,因為這樣一來,就可以從“一個類實作了哪個接口”推斷出“這個類需要使用哪些常量”,有“會暴露實作細節”的問題。
1.精确導入的方式
精确的導入一個靜态成員的方法,是在源檔案的開頭部分(任何類或接口的定義之前),加上類似這樣的聲明:
import static 包名.類或接口名.靜态成員名;
注意盡管這個機制的名目是叫做“Static Import”,但是在這裡的次序卻是正好相反的“import static”。一經導入之後,在整個源檔案的範圍内,就可以直接用這個成員的名字來通路它了。
清單1:用精确導入的方式,導入sin和PI
//精确的導入Math.sin和Math.PI
import static java.lang.Math.sin;
import static java.lang.Math.PI;
public class StaticImportSampleA {
public static void main(String[] args) {
System.out.println(sin(PI/2));//輸出“1.0”
}
}
遊離于包外的類和接口們的特别問題
Java語言并未要求每個類和接口都必須屬于某一個包。但是,在J2SE 1.4以後,無論是import語句也好,還是import static語句也好,都要求給一個所在包名出來。換而言之,對于不屬于任何包的類和接口,是既不能用import導入它本身,也不能用import static導入它的靜态成員的。
2.按需導入的方式
Static Import機制也支援一種不必逐一指出靜态成員名稱的導入方式。這時,要采用這樣的文法:
import static 包名.類或接口名.*;
注意這種方式隻是指出遇到來曆不明的成員時,可以到這個類或接口裡來查找,并不是把這個類或接口裡的所有靜态成員全部導入。
清單2:用按需導入的方式,導入sin和PI
//聲明遇到來曆不明的成員時到java.lang.Math中去尋找
import static java.lang.Math.*;
public class StaticImportSampleB {
public static void main(String[] args) {
System.out.println(sin(PI/2));//輸出“1.0”
}
}
3.可以導入的種種東西
使用import static語句,可以導入一個類裡的一切被static修飾的東西,包括變量、常量、方法和内類。
清單3:變量、常量、方法和内部類都可以導入
package com.example.p3;
public class StaticImportee {
public static int one = 1;
public static final int TWO = 2;
public static int three() {
return 3;
}
public static class Four {
public int value() {
return 4;
}
}
}package com.example.p3;
import static com.example.p3.StaticImportee.*;
public class StaticImporter {
public static void main(String[] args) {
System.out.println(one);
System.out.println(TWO);
System.out.println(three());
System.out.println(new Four());
}
}
不受影響的通路控制
Static Import不能突破Java語言中原有的通路控制機制的限制,不過也并不在這方面增加新的限制。原來有權限通路的靜态成員,都可以被導入和使用;而原來無權限通路的靜态成員,用了這個方法之後也仍然是通路不能。
4.導入之間的沖突問題
不同的類(接口)可以包括名稱相同的靜态成員。是以,在進行Static Import的時候,可能會出現“兩個語句導入同名的靜态成員”的情況。
在這種時候,J2SE 1.5會這樣來加以處理:
- 如果兩個語句都是精确導入的形式,或者都是按需導入的形式,那麼會造成編譯錯誤。
- 如果一個語句采用精确導入的形式,一個采用按需導入的形式,那麼采用精确導入的形式的一個有效。
注意,如果兩個同名的靜态成員一個是屬性,而另一個是方法,那麼因為使用時的寫法有差異,不會造成任何的沖突。
清單4:采用精确導入的形式的一個有效
package com.example.p4;
import static com.example.p4.Importee1.name;
import static com.example.p4.Importee1.*;
import static com.example.p4.Importee2.*;
import static com.example.p4.Importee2.pass;
public class Importer {
public static void main(String[] args) {
System.out.println(name);//輸出“Name1”
System.out.println(pass);//輸出“Pass2”
}
}package com.example.p4;
public class Importee1 {
public static String name = "Name1";
public static String pass = "Pass1";
}package com.example.p4;
public class Importee2 {
public static String name = "Name2";
public static String pass = "Pass2";
}
5.本地和外來的競争
有時候,導入的東西還可能和本地的東西相沖突,這種情況下的處理規則,是“本地優先”。
清單5:本地的優先于外來的
package com.example.p5;
import static com.example.Zero.*;
public class One {
public static int flag = 1;
public static void main(String[] args) {
System.out.println(flag);//輸出“1”
}
}package com.example.p5;
public class Zero {
public static int flag = 0;
}
6.Static Import的負面影響
在編譯期間,所有因Static Import的存在而簡化了的名字,都會被編譯器打回原型。是以在性能方面,Static Import沒有任何影響。但是名字簡化卻可能造成一些維護方面的問題。
去掉靜态成員前面的類型名,固然有助于在頻繁調用時顯得簡潔,但是同時也失去了關于“這個東西在哪裡定義”的提示資訊,增加了閱讀了解的麻煩。如果導入的來源很著名(比如java.lang.Math),或者來源的總數比較少,這個問題并不嚴重;但是在不屬于這兩種的情況下,這就不是基本可以忽略的問題了。
7.歸納總結
借助J2SE 1.5裡提供的Static Import機制,可以用一種更簡單的方式,來通路類和接口的靜态成員。不過,使用這一機制并不是沒有代價的,在使用不當的時候可能給維護工作帶來一定的困擾。是以,在具體使用之前,還要作一些兩方面的權衡。