【面試】java什么時候要用static


static關鍵字是在我們編寫代碼和閱讀代碼時碰到的常見的一個關鍵字,在學習java基礎時就學過了,這也是各大公司的面試官喜歡在面試時問到的知識點之一。雖然大概知道是什么,但完整的表達出來還是有點難度,容易遺漏一些地方,所以做一下整理。

在類中,使用 static 修飾符修飾的屬性(成員變量)稱為靜態變量,也可以稱為類變量,常量稱為靜態常量,方法稱為靜態方法或類方法,它們統稱為靜態成員,歸整個類所有。靜態成員不依賴於類的特定實例,被類的所有實例共享,就是說 static 修飾的方法或者變量不需要依賴於對象來進行訪問,只要這個類被加載,Java 虛擬機就可以根據類名找到它們。
調用靜態成員的語法形式如下:

類名.靜態成員

注意:
static 修飾的成員變量和方法,從屬於類。
普通變量和方法從屬於對象。
靜態方法不能調用非靜態成員,編譯會報錯。

靜態變量

類的成員變量可以分為以下兩種:
靜態變量(或稱為類變量),指被 static 修飾的成員變量。
實例變量,指沒有被 static 修飾的成員變量。

靜態變量與實例變量的區別如下:

1)靜態變量
運行時,Java 虛擬機只為靜態變量分配一次內存,在加載類的過程中完成靜態變量的內存分配。
在類的內部,可以在任何方法內直接訪問靜態變量。
在其他類中,可以通過類名訪問該類中的靜態變量。

2)實例變量
每創建一個實例,Java 虛擬機就會為實例變量分配一次內存。
在類的內部,可以在非靜態方法中直接訪問實例變量。
在本類的靜態方法或其他類中則需要通過類的實例對象進行訪問。

靜態變量在類中的作用如下:
靜態變量可以被類的所有實例共享,因此靜態變量可以作為實例之間的共享數據,增加實例之間的交互性。
如果類的所有實例都包含一個相同的常量屬性,則可以把這個屬性定義為靜態常量類型,從而節省內存空間。例如,在類中定義一個靜態常量 PI。
public static double PI = 3.14159256;

例 1
創建一個帶靜態變量的類,然后在 main() 方法中訪問該變量並輸出結果。

public class StaticVar {
    public static String str1 = "Hello";
    public static void main(String[] args) {
        String str2 = "World!";
        // 直接訪問str1
        String accessVar1 = str1+str2;
        System.out.println("第 1 次訪問靜態變量,結果為:"+accessVar1);
        // 通過類名訪問str1
        String accessVar2 = StaticVar.str1+str2;
        System.out.println("第 2 次訪問靜態變量,結果為:"+accessVar2);
        // 通過對象svt1訪問str1
        StaticVar svt1 = new StaticVar();
        svt1.str1 = svt1.str1+str2;
        String accessVar3 = svt1.str1;
        System.out.println("第3次訪向靜態變量,結果為:"+accessVar3);
        // 通過對象svt2訪問str1
        StaticVar svt2 = new StaticVar();
        String accessVar4 = svt2.str1+str2;
        System.out.println("第 4 次訪問靜態變量,結果為:"+accessVar4);
    }
}

運行該程序后的結果如下所示。

第 1 次訪問靜態變量,結果為:HelloWorld!
第 2 次訪問靜態變量,結果為:HelloWorld!
第 3 次訪向靜態變量,結果為:HelloWorld!
第 4 次訪問靜態變量,結果為:HelloWorld!World!

從運行結果可以看出,在類中定義靜態的屬性(成員變量),在 main() 方法中可以直接訪問,也可以通過類名訪問,還可以通過類的實例對象來訪問。

注意:靜態變量是被多個實例所共享的。

靜態方法

與成員變量類似,成員方法也可以分為以下兩種:
靜態方法(或稱為類方法),指被 static 修飾的成員方法。
實例方法,指沒有被 static 修飾的成員方法。

靜態方法與實例方法的區別如下:
靜態方法不需要通過它所屬的類的任何實例就可以被調用,因此在靜態方法中不能使用 this 關鍵字,也不能直接訪問所屬類的實例變量和實例方法,但是可以直接訪問所屬類的靜態變量和靜態方法。另外,和 this 關鍵字一樣,super 關鍵字也與類的特定實例相關,所以在靜態方法中也不能使用 super 關鍵字。
在實例方法中可以直接訪問所屬類的靜態變量、靜態方法、實例變量和實例方法。
例 2
創建一個帶靜態變量的類,添加幾個靜態方法對靜態變量的值進行修改,然后在 main( ) 方法中調用靜態方法並輸出結果。

