1. 程式人生 > 程式設計 >詳解JAVA中static的作用

詳解JAVA中static的作用

1、深度總結

  引用一位網友的話,說的非常好,如果別人問你static的作用;如果你說靜態修飾 類的屬性 和 類的方法 別人認為你是合格的;如果是說 可以構成 靜態程式碼塊,那別人認為你還可以; 如果你說可以構成 靜態內部類, 那別人認為你不錯;如果你說了靜態導包,那別人認為你很OK;

  那我們就先在這幾方面一一對static進行總結;然後說一些模糊的地方,以及一些面試中容易問道的地方;

1)static方法

  static方法一般稱作靜態方法,由於靜態方法不依賴於任何物件就可以進行訪問,因此對於靜態方法來說,是沒有this的,因為它不依附於任何物件,既然都沒有物件,就談不上this了。並且由於這個特性,在靜態方法中不能訪問類的非靜態成員變數和非靜態成員方法,因為非靜態成員方法/變數都是必須依賴具體的物件才能夠被呼叫。

  但是要注意的是,雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變數,但是在非靜態成員方法中是可以訪問靜態成員方法/變數的。舉個簡單的例子:

詳解JAVA中static的作用

在上面的程式碼中,由於print2方法是獨立於物件存在的,可以直接用過類名呼叫。假如說可以在靜態方法中訪問非靜態方法/變數的話,那麼如果在main方法中有下面一條語句:

  MyObject.print2();

  此時物件都沒有,str2根本就不存在,所以就會產生矛盾了。同樣對於方法也是一樣,由於你無法預知在print1方法中是否訪問了非靜態成員變數,所以也禁止在靜態成員方法中訪問非靜態成員方法。

  而對於非靜態成員方法,它訪問靜態成員方法/變數顯然是毫無限制的。

  因此,如果說想在不建立物件的情況下呼叫某個方法,就可以將這個方法設定為static。我們最常見的static方法就是main方法,至於為什麼main方法必須是static的,現在就很清楚了。因為程式在執行main方法的時候沒有建立任何物件,因此只有通過類名來訪問。

2)static變數

  static變數也稱作靜態變數,靜態變數和非靜態變數的區別是:靜態變數被所有的物件所共享,在記憶體中只有一個副本【存放在方法區】,它當且僅當在類初次載入時會被初始化【加final和不加final的static變數初始化的位置不一樣】。而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響。

  static成員變數的初始化順序按照定義的順序進行初始化。

3)static程式碼塊

  static關鍵字還有一個比較關鍵的作用就是 用來形成靜態程式碼塊以優化程式效能。static塊可以置於類中的任何地方,類中可以有多個static塊。在類初次被載入的時候,會按照static塊的順序來執行每個static塊,並且只會執行一次【根據class載入原理 每個類載入一次 使用雙親委託載入】。

  初始化的順序 靜態程式碼塊 > 構造程式碼塊 > 建構函式

public class Client {
  {//構造程式碼塊
    System.out.println("執行構造程式碼塊");
  }
}

為什麼說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是用來這個人是否是1946-1964年出生的,而每次isBornBoomer被呼叫的時候,都會生成startDate和birthDate兩個物件,造成了空間浪費,如果改成這樣效率會更好,其實就是利用了靜態程式碼塊在記憶體中值載入一次的機制:

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程式碼塊中進行。

4)靜態內部類

這個地方不單獨寫靜態內部類,通過和普通的內部類對比來加深對靜態內部類的理解:

為何要用內部類?

1. 內部類一般只為其外部類使用;【供外部類使用說的很好 舉例 hashmap集合中 有一個內部類 Entry 就是 轉為為 hashmap 儲存來使用】

2. 內部類提供了某種進入外部類的窗戶,內部類存在外部類的引用,所以內部類可以直接訪問外部類的屬性;

3. 也是最吸引人的原因,每個內部類都能獨立地繼承一個介面,而無論外部類是否已經繼承了某個介面。因此,內部類使多重繼承的解決方案變得更加完整。

定義在一個類內部的類叫內部類,包含內部類的類稱為外部類。內部類可以宣告public、protected、private等訪問限制,可以宣告 為abstract的供其他內部類或外部類繼承與擴充套件,或者宣告為static、final的,也可以實現特定的介面。

外部類按常規的類訪問方式(以物件的方式)使用內部 類,唯一的差別是外部類可以訪問內部類的所有方法與屬性,包括私有方法與屬性,外部類訪問內部類,需要建立物件訪問;有一點需要注意,內部類不能訪問外部類所在的區域性變數,只能訪問final修飾的區域性變數。

舉例: 在方法中 定義內部類 然後內部類 呼叫方法的的入參 則 入參必須是 final 修飾

