來看看一個程式片段:
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()
方法,以下情況編譯就會出錯: