1. 程式人生 > >重新認識java(六) ---- java中的另類:static關鍵字(附程式碼塊知識)

重新認識java(六) ---- java中的另類:static關鍵字(附程式碼塊知識)

你知道麼,static的用法至少有五種?

初識static

static是“靜態”的意思,這個大家應該都清楚,靜態變數,靜態方法大家也都能隨口道來。但是,你真的理解靜態變數和靜態方法麼?除了這些static還有什麼用處?

事實上,static大體上有五種用法:

  • 靜態匯入。
  • 靜態變數。
  • 靜態方法。
  • 靜態程式碼段。
  • 靜態內部類。

接下來,我們將逐個看一下這些用法。

靜態匯入

也許有的人是第一次聽說靜態匯入,反正我在寫這篇文章之前是不清楚static還可以這樣用的。什麼是靜態匯入呢?我們先來看一段程式碼:

public class
OldImport {
public static void main(String[] args) { double a = Math.cos(Math.PI / 2); double b = Math.pow(2.4,1.2); double r = Math.max(a,b); System.out.println(r); } }

看到這段程式碼,你有什麼想說的麼?啥?沒有?你不覺得Math出現的次數太多了麼?

恩,你覺得好像是有點多,怎麼辦呢?看下面:

import static java.lang.Math.*;

public
class StaticImport { public static void main(String[] args) { double a = cos(PI / 2); double b = pow(2.4,1.2); double r = max(a,b); System.out.println(r); } }

這就是靜態匯入。我們平時使用一個靜態方法的時候,都是【類名.方法名】,使用靜態變數的時候都是【類名.變數名】,如果一段程式碼中頻繁的用到了這個類的方法或者變數,我們就要寫好多次類名,比如上面的Math,這顯然不是喜歡偷懶的程式設計師所希望做的,所以出現了靜態匯入的功能。

靜態匯入,就是把一個靜態變數或者靜態方法一次性匯入,匯入後可以直接使用該方法或者變數,而不再需要寫物件名。

怎麼樣,是不是覺得很方便?如果你以前不知道這個,你大概在竊喜,以後可以偷懶了。先別高興的太早,看下面的程式碼:

import static java.lang.Double.*;
import static java.lang.Integer.*;
import static java.lang.Math.*;
import static java.text.NumberFormat.*;

public class ErrorStaticImport {
    // 輸入半徑和精度要求,計算面積
    public static void main(String[] args) {
        double s = PI * parseDouble(args[0]);
        NumberFormat nf = getInstance();
        nf.setMaximumFractionDigits(parseInt(args[1]));
        formatMessage(nf.format(s));
    }
    // 格式化訊息輸出
    public static void formatMessage(String s){
        System.out.println(" 圓面積是:"+s);
    }
}

就這麼一段程式,看著就讓人火大:常量PI,這知道,是圓周率;parseDouble 方法可能是Double 類的一個轉換方法,這看名稱也能猜測到。那緊接著的getInstance 方法是哪個類的?是ErrorStaticImport本地類的方法?不對呀,沒有這個方法,哦,原來是NumberFormate 類的方法,這和formateMessage 本地方法沒有任何區別了。這程式碼也太難閱讀了,這才幾行?要是你以後接別人的程式碼,看到成千上萬行這種程式碼大概你想死的心都有了吧?

所以,不要濫用靜態匯入!!!不要濫用靜態匯入!!!不要濫用靜態匯入!!!

正確使用靜態匯入的姿勢是什麼樣子的呢?

import java.text.NumberFormat;

import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Math.PI;
import static java.text.NumberFormat.getInstance;

public class ErrorStaticImport {
    // 輸入半徑和精度要求,計算面積
    public static void main(String[] args) {
        double s = PI * parseDouble(args[0]);
        NumberFormat nf = getInstance();
        nf.setMaximumFractionDigits(parseInt(args[1]));
        formatMessage(nf.format(s));
    }
    // 格式化訊息輸出
    public static void formatMessage(String s){
        System.out.println(" 圓面積是:"+s);
    }
}

沒錯,這才是正確的姿勢,你使用哪個方法或者哪個變數,就把他匯入進來,而不要使用萬用字元(*)!

並且,由於不用寫類名了,所以難免會和本地方法混淆。所以,本地方法在起名字的時候,一定要起得有意義,讓人一看這個方法名大概就能知道你這個方法是幹什麼的,而不是什麼method1(),method2(),鬼知道你寫的是什麼。。