public class StaticMethod {
    public static int count = 1;    // 定義靜態變量count
    public int method1() {    
        // 實例方法method1
        count++;    // 訪問靜態變量count並賦值
        System.out.println("在靜態方法 method1()中的 count="+count);    // 打印count
        return count;
    }
    public static int method2() {    
        // 靜態方法method2
        count += count;    // 訪問靜態變量count並賦值
        System.out.println("在靜態方法 method2()中的 count="+count);    // 打印count
        return count;
    }
    public static void PrintCount() {    
        // 靜態方法PrintCount
        count += 2;
        System.out.println("在靜態方法 PrintCount()中的 count="+count);    // 打印count
    }
    public static void main(String[] args) {
        StaticMethod sft = new StaticMethod();
        // 通過實例對象調用實例方法
        System.out.println("method1() 方法返回值 intro1="+sft.method1());
        // 直接調用靜態方法
        System.out.println("method2() 方法返回值 intro1="+method2());
        // 通過類名調用靜態方法,打印 count
        StaticMethod.PrintCount();
    }
}

運行該程序后的結果如下所示。

在靜態方法 method1()中的 count=2
method1() 方法返回值 intro1=2
在靜態方法 method2()中的 count=4
method2() 方法返回值 intro1=4
在靜態方法 PrintCount()中的 count=6

在該程序中,靜態變量 count 作為實例之間的共享數據,因此在不同的方法中調用 count,值是不一樣的。從該程序中可以看出,在靜態方法 method1() 和 PrintCount() 中是不可以調用非靜態方法 method1() 的,而在 method1() 方法中可以調用靜態方法 method2() 和 PrintCount()。

在訪問非靜態方法時,需要通過實例對象來訪問,而在訪問靜態方法時,可以直接訪問,也可以通過類名來訪問,還可以通過實例化對象來訪問。

靜態代碼塊

靜態代碼塊指 Java 類中的 static{ } 代碼塊,主要用於初始化類,為類的靜態變量賦初始值,提升程序性能。

靜態代碼塊的特點如下:
靜態代碼塊類似於一個方法,但它不可以存在於任何方法體中。
靜態代碼塊可以置於類中的任何地方,類中可以有多個靜態初始化塊。
Java 虛擬機在加載類時執行靜態代碼塊,所以很多時候會將一些只需要進行一次的初始化操作都放在 static 代碼塊中進行。
如果類中包含多個靜態代碼塊,則 Java 虛擬機將按它們在類中出現的順序依次執行它們,每個靜態代碼塊只會被執行一次。
靜態代碼塊與靜態方法一樣,不能直接訪問類的實例變量和實例方法,而需要通過類的實例對象來訪問。
例 3
編寫一個 Java 類,在類中定義一個靜態變量,然后使用靜態代碼塊修改靜態變量的值。最后在 main() 方法中進行測試和輸出。

public class StaticCode {
    public static int count = 0;
    {
        count++;
        System.out.println("非靜態代碼塊 count=" + count);
    }
    static {
        count++;
        System.out.println("靜態代碼塊1 count=" + count);
    }
    static {
        count++;
        System.out.println("靜態代碼塊2 count=" + count);
    }

    public static void main(String[] args) {
        System.out.println("*************** StaticCode1 執行 ***************");
        StaticCode sct1 = new StaticCode();
        System.out.println("*************** StaticCode2 執行 ***************");
        StaticCode sct2 = new StaticCode();
    }
}

如上述示例,為了說明靜態代碼塊只被執行一次,特地添加了非靜態代碼塊作為對比,並在主方法中創建了兩個類的實例對象。上述示例的執行結果為:

靜態代碼塊1 count=1
靜態代碼塊2 count=2
*************** StaticCode1 執行 ***************
非靜態代碼塊 count=3
*************** StaticCode2 執行 ***************
非靜態代碼塊 count=4

上述代碼中 { } 代碼塊為非靜態代碼塊,非靜態代碼塊是在創建對象時自動執行的代碼,不創建對象不執行該類的非靜態代碼塊。代碼域中定義的變量都是局部的,只有域中的代碼可以調用。

