Java static與final詳細講解

 

目錄

可修飾部分

一、static

1、static修飾變量:

2、static修飾方法:

3、static和final一塊用

4、static靜態代碼塊

二、final

1、final修飾類:

 2、final修飾方法:

 3、 final修飾變量:

 4、對於final類型成員變量,

 5、final修飾方法參數


可修飾部分

static:成員變量、方法、代碼塊(靜態代碼塊)、內部類(靜態內部類)
final: 類、成員變量、方法、局部變量

 

一、static

       當設計某個class時,其實就是在描述其外觀長相以及行爲舉措。除非以new 來產生對象,否則並不存在任何實質對象。產生對象之際,存儲空間纔會分配出來,其函數纔可供外界使用。

但是有兩種情況上述方式無法解決。

       第一種:你希望不論產生了多少對象或是不存在任何對象的情形下,那些特定的數據的存儲空間都只有一份。

       第二種:你希望某個函數不要和class object綁在一起,通過static 關鍵字可以處理這兩種情況。

簡言之,static的主要作用是:方便在沒有創建對象的情況下來進行調用(方法/變量)。


按照是否靜態的對類成員變量進行分類可分兩種:

       一種是被static修飾的變量,叫靜態變量或類變量;

       另一種是沒有被static修飾的變量,叫實例變量。

兩者的區別是:
對於靜態變量在內存中只有一個拷貝(節省內存),JVM只爲靜態分配一次內存,在加載類的過程中完成靜態變量的內存分配,可用類名直接訪問(方便),當然也可以通過對象來訪問(但是這是不推薦的)。
對於實例變量,每創建一個實例,就會爲實例變量分配一次內存,實例變量可以在內存中有多個拷貝,互不影響(靈活)
 

 static是靜態修飾關鍵字,可以修飾變量和程序塊以及類方法

       當定義一個static的變量的時候jvm會將將其分配在內存堆上,所有程序對它的引用都會指向這一個地址而不會重新分配內存;

       當修飾一個程序塊的時候(也就是直接將代碼寫在static{...}中)時候,虛擬機就會優先加載靜態塊中代碼,這主要用於系統初始化;

       當修飾一個類方法時候你就可以直接通過類來調用而不需要新建對象。

     1、static修飾變量:

        無論一個類生成了多少個對象,所有這個對象共用唯一一份靜態的成員變量;一個對象對該靜態成員變量進行了修改,其他對象的該靜態成員變量的值也會隨之發生變化。如果一個成員變量是static的,那麼我們可以通過類名.成員變量名的方式來使用它(推薦使用這種方式)。

       static變量也稱作靜態變量,靜態變量和非靜態變量的區別是:

       靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。static成員變量的初始化順序按照定義的順序進行初始化。
 

      2、static修飾方法:

static修飾的方法叫做靜態方法。對於靜態方法來說,可以使用類名.方法名的方式來訪問。

       static方法可以直接通過類名調用,任何的實例也都可以調用,因此static方法中不能用this和super關鍵字,不能直接訪問所屬類的實例變量和實例方法(就是不帶static的成員變量和成員成員方法),只能訪問所屬類的靜態成員變量和成員方法。因爲static方法獨立於任何實例,因此static方法必須被實現,而不能是抽象的abstract。

       static方法只能訪問static的變量和方法,因爲非static的變量和方法是需要創建一個對象才能訪問的,而static的變量/方法不需要創建任何對象。

 

********

        static的數據或方法,屬於整個類的而不是屬於某個對象的,是不會和類的任何對象實例聯繫到一起。所以子類和父類之間可以存在同名的static方法名,這裏不涉及重載。所以不能把任何方法體內的變量聲明爲static,例如:

fun() {

   static int i=0; //非法。

}

其實理解static是隻有一個存儲地方,而使用時直接使用,不需要創建對象,就能明白以上的注意事項。

 

另外,一般的類是沒有static的,只有內部類可以加上static來表示嵌套類。

public class StaticTest {
    public static void main(String[] args) {
        MyStatic.output();
    }
}
class MyStatic{
    public static void output(){
        System.out.println("output");
    }
}


             3、靜態方法只能繼承,不能重寫(Override)。

                              

