Java static關鍵字以及Java靜態變量和靜態方法
static 修飾符能夠與變量、方法一起使用,表示是“靜態”的。
靜態變量和靜態方法能夠通過類名來訪問,不需要創建一個類的對象來訪問該類的靜態成員,所以static修飾的成員又稱作類變量和類方法。靜態變量與實例變量不同,實例變量總是通過對象來訪問,因為它們的值在對象和對象之間有所不同。
請看下面的例子:
public class Demo { static int i = 10; int j; Demo() { this.j = 20; } public static void main(String[] args) { System.out.println("類變量i=" + Demo. i); Demo obj = new Demo(); System.out.println("實例變量j=" + obj.j); }}
運行結果:
類變量i=10實例變量j=20
static 的內存分配
靜態變量屬於類,不屬於任何獨立的對象,所以無需創建類的實例就可以訪問靜態變量。之所以會產生這樣的結果,是因為編譯器只為整個類創建了一個靜態變量的副本,也就是只分配一個內存空間,雖然有多個實例,但這些實例共享該內存。實例變量則不同,每創建一個對象,都會分配一次內存空間,不同變量的內存相互獨立,互不影響,改變a 對象的實例變量不會影響b 對象。
請看下面的代碼:
public class Demo { static int i; int j; public static void main(String[] args) { Demo obj1 = new Demo(); obj1.i = 10; obj1.j = 20; Demo obj2 = new Demo(); System.out.println("obj1.i=" + obj1.i + ", obj1.j=" + obj1.j); System.out.println("obj2.i=" + obj2.i + ", obj2 .j=" + obj2.j); }}
運行結果:
obj1.i=10, obj1.j=20obj2.i=10, obj2.j=0
注意:靜態變量雖然也可以通過對象來訪問,但是不被提倡,編譯器也會產生警告。
上面的代碼中,i 是靜態變量,通過obj1 改變i 的值,會影響到obj2;j 是實例變量,通過obj1 改變j 的值,不會影響到obj2。這是因為obj1.i 和obj2.i 指向同一個內存空間,而obj1.j 和obj2.j 指向不同的內存空間,請看下圖:
注意:static 的變量是在類裝載的時候就會被初始化。也就是說,只要類被裝載,不管你是否使用了這個static 變量,它都會被初始化。
小結:類變量(class variables)用關鍵字static 修飾,在類加載的時候,分配類變量的內存,以後再生成類的實例對象時,將共享這塊內存(類變量),任何一個對像對類變量的修改,都會影響其它對象。外部有兩種訪問方式:通過對象來訪問或通過類名來訪問。
靜態方法
靜態方法是一種不能向對象實施操作的方法。例如,Math 類的pow() 方法就是一個靜態方法,語法為Math.pow(x, a),用來計算x 的a 次冪,在使用時無需創建任何Math 對象。
因為靜態方法不能操作對象,所以不能在靜態方法中訪問實例變量,只能訪問自身類的靜態變量。
以下情形可以使用靜態方法:
一個方法不需要訪問對象狀態,其所需參數都是通過顯式參數提供(例如Math.pow())。
一個方法只需要訪問類的靜態變量。
讀者肯定注意到,main() 也是一個靜態方法,不對任何對象進行操作。實際上,在程序啟動時還沒有任何對象,main() 方法是程序的入口,將被執行並創建程序所需的對象。
關於靜態變量和靜態方法的總結:
一個類的靜態方法只能訪問靜態變量;
一個類的靜態方法不能夠直接調用非靜態方法;
如訪問控制權限允許,靜態變量和靜態方法也可以通過對象來訪問,但是不被推薦;
靜態方法中不存在當前對象,因而不能使用this,當然也不能使用super;
靜態方法不能被非靜態方法覆蓋;
構造方法不允許聲明為static 的;
局部變量不能使用static修飾。
靜態方法舉例:
public class Demo { static int sum(int x, int y){ return x + y; } public static void main(String[] args) { int sum = Demo.sum(10, 10); System.out.println( "10+10=" + sum); }}
運行結果:
10+10=20
static 方法不需它所屬的類的任何實例就會被調用,因此沒有this 值,不能訪問實例變量,否則會引起編譯錯誤。
注意:實例變量只能通過對象來訪問,不能通過類訪問。
靜態初始器(靜態塊)
塊是由大括號包圍的一段代碼。靜態初始器(Static Initializer)是一個存在於類中、方法外面的靜態塊。靜態初始器僅僅在類裝載的時候(第一次使用類的時候)執行一次,往往用來初始化靜態變量。
示例代碼:
public class Demo { public static int i; static{ i = 10; System.out.println("Now in static block."); } public void test() { System.out.println("test method: i=" + i); } public static void main(String[] args) { System.out.println("Demo.i=" + Demo.i); new Demo().test(); }}
運行結果是:
Now in static block.Demo.i=10test method: i=10
靜態導入
靜態導入是Java 5 的新增特性,用來導入類的靜態變量和靜態方法。
一般我們導入類都這樣寫:
import packageName.className; // 導入某個特定的類
或
import packageName.*; // 導入包中的所有類
而靜態導入可以這樣寫:
import static packageName.className.methonName; // 導入某個特定的靜態方法
或
import static packageName.className.*; // 導入類中的所有靜態成員
導入後,可以在當前類中直接用方法名調用靜態方法,不必再用className.methodName 來訪問。
對於使用頻繁的靜態變量和靜態方法,可以將其靜態導入。靜態導入的好處是可以簡化一些操作,例如輸出語句System.out.println(); 中的out 就是System 類的靜態變量,可以通過import static java.lang.System.*; 將其導入,下次直接調用out.println() 就可以了。
請看下面的代碼:
import static java.lang.System.*;import static java.lang.Math.random;public class Demo { public static void main(String[] args) { out.println("產生的一個隨機數:" + random( )); }}
運行結果:
產生的一個隨機數:0.05800891549018705
Java final關鍵字:阻止繼承和多態<br />在Java 中,聲明類、變量和方法時,可使用關鍵字final 來修飾。 final 所修飾的數據具有“終態”的特徵,表示“最終的”意思。具體規定如下:
final 修飾的類不能被繼承。
final 修飾的方法不能被子類重寫。
final 修飾的變量(成員變量或局部變量)即成為常量,只能賦值一次。
final 修飾的成員變量必須在聲明的同時賦值,如果在聲明的時候沒有賦值,那麼只有一次賦值的機會,而且只能在構造方法中顯式賦值,然後才能使用。
final 修飾的局部變量可以只聲明不賦值,然後再進行一次性的賦值。
final 一般用於修飾那些通用性的功能、實現方式或取值不能隨意被改變的數據,以避免被誤用,例如實現數學三角方法、冪運算等功能的方法,以及數學常量π=3.141593、e =2.71828 等。
事實上,為確保終態性,提供了上述方法和常量的java.lang.Math 類也已被定義為final 的。
需要注意的是,如果將引用類型(任何類的類型)的變量標記為final,那麼該變量不能指向任何其它對象。但可以改變對象的內容,因為只有引用本身是final 的。
如果變量被標記為final,其結果是使它成為常數。想改變final 變量的值會導致一個編譯錯誤。下面是一個正確定義final 變量的例子:
public final int MAX_ARRAY_SIZE = 25; // 常量名一般大寫
常量因為有final 修飾,所以不能被繼承。
請看下面的代碼:
public final class Demo{ public static final int TOTAL_NUMBER = 5; public int id; public Demo() { // 非法,對final變量TOTAL_NUMBER進行二次賦值了// 因為++TOTAL_NUMBER相當於TOTAL_NUMBER=TOTAL_NUMBER+1 id = ++TOTAL_NUMBER; } public static void main(String[] args) { final Demo t = new Demo(); final int i = 10; final int j; j = 20; j = 30; // 非法,對final變量進行二次賦值}}
final 也可以用來修飾類(放在class 關鍵字前面),阻止該類再派生出子類,例如Java.lang.String 就是一個final 類。這樣做是出於安全原因,因為要保證一旦有字符串的引用,就必須是類String 的字符串,而不是某個其它類的字符串(String 類可能被惡意繼承並篡改)。
方法也可以被final 修飾,被final 修飾的方法不能被覆蓋;變量也可以被final 修飾,被final 修飾的變量在創建對像以後就不允許改變它們的值了。一旦將一個類聲明為final,那麼該類包含的方法也將被隱式地聲明為final,但是變量不是。
被final 修飾的方法為靜態綁定,不會產生多態(動態綁定),程序在運行時不需要再檢索方法表,能夠提高代碼的執行效率。在Java中,被static 或private 修飾的方法會被隱式的聲明為final,因為動態綁定沒有意義。
由於動態綁定會消耗資源並且很多時候沒有必要,所以有一些程序員認為:除非有足夠的理由使用多態性,否則應該將所有的方法都用final 修飾。
這樣的認識未免有些偏激,因為JVM 中的即時編譯器能夠實時監控程序的運行信息,可以準確的知道類之間的繼承關係。如果一個方法沒有被覆蓋並且很短,編譯器就能夠對它進行優化處理,這個過程為稱為內聯(inlining)。例如,內聯調用e.getName() 將被替換為訪問e.name 變量。這是一項很有意義的改進,這是由於CPU在處理調用方法的指令時,使用的分支轉移會擾亂預取指令的策略,所以,這被視為不受歡迎的。然而,如果getName() 在另外一個類中被覆蓋,那麼編譯器就無法知道覆蓋的代碼將會做什麼操作,因此也就不能對它進行內聯處理了。