回顧這兩個關鍵字前,先考慮一個問題:
static變量存儲在jvm中的位置,或者說static變量是如何被加載的?
jvm會把類的靜态方法和靜态變量在類加載的過程中讀入方法區(method area),相當于常駐記憶體,
如果一個方法或者變量聲明為static,可以節約記憶體,不必要為每個對象執行個體化的時候配置設定記憶體。
根據程式上下文環境,java關鍵字final有“這是無法改變的”或者“終态的”含義,
它可以修飾非抽象類、非抽象類成員方法和變量。
final類不能被繼承,沒有子類,final類中的方法預設是final的;
final方法不能被子類的方法覆寫,但可以被繼承
final成員變量表示常量,隻能被指派一次,指派後值不再改變;
注意,final不能用于修飾構造方法;
父類的private方法是不能被子類方法通路和覆寫的,是以private類型的方法預設是final類型的,也就是說編譯器對final方法和private方法做的優化是一樣的。
(1)final類
final類不能被繼承,是以final類的成員方法沒有機會被覆寫,預設都是final的。在設計類時候,如果這個類不需要有子類,類的實作細節不允許改變,并且确信這個類不會載被擴充,那麼就設計為final類。
典型的如jdk中的string,stringbuffer和stringbuilder。
(2)final方法
如果一個類不允許其子類覆寫某個方法,則可以把這個方法聲明為final方法。
使用final方法的原因有二:
把方法鎖定,防止任何繼承類修改它的意義和實作;
高效。編譯器在遇到調用final方法時候會轉入内嵌機制,大大提高執行效率。
注意和private方法的區分,private方法不可以在子類執行個體中通路,final可以在子類執行個體中直接調用,但是不能覆寫修改。
(3)final變量(常量)
用final修飾的成員變量表示常量,值一旦給定就無法改變!
final修飾的變量有三種:靜态變量、執行個體變量和局部變量,分别表示三種類型的常量。
(4)final參數
當函數參數為final類型時,你可以讀取使用該參數,但是無法改變該參數的值。
1
2
3
4
5
6
7
8
9
10
<code>public</code> <code>class</code> <code>finalparam {</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args){</code>
<code> </code><code>finalparam test=</code><code>new</code> <code>finalparam();</code>
<code> </code><code>test.change(</code><code>10</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>public</code> <code>void</code> <code>change(</code><code>final</code> <code>int</code> <code>i){</code>
<code>// i++; 編譯報錯</code>
<code> </code><code>system.out.print(i);</code>
<code>}</code>
static表示“全局”或者“靜态”的意思,用來修飾成員變量和成員方法,也可以形成靜态static代碼塊。
被static修飾的成員變量和成員方法獨立于該類的任何對象。它不依賴類特定的執行個體,被類的所有執行個體共享。
隻要這個類被加載,java虛拟機就能根據類名在運作時資料區的方法區内定找到他們。是以,static對象可以在它的任何對象建立之前通路,無需引用任何對象。
按照是否靜态的對類成員變量進行分類可分兩種:一種是被static修飾的變量,叫靜态變量或類變量;另一種是沒有被static修飾的變量,叫執行個體變量。兩者的差別是:
對于靜态變量在記憶體中隻有一個拷貝(節省記憶體),jvm隻為靜态配置設定一次記憶體,在加載類的過程中完成靜态變量的記憶體配置設定,可用類名直接通路(友善),當然也可以通過對象來通路(但是這是不推薦的)。
對于執行個體變量,沒建立一個執行個體,就會為執行個體變量配置設定一次記憶體,執行個體變量可以在記憶體中有多個拷貝,互不影響(靈活)。
靜态方法可以直接通過類名調用,任何的執行個體也都可以調用,是以靜态方法中不能用this和super關鍵字,不能直接通路所屬類的執行個體變量和執行個體方法(就是不帶static的成員變量和成員成員方法),隻能通路所屬類的靜态成員變量和成員方法。因為執行個體成員與特定的對象關聯。
因為static方法獨立于任何執行個體,是以static方法必須被實作,而不能是抽象的abstract。
static代碼塊也叫靜态代碼塊,是在類中獨立于類成員的static語句塊,可以有多個,位置可以随便放,它不在任何的方法體内,jvm加載類時會執行這些靜态的代碼塊。
如果static代碼塊有多個,jvm将按照它們在類中出現的先後順序依次執行它們,每個代碼塊隻會被執行一次。
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<code>public</code> <code>class</code> <code>staticarea {</code>
<code> </code><code>private</code> <code>static</code> <code>int</code> <code>a;</code>
<code> </code><code>private</code> <code>int</code> <code>b;</code>
<code> </code><code>static</code> <code>{</code>
<code> </code><code>staticarea.a = </code><code>1</code><code>;</code>
<code> </code><code>system.out.println(a);</code>
<code> </code><code>staticarea temp = </code><code>new</code> <code>staticarea();</code>
<code> </code><code>temp.f();</code>
<code> </code><code>temp.b = </code><code>1000</code><code>;</code>
<code> </code><code>system.out.println(temp.b);</code>
<code> </code><code>staticarea.a = </code><code>2</code><code>;</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args) {</code>
<code> </code><code>staticarea.a = </code><code>3</code><code>;</code>
<code> </code><code>public</code> <code>void</code> <code>f() {</code>
<code> </code><code>system.out.println(</code><code>"執行執行個體中方法"</code><code>);</code>
輸出:
1
執行執行個體中方法
1000
2
3
static final用來修飾成員變量和成員方法,可簡單了解為“全局常量”,
對于變量,表示一旦給值就不可修改,并且通過類名可以通路。
對于方法,表示不可覆寫,并且可以通過類名直接通路。
以下代碼: static int foo() { int a = somevaluea(); int b = somevalueb(); return a + b; // 這裡通路局部變量 } 與帶final的版本, final int a = somevaluea(); final int b = somevalueb(); 效果一模一樣,由javac編譯得到的位元組碼會是這樣: invokestatic somevaluea:()i istore_0 // 設定a的值 invokestatic somevalueb:()i istore_1 // 設定b的值 iload_0 // 讀取a的值 iload_1 // 讀取b的值 iadd ireturn 位元組碼裡沒有任何東西能展現出局部變量的final與否,class檔案裡除位元組碼(code屬性)外的輔助資料結構也沒有記錄任何展現final的資訊。既然帶不帶final的局部變量在編譯到class檔案後都一樣了,其通路效率必然一樣高,jvm不可能有辦法知道什麼局部變量原本是用final修飾來聲明的。 但有一個例外,那就是聲明的“局部變量”并不是一個變量,而是編譯時常量的情況: static int foo2() { final int a = 2; // 聲明常量a final int b = 3; // 聲明常量b return a + b; // 常量表達式 這樣的話實際上a和b都不是變量,而是編譯時常量,在java語言規範裡稱為constant variable。 chapter 4. types, values, and variables 其通路會按照java語言對常量表達式的規定而做常量折疊。 chapter 15. expressions 實際效果跟這樣的代碼一樣: static int foo3() { return 5; 由javac編譯得到對應的位元組碼會是: iconst_5 // 常量折疊了,沒有“通路局部變量” 而這種情況如果去掉final修飾,那麼a和b就會被看作普通的局部變量而不是常量表達式,在位元組碼層面上的效果會不一樣 static int foo4() { int a = 2; int b = 3; return a + b; 就會編譯為: iconst_2 iconst_3 但其實這種層面上的差異隻對比較簡易的jvm影響較大,因為這樣的vm對解釋器的依賴較大,原本class檔案裡的位元組碼是怎樣的它就怎麼執行;對高性能的jvm(例如hotspot、j9等)則沒啥影響。這種程度的差異在經過好的jit編譯器處理後又會被消除掉,上例中無論是 foo3() 還是 foo4() 經過jit編譯都一樣能被折疊為常量5。