輸出結果:父類

3、static和final一塊用

    static final用來修飾成員變量和成員方法,可簡單理解爲“全局常量”!
    對於變量,表示一旦給值就不可修改,並且通過類名可以訪問。
    對於方法,表示不可覆蓋,並且可以通過類名直接訪問。

    特別要注意一個問題:
    對於被static和final修飾過的實例常量,實例本身不能再改變了,但對於一些容器類型(比如,ArrayList、HashMap)的實例變量,不可以改變容器變量本身,但可以修改容器中存放的對象,這一點在編程中用到很多。

最後我們需要注意的一點是,同時使用static和final修飾的成員在內存中只佔據一段不能改變的存儲空間。

看個例子:

import java.io.*;
import java.util.ArrayList; 
class test1  
{
	public static void main (String[] args) throws java.lang.Exception
	{
	     new TestStaticFinal().test();  
	    
		
	}
}
 	class TestStaticFinal {  
    private static final String strStaticFinalVar = "aaa";  
    private static String strStaticVar = null;  
    private final String strFinalVar= null;  
    private static final int intStaticFinalVar = 0;  
    private static final Integer integerStaticFinalVar = new Integer(8);  
    private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>();  
 
     void test() {  
        System.out.println("-------------值處理前----------");  
        System.out.println("strStaticFinalVar=" + strStaticFinalVar + "");  
        System.out.println("strStaticVar=" + strStaticVar + "");  
        System.out.println("strFinalVar=" + strFinalVar + "");  
        System.out.println("intStaticFinalVar=" + intStaticFinalVar + "");  
        System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "");  
        System.out.println("alStaticFinalVar=" + alStaticFinalVar + "");  
 
 
        //strStaticFinalVar="哈哈哈哈";//錯誤,final表示終態,不可以改變變量本身.  
        strStaticVar = "哈哈哈哈";     //正確,static表示類變量,值可以改變.  
        //strFinalVar="呵呵呵呵";      //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。  
        //intStaticFinalVar=2;         //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。  
        //integerStaticFinalVar=new Integer(8);        //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。  
        alStaticFinalVar.add("aaa");   //正確,容器變量本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。  
        alStaticFinalVar.add("bbb");   //正確,容器變量本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。  
 
        System.out.println("-------------值處理後----------");  
        System.out.println("strStaticFinalVar=" + strStaticFinalVar + "");  
        System.out.println("strStaticVar=" + strStaticVar + "");  
        System.out.println("strFinalVar=" + strFinalVar + "");  
        System.out.println("intStaticFinalVar=" + intStaticFinalVar + "");  
        System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "");  
        System.out.println("alStaticFinalVar=" + alStaticFinalVar + "");  
    }
}

  

 運行結果

-------------值處理前----------
strStaticFinalVar=aaa
strStaticVar=null
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[]
-------------值處理後----------
strStaticFinalVar=aaa
strStaticVar=哈哈哈哈
strFinalVar=null
intStaticFinalVar=0
integerStaticFinalVar=8
alStaticFinalVar=[aaa, bbb]

 

package com.cy.oauth.testData;
 
import java.util.*;
 
public class userList {
 
    //演示數據
    private final static List<Map<String, Object>> userList = new ArrayList<>();
 
    static {
        Map<String, Object> user = new HashMap<>();
        user.put("name", "123");
        user.put("uscc", "1123456");
        user.put("phone", "13316512676");
        user.put("password", "123");
        user.put("smscode", "1");
        user.put("username", "12312");
        user.put("idcard", "1231231");
        user.put("mail", "45612");
        user.put("userid", "098d4821833b442d83ff4478ed1ee0f9");
        user.put("status", "0");
        userList.add(user);
    }
 
 
    public List<Map<String, Object>> getUserList() {
        return userList;
    }
 
    public void setUserList(Map<String, Object> userMap) {
        this.userList.add(userMap);
    }
 
}


特別注意,靜態塊要放在聲明的變量下面
 

4、static靜態代碼塊