總結:

  • 不使用*萬用字元,除非是匯入靜態常量類(只包含常量的類或介面)。
  • 方法名是具有明確、清晰表象意義的工具類。

這裡有一個小插曲,就是我在用idea寫示例程式碼的時候,想用萬用字元做靜態匯入,結果剛寫完,idea自動給我改成非萬用字元的了,嘿我這暴脾氣,我再改成萬用字元!特喵的。。又給我改回去了。。。事實證明,用一個好的IDE,是可以提高效率,比呢且優化好你的程式碼的,有的時候後,想不優化都不行。哈哈哈,推薦大家使用idea。

靜態變數

這個想必大家都已經很熟悉了。我就再囉嗦幾句。

java類提供了兩種型別的變數:用static修飾的靜態變數和不用static修飾的成員變數。

  • 靜態變數屬於類,在記憶體中只有一個例項。當jtbl所在的類被載入的時候,就會為該靜態變數分配記憶體空間,該變數就可以被使用。jtbl有兩種被使用方式:【類名.變數名】和【物件.變數名】。

  • 例項變數屬於物件,只有物件被建立後,例項物件才會被分配空間,才能被使用。他在記憶體中存在多個例項,只能通過【物件.變數名】來使用。

    第一篇文章《萬物皆物件》中講過,java的記憶體大體上有四塊:堆,棧,靜態區,常量區。

    其中的靜態區,就是用來放置靜態變數的。當靜態變數的類被載入時,虛擬機器就會在靜態區為該變數開闢一塊空間。所有使用該靜態變數的物件都訪問這一個空間。

一個例子學習靜態變數與例項變數

public class StaticAttribute {
    public static int staticInt = 10;
    public static int staticIntNo ;
    public int nonStatic = 5;

    public static void main(String[] args) {
        StaticAttribute s = new StaticAttribute();

        System.out.println("s.staticInt= " + s.staticInt);
        System.out.println("StaticAttribute.staticInt= " + StaticAttribute.staticInt);

        System.out.println("s.staticIntNo= " + s.staticIntNo);
        System.out.println("StaticAttribute.staticIntNo= " + StaticAttribute.staticIntNo);

        System.out.println("s.nonStatic= " + s.nonStatic);

        System.out.println("使用s,讓三個變數都+1");

        s.staticInt ++;
        s.staticIntNo ++;
        s.nonStatic ++;

        StaticAttribute s2 = new StaticAttribute();

        System.out.println("s2.staticInt= " + s2.staticInt);
        System.out.println("StaticAttribute.staticInt= " + StaticAttribute.staticInt);

        System.out.println("s2.staticIntNo= " + s2.staticIntNo);
        System.out.println("StaticAttribute.staticIntNo= " + StaticAttribute.staticIntNo);

        System.out.println("s2.nonStatic= " + s2.nonStatic);

    }
}
//        結果:
//        s.staticInt= 10
//        StaticAttribute.staticInt= 10
//        s.staticIntNo= 0
//        StaticAttribute.staticIntNo= 0
//        s.nonStatic= 5
//        使用s,讓三個變數都+1
//        s2.staticInt= 11
//        StaticAttribute.staticInt= 11
//        s2.staticIntNo= 1
//        StaticAttribute.staticIntNo= 1
//        s2.nonStatic= 5

從上例可以看出,靜態變數只有一個,被類擁有,所有物件都共享這個靜態變數,而例項物件是與具體物件相關的。

與c++不同的是,在java中,不能在方法體中定義static變數,我們之前所說的變數,都是類變數,不包括方法內部的變數。

那麼,靜態變數有什麼用途呢?

靜態變數的用法

最開始的程式碼中有一個靜態變數 — PI,也就是圓周率。為什麼要把它設計為靜態的呢?因為我們可能在程式的任何地方使用到這個變數,如果不是靜態的,那麼我們每次使用這個變數的時候都要建立一個Math物件,不僅程式碼臃腫而且浪費了記憶體空間。

所以,當你的某一個變數會經常被外部程式碼訪問的時候,可以考慮設計為靜態的。

靜態方法

同樣,靜態方法大家應該也比較熟悉了。就是在定義類的時候加一個static修飾符。

與靜態變數一樣,java類也同時提供了static方法和非static方法。

  • static方法是類的方法,不需要建立物件就可以使用,比如Math類裡面的方法。使用方法【物件.方法名】或者【類名.方法名】
  • 非static方法是物件的方法,只有物件唄創建出來以後才可以被使用。使用方法【物件.方法名】

