來看看一個程式片段:
class Ball {
double radius;
final double PI = 3.14159;
...
}
如果你建立了多個Ball物件,那每個Ball物件都會有自己的radius與PI成員:
不過我們都知道,圓周率其實是個固定的常數,不用每個物件各自擁有,你可以在
PI上宣告static,表示它屬於類別:class Ball { double radius; static final double PI = 3.141596; ...}被宣告為
static的成員,不會讓個別物件擁有,而是屬於類別,如上定義後,如果建立多個Ball物件,每個Ball物件只會各自擁有radius:被宣告為
static的成員,是將類別名稱作為名稱空間,也就是說,你可以如下取得圓周率:System.out.println(Ball.PI);也就是透過類別名稱與
.運算子,就可以取得static成員。你也可以將宣告方法為static成員。例如:class Ball { double radius; static final double PI = 3.141596; static double toRadians(double angdeg) { // 角度轉徑度 return angdeg * (Ball.PI / 180); }}被宣告為
static的方法,也是將類別名稱作為名稱空間,可以透過類別名稱與.運算子來呼叫static方法:System.out.println(Ball.toRadians(100));
雖然語法上,也是可以透過參考名稱存取static成員,但非常不建議如此撰寫:
Ball ball = new Ball();System.out.println(ball.PI); // 極度不建議System.out.println(ball.toRadians(100)); // 極度不建議Java程式設計領域,早就有許多良好命名慣例,沒有遵守慣例並不是錯,但會造成溝通與維護的麻煩。以類別命名實例來說,首字是大寫,以
static使用慣例來說,是透過類別名稱與.運算子來存取。在大家都遵守命名慣例的情況下,看到首字大寫就知道它是類哵,透過類別名稱與.運算子來存取,就會知道它是static成員。所以,你一直在用的System.out、System.in呢?沒錯!out就是System擁有的static成員,in也是System擁有的static成員,這可以查看API文件得知:進一步按下
out鏈結就會看到完整宣告(有興趣也可以看src.zip中的System.java):所以
out實際上是java.io.PrintStream型態,被宣告為static,屬於System類別擁有。先前遇過的例子還有Integer.parseInt()、Long.parseLong()等剖析方法,根據命名慣例,首字大寫就是類別,類別名稱加上.運算子直接呼叫的,就是static成員,你可以自行查詢API文件來確認這件事。正如先前
Ball類別所示範,static成員屬於類別所擁有,將類別名稱當作是名稱空間是其最常使用之方式。例如在Java SE API中,只要想到與數學相關的功能,就會想到java.lang.Math,因為有許多以Math類別為名稱空間的常數與公用方法。因為都是static成員,所以你就可以這麼使用:System.out.println(Math.PI);System.out.println(Math.toRadians(100));由於
static成員是屬於類別,而非個別物件,所以在static成員中使用this,會是一種語意上的錯誤,具體來說,就是在static方法或區塊中不能出現this關鍵字。例如:如果你在程式碼中撰寫了某個物件資料成員,雖然沒有撰寫
this,但也隱含了這個物件某成員的意思,也就是:在上圖中,雖然撰寫
radius,但隱含了this.radius的意義,因此會編譯錯誤。static方法或區塊中,也不能呼叫非static方法或區塊。例如:在上圖中,雖然撰寫
doOther(),但實際隱含了this.doOther(),因此會編譯錯誤。static方法或區塊中,可以使用static資料成員或方法成員。例如:class Ball { static final double PI = 3.141596; static void doOther() { double o = 2 * PI; } static void doSome() { doOther(); } ...} 如果你有些動作,想在位元碼載入後執行,則可以定義
static區塊。例如:class Ball {
static {
System.out.println("位元碼載入後就會被執行");
}
}
在這個例子中,Ball.class載入JVM後,預設就會執行
static區塊。實際上,載入JDBC驅動程式的方式之一是運用Class.forName()動態載入Driver實作類別的位元碼:Class.forName("com.mysql.jdbc.Driver");這個程式碼片段,會將Driver.class載入JVM,而
com.mysql.jdbc.Driver的原始碼中,就是在static區塊中進行驅動程式實例註冊的動作:public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } ...}在JDK5之後,新增了
import static語法,可以在使用靜態成員時少打幾個字。例如System的out是static成員,為了要在文字模式下顯示訊息,本來都要這麼撰寫:System.out.println("好麻煩");
有了
import static,就可以簡化:package cc.openhome;
import java.util.Scanner;
import static java.lang.System.in;
import static java.lang.System.out;
public class ImportStatic {
public static void main(String[] args) {
Scanner scanner = new Scanner(in);
out.print("請輸入姓名:");
out.printf("%s 你好!%n", scanner.nextLine());
}
}
原本編譯器看到in時,並不知道in是什麼,但想起你用import static告訴過它,想針對java.lang.System.in這個static成員偷懶,所以就試著用java.lang.System.in編譯看看,結果就成功了,out也是同樣的道理,在不影響可讀性的情況下,適時使用import static可以簡化程式碼,讓程式碼讀來更流暢。
如果一個類別中有多個static成員想偷懶,也可以使用*。例如將上例中import static的兩行改為如下一行,也可以編譯成功:
import static java.lang.System.*;
與import一樣,import static語法是為了偷懶,但別偷懶過頭,要注意名稱衝突問題,有些名稱衝突編譯器可透過以下順序來解析:
- 區域變數覆蓋:選用方法中的同名變數、參數、方法名稱
- 成員覆蓋:選用類別中定義的同名資料成員、方法名稱
- 重載方法比對:使用
import static的各個靜態成員,若有同名衝突,嘗試透用重載判斷
如果編譯器無法判斷,則會回報錯誤,例如若cc.openhome.Util定義有static的sort()方法,而java.util.Arrays也定義有static的sort()方法,以下情況編譯就會出錯:

