Java關(guān)鍵字(三)——static

我們說Java是一種面向?qū)ο缶幊痰恼Z言,而對象是把數(shù)據(jù)及對數(shù)據(jù)的操作方法放在一起,作為一個相互依存的整體,對同類對象抽象出其共性,便是Java中的類,我們可以用類描述世間萬物,也可以說萬物皆對象。但是這里有個特殊的東西——static,它不屬于對象,那么為什么呢?

static 是Java的一個關(guān)鍵字,可以用來修飾成員變量、修飾成員方法、構(gòu)造靜態(tài)代碼塊、實現(xiàn)靜態(tài)導報以及實現(xiàn)靜態(tài)內(nèi)部類,下面我們來分別介紹。

1、修飾成員變量

用 static 修飾成員變量可以說是該關(guān)鍵字最常用的一個功能,通常將用 static 修飾的成員變量稱為類成員或者靜態(tài)成員,那么靜態(tài)成員和不用 static 修飾的非靜態(tài)成員有什么區(qū)別呢?

我們先看看不用 static 修飾的成員變量在內(nèi)存中的構(gòu)造。

package com.ys.bean;

/**
 * Create by YSOcean
 */
public class Person {
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    //get和set方法省略
}

首先,我們創(chuàng)建一個實體類 Person,有兩個屬性 name 和 age,都是普通成員變量(沒有用 static 關(guān)鍵字修飾),接著我們通過其構(gòu)造方法創(chuàng)建兩個對象:

1 Person p1 = new Person("Tom",21); 
2 Person p2 = new Person("Marry",20);
3 System.out.println(p1.toString());//Person{name='Tom', age=21}
4 System.out.println(p2.toString());//Person{name='Marry', age=20}

這兩個對象在內(nèi)存中的存儲結(jié)構(gòu)如下:

image

由上圖可知,我們創(chuàng)建的兩個對象 p1 和 p2 存儲在堆中,但是其引用地址是存放在棧中的,而且這兩個對象的兩個變量互相獨立,我們修改任何一個對象的屬性值,是不改變另外一個對象的屬性值的。

下面我們將 Person 類中的 age 屬性改為由 static 關(guān)鍵字修飾:

package com.ys.bean;

/**
 * Create by YSOcean
 */
public class Person {
    private  String name;
    private static Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    //get和set方法省略

}

同樣我們還是向上面一樣,創(chuàng)建 p1 和 p2 兩個對象,并打印這兩個對象,看看和上面打印的有啥區(qū)別呢?

1 Person p1 = new Person("Tom",21);
2 Person p2 = new Person("Marry",20); 
3 System.out.println(p1.toString());//Person{name='Tom', age=20}
4 System.out.println(p2.toString());//Person{name='Marry', age=20}

我們發(fā)現(xiàn)第三行代碼打印的 p1 對象 age 屬性變?yōu)?20了,這是為什么呢?

image

這是因為用在 jvm 的內(nèi)存構(gòu)造中,會在堆中開辟一塊內(nèi)存空間,專門用來存儲用 static 修飾的成員變量,稱為靜態(tài)存儲區(qū),無論我們創(chuàng)建多少個對象,用 static 修飾的成員變量有且只有一份存儲在靜態(tài)存儲區(qū)中,所以該靜態(tài)變量的值是以最后創(chuàng)建對象時設(shè)置該靜態(tài)變量的值為準,也就是由于 p1 先設(shè)置 age = 21,后來創(chuàng)建了 p2 對象,p2將 age 改為了20,那么該靜態(tài)存儲區(qū)的 age 屬性值也被修改成了20。

PS:在 JDK1.8 以前,靜態(tài)存儲區(qū)是存放在方法區(qū)的,而方法區(qū)不屬于堆,在 JDK1.8 之后,才將方法區(qū)干掉了,方法區(qū)中的靜態(tài)存儲區(qū)改為到堆中存儲。

總結(jié):static 修飾的變量被所有對象所共享,在內(nèi)存中只有一個副本。由于與對象無關(guān),所以我們可以直接通過 類名.靜態(tài)變量 的方式來直接調(diào)用靜態(tài)變量。對應(yīng)的非靜態(tài)變量是對象所擁有的,多少個對象就有多少個非靜態(tài)變量,各個對象所擁有的副本不受影響。

2、修飾修飾成員方法

用 static 關(guān)鍵字修飾成員方法也是一樣的道理,我們可以直接通過 類名.靜態(tài)方法名() 的方式來調(diào)用,而不用創(chuàng)建對象。

public class Person {
    private  String name;
    private static Integer age;

    public static void printClassName(){
        System.out.println("com.ys.bean.Person");
    }
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    //get和set方法省略

}

調(diào)用靜態(tài)方法:

1 Person.printClassName();//com.ys.bean.Person</pre>

3、靜態(tài)代碼塊

用 static 修飾的代碼塊稱為靜態(tài)代碼塊,靜態(tài)代碼塊可以置于類的任意一個地方(和成員變量成員方法同等地位,不可放入方法中),并且一個類可以有多個靜態(tài)代碼塊,在類初次載入內(nèi)存時加載靜態(tài)代碼塊,并且按照聲明靜態(tài)代碼塊的順序來加載,且僅加載一次,優(yōu)先于各種代碼塊以及構(gòu)造函數(shù)。

關(guān)于靜態(tài)代碼塊、構(gòu)造代碼塊、構(gòu)造函數(shù)、普通代碼塊的區(qū)別可以參考我的這篇博客。

1 public class CodeBlock {
2     static{ 
3         System.out.println("靜態(tài)代碼塊"); 
4    } 
5 }