在方法內定義內部類時,如果內部類呼叫了方法中的變數,那麼該變數必須申明為final型別,百思不得其解,後來想到應該是生命週期的原因,因為方法內定義的變數是區域性變數,離開該方法,變數就失去了作用,也就會自動被消除,而內部類卻不會離開它所在方法就失去作用,它有更廣的生命週期,下面通過一個例項加以說明:

詳解JAVA中static的作用

(1)建立例項

OutClass.InnerClassobj=outClassInstance.newInnerClass(); //注意是外部類例項.new,內部類

AAA.StaticInner in = newAAA.StaticInner();//注意是外部類本身,靜態內部類

(2)內部類中的this

內部類中的this與其他類一樣是指的本身。建立內部類物件時,它會與創造它的外圍物件有了某種聯絡,於是能訪問外圍類的所有成員,不需任何特殊條件,可理解為內部類連結到外部類。用外部類建立內部類物件時,此內部類物件會祕密的捕獲一個指向外部類的引用,於是,可以通過這個引用來訪問外圍類的成員。

(3)外部類訪問內部類

內部類類似外部類的屬性,因此訪問內部類物件時總是需要一個建立好的外部類物件。外部類物件通過‘外部類名.this.xxx'的形式訪問內部類的屬性與方法。如:

System.out.println("Print in inner Outer.index=" + pouter.this.index);

System.out.println("Print in inner Inner.index=" + this.index);

(4)內部類向上轉型

內部類也可以和普通類一樣擁有向上轉型的特性。將內部類向上轉型為基型別,尤其是介面時,內部類就有了用武之地。如果內部類是private的,只可以被它的外部類問,從而完全隱藏實現的細節。

(5)方法內的類

方法內建立的類(注意方法中也能定義類),不能加訪問修飾符。另外,方法內部的類也不是在呼叫方法時才會建立的,它們一樣也被事先編譯了。

(6)靜態內部類

定義靜態內部類:在定義內部類的時候,可以在其前面加上一個許可權修飾符static。此時這個內部類就變為了靜態內部類。

通常稱為巢狀類,當內部類是static時,意味著:

[1]要建立巢狀類的物件,並不需要其外圍類的物件;

[2]不能從巢狀類的物件中訪問非靜態的外圍類物件(不能夠從靜態內部類的物件中訪問外部類的非靜態成員);

嵌 套類與普通的內部類還有一個區別:普通內部類的欄位與方法,只能放在類的外部層次上,所以普通的內部類不能有static資料和static欄位,也不能包含巢狀類。但是在巢狀類裡可以包含所有這些東西。也就是說,在非靜態內部類中不可以宣告靜態成員,只有將某個內部類修飾為靜態類,然後才能夠在這 個類中定義靜態的成員變數與成員方法。

另外,在建立靜態內部類時不需要將靜態內部類的例項繫結在外部類的例項上。普通非靜態內部類的 物件是依附在外部類物件之中的,要在一個外部類中定義一個靜態的內部類,不需要利用關鍵字new來建立內部類的例項。靜態類和方法只屬於類本身,並不屬於 該類的物件,更不屬於其他外部類的物件。

(7)內部類識別符號

每個類會產生一個.class檔案,檔名即為類名。同樣,內部類也會產生這麼一個.class檔案,但是它的名稱卻不是內部類的類名,而是有著嚴格的限制:外圍類的名字,加上$,再加上內部類名字。

程式碼具體:

public class OutClassTest {
  static int a;
  int b;

  public static void test() {
    System.out.println("outer class static function");
  }

  public static void main(String[] args) {
    // new一個外部類
    OutClassTest oc1 = new OutClassTest();
    // 通過外部類的物件new一個非靜態的內部類
    OutClassTest.InnerClass no_static_inner = oc1.new InnerClass();
    // 呼叫非靜態內部類的方法
    System.out.println(no_static_inner.getKey());

    // 呼叫靜態內部類的靜態變數
    System.out.println(OutClassTest.InnerStaticClass.static_value);
    // 不依賴於外部類例項,直接例項化內部靜態類
    OutClassTest.InnerStaticClass inner = new OutClassTest.InnerStaticClass();
    // 呼叫靜態內部類的非靜態方法
    System.out.println(inner.getValue());
    // 呼叫內部靜態類的靜態方法
    System.out.println(OutClassTest.InnerStaticClass.getMessage());
  }

  private class InnerClass {
    // 只有在靜態內部類中才能夠宣告或定義靜態成員
    // private static String tt = "0";
    private int flag = 0;

    public InnerClass() {
      // 三.非靜態內部類的非靜態成員可以訪問外部類的非靜態變數和靜態變數
      System.out.println("InnerClass create a:" + a);
      System.out.println("InnerClass create b:" + b);
      System.out.println("InnerClass create flag:" + flag);
      //
      System.out.println("InnerClass call outer static function");
      // 呼叫外部類的靜態方法
      test();
    }

    public String getKey() {
      return "no-static-inner";
    }
  }

  private static class InnerStaticClass {
    // 靜態內部類可以有靜態成員,而非靜態內部類則不能有靜態成員。
    private static String static_value = "0";

    private int flag = 0;

    public InnerStaticClass() {
      System.out.println("InnerClass create a:" + a);
      // 靜態內部類不能夠訪問外部類的非靜態成員
      // System.out.println("InnerClass create b:" + b);
      System.out.println("InnerStaticClass flag is " + flag);
      System.out.println("InnerStaticClass tt is " + static_value);
    }

    public int getValue() {
      // 靜態內部類訪問外部類的靜態方法
      test();
      return 1;
    }

    public static String getMessage() {
      return "static-inner";
    }
  }

  public OutClassTest() {
    // new一個非靜態的內部類
    InnerClass ic = new InnerClass();
    System.out.println("OuterClass create");
  }

}

4)靜態導包

靜態導包就是java包的靜態匯入,用import static代替import靜態匯入包是JDK1.5中的新特性。

一般我們匯入一個類都用 import com…..ClassName;而靜態匯入是這樣:import static com…..ClassName.*;這裡的多了個static,還有就是類名ClassName後面多了個.* ,意思是匯入這個類裡的靜態方法。當然,也可以只匯入某個靜態方法,只要把 .* 換成靜態方法名就行了。然後在這個類中,就可以直接用方法名呼叫靜態方法,而不必用ClassName.方法名 的方式來呼叫。

好處:這種方法的好處就是可以簡化一些操作,例如列印操作System.out.println(…);就可以將其寫入一個靜態方法print(…),在使用時直接print(…)就可以了。但是這種方法建議在有很多重複呼叫的時候使用,如果僅有一到兩次呼叫,不如直接寫來的方便

example:

在Java 5中,import語句得到了增強,以便提供甚至更加強大的減少擊鍵次數功能,雖然一些人爭議說這是以可讀性為代價的。這種新的特性成為靜態匯入。當你想使用static成員時,可以使用靜態匯入(在API中的類和你自己的類上,都可以使用該特性)。下面是靜態匯入前後的程式碼例項:

在靜態匯入之前:

public class TestStatic {

public static void main(String[] args) {

System.out.println(Integer.MAX_VALUE);

System.out.println(Integer.toHexString(42));

}

}

在靜態匯入之後:

import static java.lang.System.out;

import static java.lang.Integer.*;


public class TestStaticImport {

public static void main(String[] args) {

out.println(MAX_VALUE);

out.println(toHexString(42));

}

}

讓我們看一下使用靜態匯入特性的程式碼中將發生什麼:

1、雖然該特性通常稱為“靜態匯入”,但語法必須是import static,後面跟你想匯入的static成員的完全限定名稱,或者萬用字元。在本例中,我們在System類的out物件上進行靜態匯入。

2、在本例中,我們可能想使用java.lang.Integer類的幾個static成員。該靜態匯入語句使用萬用字元來表達“我想在此類中的所有靜態成員上進行靜態匯入”。

3、現在我們終於看到靜態匯入特性的好處!我們不必在System.out.println中鍵入System。太好了!另外,我們不必在Integer.MAX_VALUE中鍵入Integer。因此,在這行程式碼中,我們能夠將快捷方式用於靜態方法和一個常量。

4、最後,我們進行更多的快捷操作,這次針對Integer類的方法。

關於該特性,我們已經有點兒諷刺意味兒了,但不僅我們是這樣的。我們不認為節省少量的擊鍵次數會讓程式碼

難於閱讀一點,但許多開發人員要求將它新增到語言中。

下面是使用靜態匯入的幾條原則:

你必須說import static, 不能說static import。

提防含糊不清的命名static成員。例如,如果你對Integer類和Long類執行了靜態匯入,引用MAX_VALUE將導致一個編譯器錯誤,因為Integer和Long都有一個MAX_VALUE常量,並且Java不會知道你在引用哪個MAX_VALUE。

你可以在static物件引用、常量(記住,它們是static 或final)和static方法上進行靜態匯入。

二.static關鍵字的誤區

1.static關鍵字會改變類中成員的訪問許可權嗎?

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

詳解JAVA中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語法的規定。

三.常見的筆試面試題

  下面列舉一些面試筆試中經常遇到的關於static關鍵字的題目,僅供參考,如有補充歡迎下方留言。

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

以上就是詳解JAVA中static的作用的詳細內容,更多關於JAVA STATIC作用的資料請關注我們其它相關文章!