static關鍵字還有一個比較關鍵的作用就是 用來形成靜態代碼塊以優化程序性能。static塊可以置於類中的任何地方,類中可以有多個static塊。在類初次被加載的時候,會按照static塊的順序來執行每個static塊,並且只會執行一次。
爲什麼說靜態代碼塊可以優化效率?看下面兩段代碼:

class Person{
    private Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

上面這段代碼每次調用isBornBoomer這個方法的時候都會生成startDate 和 endDate 這兩個對象,造成空間的浪費。
換成下面這種寫法,稍微的會優化一點效率。

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

       static靜態代碼塊。靜態代碼塊的作用也是完成一些初始化工作。首先執行靜態塊,然後執行構造方法。靜態代碼塊在類被加載的時候執行,而構造方法是在生成對象的時候執行;要調用某個類來生成對象,首先需要將類加載到Java虛擬機上(JVM),然後由JVM加載這個類來生成對象。

        類的靜態代碼塊只會執行一次,是在類被加載的時候執行的,因爲每個類只會加載一個,所以靜態代碼塊也只會被執行一次;而構造方法則不然,每次生成一個對象的時候都會調用類的構造方法,所以new一次就會調用構造方法一次。

        如果繼承體系既有構造方法,又有靜態代碼塊,那麼首先執行最頂層的類的靜態代碼塊,一直執行到最底層類的靜態代碼塊,然後再去執行最頂層類的構造方法,一直執行到最底層的構造方法,注意:靜態代碼塊只會執行一次。

public class StaticTest5 {
    public static void main(String[] args) {
        new R();
        new R();
    }
}
class P{
    //static靜態代碼塊
    static {
        System.out.println("static block");
    }
    public P(){
        System.out.println("P constructor");
    }
}
class Q extends P{
    static {
        System.out.println("static block");
    }
    public Q(){
        System.out.println("Q constructor");
    }
}
class R extends Q{
    static {
        System.out.println("static block");
    }
    public R(){
        System.out.println("R constructor");
    }
}


               輸出結果:

static block
static block
static block
P constructor
Q constructor
R constructor
P constructor
Q constructor
R constructor


        不能在靜態方法中訪問非靜態的成員變量:可以在靜態方法中訪問靜態的成員變量。可以在非靜態方法中訪問靜態的成員變量。

        總結:靜態的只能訪問靜態的;非靜態的訪問一切。   

        不能在靜態方法中使用this、super關鍵字。

靜態內部類:
static final 和 final static
兩者沒有區別,但是習慣於將static寫在前面。

那麼static final 和 單純的final有什麼區別呢?
final在一個對象類唯一,static final在多個對象中都唯一;

 

二、final

 

 根據程序上下文環境,Java關鍵字final有“這是無法改變的”或者“終態的”含義,它可以修飾非抽象類、非抽象類成員方法和變量。 

你可能出於兩種理解而需要阻止改變:設計或效率。
    final類不能被繼承,沒有子類,final類中的方法默認是final的。
    final方法不能被子類的方法覆蓋,但可以被繼承。
    final成員變量表示常量,只能被賦值一次,賦值後值不再改變。
    final不能用於修飾構造方法。


  

用final修飾的成員變量表示常量,值一旦給定就無法改變!
    final修飾的變量有三種:靜態變量、實例變量和局部變量,分別表示三種類型的常量。
    從下面的例子中可以看出,一旦給final變量初值後,值就不能再改變了。
    另外,final變量定義的時候,可以先聲明,而不給初值,這中變量也稱爲final空白,無論什麼情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,爲此,一個類中的final數據成員就可以實現依對象而有所不同,卻有保持其恆定不變的特徵。
 

     在Java中聲明屬性、方法和類時,可使用關鍵字final來修飾。

     final變量即爲常量,只能賦值一次;   final方法不能被子類重寫;     final類不能被繼承。

             final關鍵字:final可以修飾屬性、方法、類、成員變量、局部變量

1、final修飾類:

當一個類被final所修飾時,表示該類是一個終態類,即不能被繼承。

                              

 2、final修飾方法:

          當一個方法被final所修飾的時,表示該方法是一個終態方法,即不能被重寫(Override)。

          如果一個類不允許其子類覆蓋某個方法,則可以把這個方法聲明爲final方法。

           使用final方法的原因有二:

                   第一、把方法鎖定,防止任何繼承類修改它的意義和實現。

                   第二、高效。編譯器在遇到調用final方法時候會轉入內嵌inline機制,大大提高執行效率。

 

         注意,類中所有的private方法都被隱含是final的。由於無法取用private方法,則也無法重載之。

                              

 

例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

class WithFinals{

 private final void f(){

  System.out.println("WithFinals.f()");

 }

 private void g(){

  System.out.println("OverridingPrivate.f()");

 }

}

class OverridingPrivate extends WithFinals{

 private final void f(){

  System.out.println("OverridingPrivate.f()");

 }

 private void g(){

  System.out.println("OverridingPrivate.g()");

 }

}

class OverridingPrivate2 extends OverridingPrivate{

 /*

  * 當使用Override註解強制使f()方法覆蓋父類的f()方法時,會報錯

  * 因爲它不知道父類是否有該方法,對於g()方法來說,它只是生成了一個新的方法,

  * 並沒有覆蓋掉父類中的g()方法。

  */

 //@Override

 public final void f(){

  System.out.println("OverridingPrivate2.f()");

 }

 public void g(){

  System.out.println("OverridingPrivate2.g()");

 }

}

public class FinalOverridingIllusion{

 public static void main(String[] args) {

  OverridingPrivate2 op2 = new OverridingPrivate2();

  op2.f();

  op2.g();

   

  // 可以向上轉型

  OverridingPrivate op = op2;

  //! op.f(); // 父類中final方法對子類來說是不可見的

  //! op.g();

  WithFinals wf = op2;

  // wf.f();

  // wf.g();

 }

}

/*output:

OverridingPrivate2.f()

OverridingPrivate2.g()

*/

分析:

覆蓋何時發生:

  1,子類中出現與父類完全一致的方法

  2. 子類可以通過向上轉型爲父類,並調用父類中的那個方法

若父類中某個方法被聲明爲final或者private,那麼這個方法對子類來說是不可見的,就算在子類中創建了與父類一模一樣的方法,這也是一個新的方法,而不是從父類中覆蓋的方法。

 3、 final修飾變量:

                當一個屬性被final所修飾時,表示該屬性不能被改寫。

  •          基礎類型 用fianl修飾後就變成了一個常量,不可以重新賦值。
  •          包裝類型 用final修飾後該變量指向的地址不變,但是該地址的的變量完全可以改變。

            當final修飾一個原生數據類型時,表示該原生數據類型的值不能發生變化(比如說不能從10變爲20);如果final修飾一個引用類型時,表示該引用類型不能再指向其他對象了,但該引用所指向的對象的內容是可以發生變化的。

 

在編寫程序時,我們經常需要說明一個數據是不可變的,我們成爲常量。在java中,用final關鍵字修飾的變量,只能進行一次賦值操作,並且在生存期內不可以改變它的值。更重要的是,final會告訴編譯器,這個數據是不會修改的,那麼編譯器就可能會在編譯時期就對該數據進行替換甚至執行計算,這樣可以對我們的程序起到一點優化。不過在針對基本類型和引用類型時,final關鍵字的效果存在細微差別。我們來看下面的例子:

1 class Value {
 2     int v;
 3     public Value(int v) {
 4         this.v = v;
 5     }
 6 }
 7 
 8 public class FinalTest {
 9     
10     final int f1 = 1;
11     final int f2;
12     public FinalTest() {
13         f2 = 2;
14     }
15 
16     public static void main(String[] args) {
17         final int value1 = 1;
18         // value1 = 4;
19         final double value2;
20         value2 = 2.0;
21         final Value value3 = new Value(1);
22         value3.v = 4;
23     }
24 }

 

上面的例子中,我們先來看一下main方法中的幾個final修飾的數據,在給value1賦初始值之後,我們無法再對value1的值進行修改,final關鍵字起到了常量的作用。從value2我們可以看到,final修飾的變量可以不在聲明時賦值,即可以先聲明,後賦值。value3時一個引用變量,這裏我們可以看到final修飾引用變量時,只是限定了引用變量的引用不可改變,即不能將value3再次引用另一個Value對象,但是引用的對象的值是可以改變的,從內存模型中我們看的更加清晰:

上圖中,final修飾的值用粗線條的邊框表示它的值是不可改變的,我們知道引用變量的值實際上是它所引用的對象的地址,也就是說該地址的值是不可改變的,從而說明了爲什麼引用變量不可以改變引用對象。而實際引用的對象實際上是不受final關鍵字的影響的,所以它的值是可以改變的。

另一方面,我們看到了用final修飾成員變量時的細微差別,因爲final修飾的數據的值是不可改變的,所以我們必須確保在使用前就已經對成員變量賦值了。因此對於final修飾的成員變量,我們有且只有兩個地方可以給它賦值,一個是聲明該成員時賦值,另一個是在構造方法中賦值,在這兩個地方我們必須給它們賦初始值。

最後我們需要注意的一點是,同時使用static和final修飾的成員在內存中只佔據一段不能改變的存儲空間。

            原生數據類型案例:

                           

            圖中的錯誤是無法爲最終變量age分配值

            引用類型案例:

                          

             圖中錯誤是無法爲最終變量address分配值

             該引用所指向的對象的內容是可以發生變化的

                          

 4、對於final類型成員變量,

         其初始化可以在兩個地方,有兩種賦值方式: 

                     a) 在聲明final類型的成員變量時就賦初值

                     b) 在聲明final類型的成員變量時不賦初值,但在類的所有構造方法中都爲其賦上初值。

        這兩個地方只能選其一,要麼在定義時給值,要麼在構造函數中給值,不能同時既在定義時給了值,又在構造函數中給另外的值。

      一旦被初始化便不可改變,這裏不可改變的意思對基本類型來說是其值不可變,而對於對象變量來說其引用不可再變。

     當函數參數爲final類型時,你可以讀取使用該參數,但是無法改變該參數的值。

     另外方法中的內部類在用到方法中的參變量時,此參變也必須聲明爲final纔可使用。

public class FinalTest {
    final int a;
 
    public FinalTest() {
        a = 0;
    }
 
    public FinalTest(int a) {
        this.a = a;
    }
}


             
 5、final修飾方法參數

       前面我們可以看到,如果變量是我們自己創建的,那麼使用final修飾表示我們只會給它賦值一次且不會改變變量的值。那麼如果變量是作爲參數傳入的,我們怎麼保證它的值不會改變呢?這就用到了final的第二種用法,即在我們編寫方法時,可以在參數前面添加final關鍵字,它表示在整個方法中,我們不會(實際上是不能)改變參數的值:

主要分爲兩種情況:第一,用final修飾基本數據類型;第二,用final修飾引用數據類型。

第一種情況,修飾基本數據類型,這時參數的值在方法體內是不能被修改的,即不能被重新賦值。否則編譯就不通過。

第二種情況,修飾引用類型。這時參數變量所引用的對象是不能被改變的。但是對於引用數據類型,如果修改其屬性的話是完全可以的。

所以,final這個關鍵字,想用的話就用基本數據類型,還是很有作用的

public class FinalTest {

    /* ... */

    public void finalFunc(final int i, final Value value) {
        // i = 5; 不能改變i的值
        // v = new Value(); 不能改變v的值
        value.v = 5; // 可以改變引用對象的值
    }
}

例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

class Gizmo{

 int i = 0;

 public void spin(){}

}

public class FinalArguments {

 void with(final Gizmo g){

  //! g = new Gizmo(); // 無法修改final修飾的引用,使它指向另一個對象

  g.i++; // 但可以修改final對象所指向的內容

 }

 void without(Gizmo g){

  g = new Gizmo();

  g.spin();

 }

// int g(final int i){

//  //! i++; //因爲參數i是常量值

// }

 int g(final int i){

  return i + 1;

 }

 public static void main(String[] args) {

  FinalArguments bf = new FinalArguments();

  bf.without(null);

  bf.with(null);

 }

}

分析:

參數被聲明爲final,若是基本參數,那它就是一個常量,不能被修改;若是一個引用變量,那麼它就不能被修改指向另一個對象,但可以修改該引用所指對象的內容。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章