Java Static機制分析

1、問題引出

    在java的關鍵字中,static是常常用到的關鍵字。static修飾的成員變量和成員方法獨立於該類的任何對象。也就是說,它不依賴類特定的實例,被類的全部實例共享。只要這個類被加載,Java虛擬機就能根據類名在運行時數據區的方法區內定找到他們。所以,static對象能夠在它的任何對象建立以前訪問,無需引用任何對象。html

static關鍵字有以下幾種用法:java

一、用來修飾成員變量,將其變爲類的成員,從而實現全部對象對於該成員的共享;數據庫

二、用來修飾成員方法,將其變爲類方法,能夠直接使用「類名.方法名」的方式調用,經常使用於工具類;jvm

三、靜態塊用法,將多個類成員放在一塊兒初始化,使得程序更加規整,其中理解對象的初始化過程很是關鍵;函數

四、靜態導包用法,將類的方法直接導入到當前類中,從而直接使用「方法名」便可調用類方法,更加方便。工具

下面將詳細分析這四種用法:   性能

2、static用法分析

一、static修飾成員變量

按照是否靜態的對類成員變量進行分類可分兩種:一種是被static修飾的變量,叫靜態變量或類變量;另外一種是沒有被static修飾的變量,叫實例變量。二者的區別是:優化

    對於靜態變量在內存中只有一個拷貝(節省內存),JVM只爲靜態分配一次內存(在虛擬機的方法區),在加載類的過程當中完成靜態變量的內存分配,可用類名直接訪問(方便),固然也能夠經過對象來訪問(可是這是不推薦的)。    this

    對於實例變量,每建立一個實例,就會爲實例變量分配一次內存,實例變量能夠在內存中有多個拷貝,互不影響(靈活)。spa

    static成員變量的初始化順序按照定義的順序進行初始化。static不能夠修飾局部變量。

因此通常在須要實現如下兩個功能時使用靜態變量:(1)在對象之間共享值時 (2)方便訪問變量時

接下來看下面的例子:

public class Person {
    String name;
    int age;
    
    public String toString() {
        return "Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "zhangsan";
        p1.age = 10;
        Person p2 = new Person();
        p2.name = "lisi";
        p2.age = 12;
        System.out.println(p1);
        System.out.println(p2);
    }
    /**Output
     * Name:zhangsan, Age:10
     * Name:lisi, Age:12
     *///~
}

上面的代碼咱們很熟悉,根據Person構造出的每個對象都是獨立存在的,保存有本身獨立的成員變量,相互不會影響,他們在內存中的示意以下:

 

從上圖中能夠看出,p1和p2兩個變量引用的對象分別存儲在內存中堆區域的不一樣地址中,因此他們之間相互不會干擾。但其實,在這當中,咱們省略了一些重要信息,相信你們也都會想到,對象的成員屬性都在這了,由每一個對象本身保存,那麼他們的方法呢?實際上,不論一個類建立了幾個對象,他們的方法都是同樣的:

從上面的圖中咱們能夠看到,兩個Person對象的方法實際上只是指向了同一個方法定義。這個方法定義是位於內存中的一塊不變區域(由jvm劃分),咱們暫稱它爲靜態存儲區。這一塊存儲區不只存放了方法的定義,實際上從更大的角度而言,它存放的是各類類的定義,當咱們經過new來生成對象時,會根據這裏定義的類的定義去建立對象。多個對象僅會對應同一個方法,這裏有一個讓咱們充分信服的理由,那就是無論多少的對象,他們的方法老是相同的,儘管最後的輸出會有所不一樣,可是方法老是會按照咱們預想的結果去操做,即不一樣的對象去調用同一個方法,結果會不盡相同。

咱們知道,static關鍵字能夠修飾成員變量和方法,來讓它們變成類的所屬,而不是對象的所屬,好比咱們將Person的age屬性用static進行修飾,結果會是什麼樣呢?請看下面的例子:

public class Person {
    String name;
    static int age;
    
    /* 其他代碼不變... */

    /**Output
     * Name:zhangsan, Age:12
     * Name:lisi, Age:12
     *///~
}

咱們發現,結果發生了一點變化,在給p2的age屬性賦值時,干擾了p1的age屬性,這是爲何呢?咱們仍是來看他們在內存中的示意:

咱們發現,給age屬性加了static關鍵字以後,Person對象就再也不擁有age屬性了,age屬性會統一交給Person類去管理,即多個Person對象只會對應一個age屬性,一個對象若是對age屬性作了改變,其餘的對象都會受到影響。咱們看到此時的age和toString()方法同樣,都是交由類去管理。

雖然咱們看到static可讓對象共享屬性,可是實際中咱們不多這麼用,也不推薦這麼使用。由於這樣會讓該屬性變得難以控制,由於它在任何地方都有可能被改變。若是咱們想共享屬性,通常咱們會採用其餘的辦法:

public class Person {
    private static int count = 0;
    int id;
    String name;
    int age;
    
    public Person() {
        id = ++count;
    }
    
    public String toString() {
        return "Id:" + id + ", Name:" + name + ", Age:" + age;
    }
    
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "zhangsan";
        p1.age = 10;
        Person p2 = new Person();
        p2.name = "lisi";
        p2.age = 12;
        System.out.println(p1);
        System.out.println(p2);
    }
    /**Output
     * Id:1, Name:zhangsan, Age:10
     * Id:2, Name:lisi, Age:12
     *///~
}