常見誤區

(以下轉自: https://www.cnblogs.com/dolphin0520/p/3799052.html
1.static關鍵字會改變類中成員的訪問權限嗎?

  有些初學的朋友會將java中的static與C/C++中的static關鍵字的功能混淆了。在這里只需要記住一點:與C/C++中的static不同,Java中的static關鍵字不會影響到變量或者方法的作用域。在Java中能夠影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。看下面的例子就明白了:

  提示錯誤"Person.age 不可視",這說明static關鍵字並不會改變變量和方法的訪問權限。

2.能通過this訪問靜態成員變量嗎?

  雖然對於靜態方法來說沒有this,那么在非靜態方法中能夠通過this訪問靜態成員變量嗎?先看下面的一個例子,這段代碼輸出的結果是什么?

public class Main {  
   static int value = 33;

   public static void main(String[] args) throws Exception{
       new Main().printValue();
   }

   private void printValue(){
       int value = 3;
       System.out.println(this.value);
   }
}

\\輸出結果
33

 這里面主要考察隊this和static的理解。this代表什么?this代表當前對象,那么通過new Main()來調用printValue的話,當前對象就是通過new Main()生成的對象。而static變量是被對象所享有的,因此在printValue中的this.value的值毫無疑問是33。在printValue方法內部的value是局部變量,根本不可能與this關聯,所以輸出結果是33。在這里永遠要記住一點:靜態成員變量雖然獨立於對象,但是不代表不可以通過對象去訪問,所有的靜態方法和靜態變量都可以通過對象訪問(只要訪問權限足夠)。

3.static能作用於局部變量么?

  在C/C++中static是可以作用域局部變量的,但是在Java中切記:static是不允許用來修飾局部變量。不要問為什么,這是Java語法的規定。

  具體原因可以參考這篇博文的討論:http://www.debugease.com/j2se/178932.html

常見面試題

1.下面這段代碼的輸出結果是什么?

public class Test extends Base{
    static{
        System.out.println("test static");
    }    
    public Test(){
        System.out.println("test constructor");
    }   
    public static void main(String[] args) {
        new Test();
    }
}
 
class Base    
    static{
        System.out.println("base static");
    }
    public Base(){
        System.out.println("base constructor");
    }
}

輸出結果

base static
test static
base constructor
test constructor

至於為什么是這個結果,我們先不討論,先來想一下這段代碼具體的執行過程,在執行開始,先要尋找到main方法,因為main方法是程序的入口,但是在執行main方法之前,必須先加載Test類,而在加載Test類的時候發現Test類繼承自Base類,因此會轉去先加載Base類,在加載Base類的時候,發現有static塊,便執行了static塊。在Base類加載完成之后,便繼續加載Test類,然后發現Test類中也有static塊,便執行static塊。在加載完所需的類之后,便開始執行main方法。在main方法中執行new Test()的時候會先調用父類的構造器,然后再調用自身的構造器。因此,便出現了上面的輸出結果。

2.這段代碼的輸出結果是什么?

public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }
     
    public Test() {
        System.out.println("test constructor");
    }
     
    public static void main(String[] args) {
        new MyClass();
    }
}
 
class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}
 
 
class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }
     
    public MyClass() {
        System.out.println("myclass constructor");
    }
}

輸出結果

test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor

類似地,我們還是來想一下這段代碼的具體執行過程。首先加載Test類,因此會執行Test類中的static塊。接着執行new MyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時候,發現MyClass類繼承自Test類,但是由於Test類已經被加載了,所以只需要加載MyClass類,那么就會執行MyClass類的中的static塊。在加載完之后,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會先加載Person類並執行Person類中的static塊,接着執行父類的構造器,完成了父類的初始化,然后就來初始化自身了,因此會接着執行MyClass中的Person person = new Person(),最后執行MyClass的構造器。

3.這段代碼的輸出結果是什么?

public class Test {
     
    static{
        System.out.println("test static 1");
    }
    public static void main(String[] args) {
         
    }
     
    static{
        System.out.println("test static 2");
    }
}

輸出結果

test static 1
test static 2

 雖然在main方法中沒有任何語句,但是還是會輸出,原因上面已經講述過了。另外,static塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),並且執行是按照static塊的順序執行的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM