天天看點

了解Java中的final和static關鍵字

回顧這兩個關鍵字前,先考慮一個問題:

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。