由于靜態(tài)代碼塊只在類載入內(nèi)存時加載一次的特性,我們可以利用靜態(tài)代碼塊來優(yōu)化程序性能,比如某個比較大配置文件需要在創(chuàng)建對象時加載,這時候為了節(jié)省內(nèi)存,我們可以將該配置文件的加載時機放入到靜態(tài)代碼塊中,那么我們無論創(chuàng)建多少個對象時,該配置文件也只加載了一次。

4、靜態(tài)導包

用 static 來修飾成員變量,成員方法,以及靜態(tài)代碼塊是最常用的三個功能,靜態(tài)導包是 JDK1.5以后的新特性,用 import static 包名 來代替?zhèn)鹘y(tǒng)的 import 包名 方式。那么有什么用呢?

比如我們創(chuàng)建一個數(shù)組,然后用 JDK 自帶的 Arrays 工具類的 sort 方法來對數(shù)組進行排序:

package com.ys.test;

import java.util.Arrays;
/**
 * Create by YSOcean
 */
public class StaticTest {

    public static void main(String[] args) {
        int[] arrays = {3,4,2,8,1,9};
        Arrays.sort(arrays);
    }
}

我們可以看到,調(diào)用 sort 方法時,需要進行 import java.util.Arrays 的導包操作,那么變?yōu)殪o態(tài)導包呢?

package com.ys.test;

import static java.util.Arrays.*;
/**
 * Create by YSOcean
 */
public class StaticTest {

    public static void main(String[] args) {
        int[] arrays = {3,4,2,8,1,9};
        sort(arrays);
    }
}

我們可以看到第三行代碼的 import java.util.Arrays 變?yōu)榱?import static java.util.Arrays.*,意思是導入 Arrays 類中的所有靜態(tài)方法,當然你也可以將 * 變?yōu)槟硞€方法名,也就是只導入該方法,那么我們在調(diào)用該方法時,就可以不帶上類名,直接通過方法名來調(diào)用(第 11 行代碼)。

靜態(tài)導包只會減少程序員的代碼編寫量,對于性能是沒有任何提升的(也不會降低性能,Java核心技術(shù)第10版卷1第148頁4.7.1章節(jié)類的導入有介紹),反而會降低代碼的可讀性,所以實際如何使用需要權(quán)衡。

5、靜態(tài)內(nèi)部類

首先我們要知道什么是內(nèi)部類,定義在一個類的內(nèi)部的類叫內(nèi)部類,包含內(nèi)部類的類叫外部類,內(nèi)部類用 static 修飾便是我們所說的靜態(tài)內(nèi)部類。

定義內(nèi)部類的好處是外部類可以訪問內(nèi)部類的所有方法和屬性,包括私有方法和私有屬性。

訪問普通內(nèi)部類,我們需要先創(chuàng)建外部類的對象,然后通過外部類名.new 創(chuàng)建內(nèi)部類的實例。

package com.ys.bean;

/**
 * Create by hadoop
 */
public class OutClass {

    public class InnerClass{

    }
}
1  * OuterClass oc = new OuterClass();
2  * OuterClass.InnerClass in = oc.new InnerClass();

訪問靜態(tài)內(nèi)部類,我們不需要創(chuàng)建外部類的對象,可以直接通過 外部類名.內(nèi)部類名 來創(chuàng)建實例。

package com.ys.bean;

/**
 * Create by hadoop
 */
public class OutClass {

    public static class InnerClass{

    }
}
1 OuterClass.StaticInnerClass sic = new OuterClass.StaticInnerClass();

6、常見問題

①、靜態(tài)變量能存在于普通方法中嗎?

能。很明顯,普通方法必須通過對象來調(diào)用,靜態(tài)變量都可以直接通過類名來調(diào)用了,更不用說通過對象來調(diào)用,所以是可以存在于普通方法中的。

②、靜態(tài)方法能存在普通變量嗎?

不能。因為靜態(tài)方法可以直接通過類名來直接調(diào)用,不用創(chuàng)建對象,而普通變量是必須通過對象來調(diào)用的。那么將普通變量放在靜態(tài)方法中,在直接通過類來調(diào)用靜態(tài)方法時就會報錯。所以不能。

③、靜態(tài)代碼塊能放在方法體中嗎?

不能。首先我們要明確靜態(tài)代碼塊是在類加載的時候自動運行的。

普通方法需要我們創(chuàng)建對象,然后手工去調(diào)用方法,所靜態(tài)代碼塊不能聲明在普通方法中。

那么對于用 static 修飾的靜態(tài)方法呢?同樣也是不能的。因為靜態(tài)方法同樣也需要我們手工通過類名來調(diào)用,而不是直接在類加載的時候就運行了。

也就是說靜態(tài)代碼塊能夠自動執(zhí)行,而不管是普通方法還是靜態(tài)方法都是需要手工執(zhí)行的。

④、靜態(tài)導包會比普通導包消耗更多的性能?

不會。靜態(tài)導包實際上在編譯期間都會被編譯器進行處理,將其轉(zhuǎn)換成普通按需導包的形式,所以在程序運行期間是不影響性能的。

⑤、static 可以用來修飾局部變量嗎?

不能。不管是在普通方法還是在靜態(tài)方法中,static 關(guān)鍵字都不能用來修飾局部變量,這是Java的規(guī)定。稍微想想也能明白,局部變量的聲明周期是隨著方法的結(jié)束而結(jié)束的,因為static 修飾的變量是全局的,不與對象有關(guān)的,如果用 static 修飾局部變量容易造成理解上的沖突,所以Java規(guī)定 static 關(guān)鍵字不能用來修飾局部變量。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容