上面的代碼起到了給Person的對象建立一個惟一id以及記錄總數的做用,其中count由static修飾,是Person類的成員屬性,每次建立一個Person對象,就會使該屬性自加1而後賦給對象的id屬性,這樣,count屬性記錄了建立Person對象的總數,因爲count使用了private修飾,因此從類外面沒法隨意改變。

二、修飾成員方法

    靜態方法的好處就是不用生成類的實例就能直接調用,能夠這樣理解使用static修飾的成員再也不歸對象因此,而是屬於類能夠理解爲是共有的,也就說只要經過類名就能夠訪問,就是可使用"類名.方法名"的方式操做方法,不須要耗費資源反覆new出對象,由於在程序第一次加載的時候就已經在內存中了,直到程序結束該內存纔會釋放。若是不是static修飾的成員在使用完以後該內存就會被回收,因此說static要慎用,根據實際狀況而定

若是這個方法是做爲一個工具來使用,就聲明爲static,不用new一個對象出來就可使用了,好比鏈接到數據庫,我聲明一個 getConnection()的方法,就定義爲靜態的,由於鏈接到數據庫不是某一個對象所特有的。它只做爲一個鏈接到數據庫的工具。至於提升效率的也未 必,要看具體的方法的用處,去定義這個方法是否是靜態的。

咱們可能會常常在幫助類中看到它的使用:

public class PrintHelper {

    public static void print(Object o){
        System.out.println(o);
    }
    
    public static void main(String[] args) {
        PrintHelper.print("Hello world");
    }
}

上面即是一個例子(如今還不太實用),可是咱們能夠看到它的做用,使得static修飾的方法成爲類的方法,使用時經過「類名.方法名」的方式就能夠方便的使用了,至關於定義了一個全局的函數(只要導入該類所在的包便可)。不過它也有使用的侷限,一個static修飾的類中,不能使用非static修飾的成員變量和方法,這很好理解,由於static修飾的方法是屬於類的,若是去直接使用對象的成員變量,它會不知所措(不知該使用哪個對象的屬性)。

三、靜態代碼塊

static代碼塊也叫靜態代碼塊,是在類中獨立於類成員的static語句塊,能夠有多個,位置能夠隨便放,它不在任何的方法體內,JVM加載類時會執行這些靜態的代碼塊,若是static代碼塊有多個,JVM將按照它們在類中出現的前後順序依次執行它們,每一個代碼塊只會被執行一次,因此說static塊能夠用來優化程序性能。

 

static方法塊和static方法的區別:

靜態代碼塊是自動執行的;

靜態方法是被調用的時候才執行的.

靜態方法:若是咱們在程序編寫的時候須要一個不實例化對象就能夠調用的方法,咱們就可使用靜態方法,具體實現是在方法前面加上static,以下:

public static void method(){}

在使用靜態方法的時候須要注意一下幾個方面:

在靜態方法裏只能直接調用同類中其餘的靜態成員(包括變量和方法),而不能直接訪問類中的非靜態成員。這是由於,對於非靜態的方法和變量,須要先建立類的實例對象後纔可以使用,而靜態方法在使用前不用建立任何對象。(備註:靜態變量是屬於整個類的變量而不是屬於某個對象的)

靜態方法不能以任何方式引用this和super關鍵字,由於靜態方法在使用前不用建立任何實例對象,當靜態方法調用時,this所引用的對象根本沒有產生。

靜態程序塊:當一個類須要在被載入時就執行一段程序,這樣可使用靜態程序塊。

public class DemoClass {

private DemoClass(){}

public static DemoClass _instance;

static{

if(null == _instance ){

_instance = new DemoClass();

}

}

public static DemoClass getInstance(){

return _instance;

}

}

這樣的程序在類被加載的時候就執行了static中的代碼。

4.靜態導包

 相比於上面的三種用途,第四種用途可能瞭解的人就比較少了,可是實際上它很簡單,並且在調用類方法時會更方便。以上面的「PrintHelper」的例子爲例,作一下稍微的變化,便可使用靜態導包帶給咱們的方便:

/* PrintHelper.java文件 */
package com.dotgua.study;

public class PrintHelper {

    public static void print(Object o){
        System.out.println(o);
    }
}
/* App.java文件 */

import static com.dotgua.study.PrintHelper.*;

public class App 
{
    public static void main( String[] args )
    {
        print("Hello World!");
    }
    /**Output
     * Hello World!
     *///~
}

上面的代碼來自於兩個java文件,其中的PrintHelper很簡單,包含了一個用於打印的static方法。而在App.java文件中,咱們首先將PrintHelper類導入,這裏在導入時,咱們使用了static關鍵字,並且在引入類的最後還加上了「.*」,它的做用就是將PrintHelper類中的全部類方法直接導入。不一樣於非static導入,採用static導入包後,在不與當前類的方法名衝突的狀況下,無需使用「類名.方法名」的方法去調用類方法了,直接能夠採用"方法名"去調用類方法,就好像是該類本身的方法同樣使用便可。

參考文獻:一、https://www.cnblogs.com/dotgua/p/6354151.html?utm_source=itdadao&utm_medium=referral

二、https://www.cnblogs.com/wonderlands/p/5630573.html

三、https://www.cnblogs.com/itcqx/p/5519464.html

相關文章
相關標籤/搜索