static怎麼用程式碼寫我想大家都知道,這裡我就不舉例了,你們看著煩,我寫著也煩。

注意事項

static方法中不能使用this和super關鍵字,不能呼叫非static方法,只能訪問所屬類的靜態變數和靜態方法。因為當static方法被呼叫的時候,這個類的物件可能還沒有建立,即使已經被建立了,也無法確認呼叫那個物件的方法。不能訪問非靜態方法同理。

用途—單例模式

static的一個很常見的用途是實現單例模式。單例模式的特點是一個類只能有一個例項,為了實現這一功能,必須隱藏該類的建構函式,即把建構函式宣告為private,並提供一個建立物件的方法。我們來看一下怎麼實現:

public class Singleton {
    private static Singleton singleton;

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

    private Singleton() {

    }
}

這個類,只會有一個物件。

其他

用public修飾的static成員變數和成員方法本質是全域性變數和全域性方法,當宣告它類的物件時,不生成static變數的副本,而是類的所有例項共享同一個static變數。

static 變數前可以有private修飾,表示這個變數可以在類的靜態程式碼塊中,或者類的其他靜態成員方法中使用(當然也可以在非靜態成員方法中使用–廢話),但是不能在其他類中通過類名來直接引用,這一點很重要。

實際上你需要搞明白,private是訪問許可權限定,static表示不要例項化就可以使用,這樣就容易理解多了。static前面加上其它訪問許可權關鍵字的效果也以此類推。

靜態方法的用場

靜態變數可以被非靜態方法呼叫,也可以被靜態方法呼叫。但是靜態方法只能被靜態方法呼叫。

一般工具方法會設計為靜態方法,比如Math類中的所有方法都是驚天的,因為我們不需要Math類的例項,我們只是想要用一下里面的方法。所以,你可以寫一個通用的 工具類,然後裡面的方法都寫成靜態的。

靜態程式碼塊

在講靜態程式碼塊之前,我們先來看一下,什麼是程式碼塊。

什麼是程式碼塊

所謂程式碼塊就是用大括號將多行程式碼封裝在一起,形成一個獨立的資料體,用於實現特定的演算法。一般來說程式碼塊是不能單獨執行的,它必須要有執行主體。在Java中程式碼塊主要分為四種:普通程式碼塊,靜態程式碼塊,同步程式碼塊和構造程式碼塊。

四種程式碼塊

  • 普通程式碼塊

    普通程式碼塊是我們用得最多的也是最普遍的,它就是在方法名後面用{}括起來的程式碼段。普通程式碼塊是不能夠單獨存在的,它必須要緊跟在方法名後面。同時也必須要使用方法名呼叫它。

    public void common(){  
        System.out.println("普通程式碼塊執行");  
    } 
  • 靜態程式碼塊

    靜態程式碼塊就是用static修飾的用{}括起來的程式碼段,它的主要目的就是對靜態屬性進行初始化。

    靜態程式碼塊可以有多個,位置可以隨便放,它不在任何的方法體內,JVM載入類時會執行這些靜態的程式碼塊,如果static程式碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,每個程式碼塊只會被執行一次。

看一段程式碼:

public class Person{
    private Date birthDate;

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

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

 isBornBoomer是用來這個人是否是1990-1999年出生的,而每次isBornBoomer被呼叫的時候,都會生成startDate和birthDate兩個物件,造成了空間浪費,如果改成這樣效率會更好:

public class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1990");
        endDate = Date.valueOf("1999");
    }

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

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

因此,很多時候會將一些只需要進行一次的初始化操作都放在static程式碼塊中進行。

  • 同步程式碼塊

    使用 synchronized 關鍵字修飾,並使用“{}”括起來的程式碼片段,它表示同一時間只能有一個執行緒進入到該方法塊中,是一種多執行緒保護機制。

等講多執行緒的時候,在詳細講解這種程式碼塊~

  • 構造程式碼塊

    在類中直接定義沒有任何修飾符、字首、字尾的程式碼塊即為構造程式碼塊。我們明白一個類必須至少有一個建構函式,建構函式在生成物件時被呼叫。構造程式碼塊和建構函式一樣同樣是在生成一個物件時被呼叫,那麼構造程式碼在什麼時候被呼叫?如何呼叫的呢?

看一段程式碼:

public class CodeBlock {
    private int a = 1;
    private int b ;
    private int c ;
    //靜態程式碼塊
    static {
        int a = 4;
        System.out.println("我是靜態程式碼塊1");
    }
    //構造程式碼塊
    {
        int a = 0;
        b = 2;
        System.out.println("構造程式碼塊1");
    }

    public CodeBlock(){
        this.c = 3;
        System.out.println("建構函式");
    }

    public int add(){

        System.out.println("count a + b + c");
        return a + b + c;
    }
    //靜態程式碼塊
    static {
        System.out.println("我是靜態程式碼塊2,我什麼也不做");
    }
    //構造程式碼塊
    {
        System.out.println("構造程式碼塊2");
    }
    public static void main(String[] args) {
        CodeBlock c = new CodeBlock();
        System.out.println(c.add());

        System.out.println();
        System.out.println("*******再來一次*********");
        System.out.println();

        CodeBlock c1 = new CodeBlock();
        System.out.println(c1.add());
    }
}
//結果:
//我是靜態程式碼塊1
//我是靜態程式碼塊2,我什麼也不做
//構造程式碼塊1
//構造程式碼塊2
//建構函式
//count a + b + c
//6
//
//*******再來一次*********
//
//構造程式碼塊1
//構造程式碼塊2
//建構函式
//count a + b + c
//6

這段程式碼綜合了構造程式碼塊,普通程式碼塊和靜態程式碼塊。我們來總結一下:

  • 靜態程式碼塊只會執行一次。有多個靜態程式碼塊時按順序依次執行。
  • 構造程式碼塊每次建立新物件時都會執行。有多個時依次執行。
  • 執行順序:靜態程式碼塊 > 構造程式碼塊 > 建構函式。
  • 構造程式碼塊和靜態程式碼塊有自己的作用域,作用域內部的變數不影響作用域外部。

構造程式碼塊的應用場景:

1、 初始化例項變數
如果一個類中存在若干個建構函式,這些建構函式都需要對例項變數進行初始化,如果我們直接在建構函式中例項化,必定會產生很多重複程式碼,繁瑣和可讀性差。這裡我們可以充分利用構造程式碼塊來實現。這是利用編譯器會將構造程式碼塊新增到每個建構函式中的特性。

2、 初始化例項環境
一個物件必須在適當的場景下才能存在,如果沒有適當的場景,則就需要在建立物件時建立此場景。我們可以利用構造程式碼塊來建立此場景,尤其是該場景的建立過程較為複雜。構造程式碼會在建構函式之前執行。

靜態內部類

被static修飾的內部類,它可以不依賴於外部類例項物件而被例項化,而通常的內部類需要在外部類例項化後才能例項化。靜態內部類不能與外部類有相同的名字,不能訪問外部類的普通成員變數,只能訪問內部類中的靜態成員和靜態方法(包括私有型別)。

由於還沒有詳細講解過內部類,這裡先一筆帶過,在講解內部類的時候會詳細分析靜態內部類。

只有內部類才能被static修飾,普通的類不可以。

總結

本文內容就先到這裡,我們再來回顧一下學了什麼:

  • static關鍵字的五種用法:

    • 靜態匯入
    • 靜態變數
    • 靜態方法
    • 靜態程式碼塊
  • 程式碼塊

    • 普通程式碼塊
    • 靜態程式碼塊
    • 構造程式碼塊
    • 同步程式碼塊

回憶一下這些知識點的內容,如果想不起來,記得翻上去再看一遍~

彩蛋 —— 繼承+程式碼塊的執行順序

如果既有繼承,又有程式碼塊,執行的順序是怎樣呢?

public class Parent {
    static {
        System.out.println("父類靜態程式碼塊");
    }

    {
        System.out.println("父類構造程式碼塊");
    }

    public Parent(){
        System.out.println("父類建構函式");
    }
}

class Children extends Parent {
    static {
        System.out.println("子類靜態程式碼塊");
    }
    {
        System.out.println("子類構造程式碼塊");
    }
    public Children(){
        System.out.println("子類建構函式");
    }

    public static void main(String[] args) {
        new Children();
    }
}

//結果:
//父類靜態程式碼塊
//子類靜態程式碼塊
//父類構造程式碼塊
//父類建構函式
//子類構造程式碼塊
//子類建構函式

結果你也知道了:

先執行靜態內容(先父類後子類),然後執行父類非靜態,最後執行子類非靜態。(非靜態包括構造程式碼塊和建構函式,構造程式碼塊先執行)

如果文中有錯誤或者你有其他見解,請及時與我聯絡。不保證文章內容的完全正確性。

轉載請註明出處。

看完文章,如果你學到了你以前不知道的知識,點個贊支援一下喲~