Java關鍵字final&static

Java關鍵字final

在設計程序時肮韧,出于效率或者設計的原因嘉蕾,有時候希望某些數(shù)據是不可改變的挤茄。這時候可以使用final關鍵字曙咽,修飾這部分是無法修改的,達到了終態(tài)。final可以修飾非抽象類,非抽象類成員變量和方法。

final常量

在Java中败晴,利用關鍵字final指示常量。而常量有兩種:

  • final修飾實例域栽渴,final+類型
  • final修飾的類常量尖坤,static final+類型

類型可以是基本數(shù)據類型,也可以是引用數(shù)據類型闲擦。

如果根據初始化時機分:

  • 編譯期常量慢味,static final修飾的基本數(shù)據類型或者String類型。需要注意的是這種情況必須是在聲明時就顯示的賦值(基本類型賦予直接量墅冷,String類型直接用字符串字面量聲明)纯路。因為它們在類加載的加載階段被放入方法區(qū)中的運行時常量池中,能夠存放的只有聲明為final的常量值寞忿,字符串字面量驰唬。一旦類加載完畢,就不能在更改。編譯期可以將它代入到任何用到它的計算式中叫编,也就是說可以在編譯期執(zhí)行計算式辖佣。
  • 運行期常量,final修飾的基本數(shù)據類型或者引用數(shù)據類型搓逾。它們是在實例化過程中依據不同對象的要求進行不同的初始化卷谈。同時由于final的特性一旦被初始化就不會改變。這是由于final空白特性恃逻。在聲明final常量時,可以不賦初值藕施,但是編譯器必須確保使用該空白final常量時寇损,已經被賦值(初始化)。所以必須在執(zhí)行完構造函數(shù)之后必須已經被初始化裳食。
  • 介于編譯期和運行期矛市,static final修飾的其他引用數(shù)據類型(包括不使用字符串字面量聲明的String對象)或者在聲明中未賦值的基本類型,必須在類加載的初始化階段被初始化(在static代碼塊中)诲祸。

注意一旦給final變量初值后浊吏,值就不能再改變了。但是有一個誤區(qū)救氯,當修飾引用數(shù)據類型時找田,而且類型是可變類,那么不可變的是引用地址着憨,而對象的內容是可變的墩衙。

import java.util.*;
class BaseLoader {
    static final int i = new Random(47).nextInt(20);
    static {
        System.out.println("Inititalization!");
        System.out.println("i is " + i);
    }
}

public class Test {
    public static void main(String[] args) {
        System.out.println(BaseLoader.i);
    }
}

執(zhí)行Test.java后,Console輸出:Inititalization! i is 18 18甲抖。說明只有在聲明時賦值的static final修飾的常量才屬于編譯期常量漆改。而static final int i = new Random(47).nextInt(20)是在類加載的初始化階段初始化的。

final方法

如果一個方法被final修飾准谚。那么其子類不能覆寫該方法挫剑。這樣做的原因出于兩個方面的考慮:

  1. 把方法鎖定,防止子類修改它的意義和實現(xiàn)
  2. 高效柱衔。編譯器在遇到調用final方法時候會轉入內嵌機制樊破,大大提高執(zhí)行效率。

在java的早期實現(xiàn)中唆铐,如果將一個方法指明為final捶码,就是同意編譯器將針對該方法的所有調用都轉為內嵌調用。當編譯器發(fā)現(xiàn)一個final方法調用命令時或链,它會根據自己的謹慎判斷惫恼,跳過插入程序代碼這種正常的調用方式而執(zhí)行方法調用機制(將參數(shù)壓入棧,跳至方法代碼處執(zhí)行澳盐,然后跳回并清理棧中的參數(shù)祈纯,處理返回值)令宿,并且以方法體中的實際代碼的副本來代替方法調用。這將消除方法調用的開銷腕窥。當然粒没,如果一個方法很大,你的程序代碼會膨脹簇爆,因而可能看不到內嵌所帶來的性能上的提高癞松,因為所帶來的性能會花費于方法內的時間量而被縮減(不是很理解)。

final類

在設計類的時候入蛆,出于某些因素的考慮响蓉,這個類的實現(xiàn)細節(jié)不允許隨意修改,而且不需要子類哨毁,確定它不會要被擴展枫甲。那么設計時使用final修飾。final類是不允許被繼承的扼褪,表明該類事最終類想幻。由于final類是無法繼承的,所以類方法會默認加上final修飾话浇。而它的成員變量并沒有強制規(guī)定被final修飾脏毯。

final參數(shù)

final可以修飾方法參數(shù)列表中的參數(shù),一旦調用方法傳遞參數(shù)后幔崖,方法內不可以修改參數(shù)(基本數(shù)據類型不能修改值抄沮,引用類型的可變類不能修改地址,不可變類完全不可變)岖瑰。最常見的就是方法中將參數(shù)傳遞給匿名內部類使用叛买,此時該參數(shù)必須為final。

那么為什么匿名內部類在使用方法中的局部變量或者方法的參數(shù)時蹋订,需要使用final修飾率挣?首先來了解一個基本概念:

內部類被編譯時,字節(jié)碼會單獨放在一個.class文件中露戒,與外部類的字節(jié)碼文件分開椒功。

匿名內部類使用方法局部變量

public class OuterClass{

    public void test() {
        final int a = 10;
        new Thread() {
            public void run() {
                System.out.println(a);
            }
        }.start();
    }
}

如果執(zhí)行test()完成后,那么在站內存中的變量a就會被回收智什,而此時如果匿名內部類(Thread)生命周期沒有結束动漾,那么在run()方法中訪問變量a就無法實現(xiàn)。所以Java通過復制的手段來避免這個問題荠锭。

這個過程是在編譯期間由編譯器默認進行旱眯,如果這個變量的值在編譯期間可以確定,則編譯器默認會在匿名內部類(局部內部類)的常量池中添加一個內容相等的字面量或直接將相應的字節(jié)碼嵌入到執(zhí)行字節(jié)碼中。這樣一來删豺,匿名內部類使用的變量是另一個局部變量共虑,只不過值和方法中局部變量的值相等,因此和方法中的局部變量完全獨立開呀页。

匿名內部類使用方法的參數(shù)

public class Outer{

    public void test(final int a) {
        new Inner() {
            public void innerMethod() {
                System.out.println(a);
        }
    }
    
    interface Inner{
        void innerMethod();
    }
}

從上代碼比較直觀的翻譯是:

public void test(final int a) {
    class Inner {
        public void innerMethod() {
            System.out.println(a);
        }
    }
    Inner inner = new Inner();
    inner.innerMethod();
}

從上面代碼可以認為內部類直接調用了參數(shù)a妈拌。其實Java編譯后內部類單獨放在自己的字節(jié)碼文件中,可以直觀的翻譯為:

public class Outer$Inner {
    public Outer$Inner(final int a) {
        this.Inner$a = a;
    }
    
    public void innerMethod() {
        System.out.println(this.Inner$a);
    }
}

從上面內部類的構造函數(shù)中可以看到蓬蝶,這里是將變量test方法中的形參a以參數(shù)的形式傳進來對匿名內部類中的拷貝(變量a的拷貝)進行賦值初始化尘分。內部的方法調用的實際是自己的屬性而不是外部類方法的參數(shù)。這么做的好處解決了上一節(jié)所說的生命周期的問題丸氛。

總結

也就說如果局部變量的值在編譯期間就可以確定培愁,則直接在匿名內部里面創(chuàng)建一個拷貝。如果局部變量的值無法在編譯期間確定雪位,則通過構造器傳參的方式來對拷貝進行初始化賦值竭钝。

方法參數(shù)或者局部變量和匿名內部類使用的變量看似是同一個梨撞,其實在匿名內部類中實行了拷貝操作雹洗,兩個并不是同一個變量。如果在內部類中修改了這個變量卧波,方法的參數(shù)或者局部變量并不會受到影響时肿,這樣就失去了一致性,這是程序猿不愿意看到的港粱。所以使用final來修飾螃成,保證它的不可變,達到變量的一致性查坪。

簡單理解就是寸宏,拷貝引用,為了避免引用值發(fā)生改變偿曙,例如被外部類的方法修改等氮凝,而導致內部類得到的值不一致,于是用final來讓該引用不可改變望忆。

參考

java提高篇(十四)-----關鍵字final

Java內部類的使用小結

Java內部類詳解

Java關鍵字static

Java中沒有全局變量的概念罩阵,但是可以通過static來實現(xiàn)“全局”的概念。static關鍵字可以用來修飾成員變量启摄,方法以及代碼塊稿壁。static關鍵字表示“全局”或者“靜態(tài)”的意思。

固定內存分配

靜態(tài)變量

Java類加載過程中有兩個階段對類變量初始化歉备。一個是在連接階段的準備部分中對類變量分配內存并設置JVM默認值傅是;另一個是類加載的最后階段,初始化,根據類變量的聲明進行賦值初始化或者在靜態(tài)代碼塊中執(zhí)行相應的賦值語句落午。

那么分配在哪塊內存中呢谎懦?在運行時數(shù)據區(qū)的方法區(qū)內。

方法區(qū)主要存儲已被虛擬機加載的類信息溃斋、常量界拦、靜態(tài)變量、即使編譯器編譯后的代碼等數(shù)據梗劫。

靜態(tài)方法

方法區(qū)會存儲即使編譯器編譯后的代碼享甸。

即使編譯器可以監(jiān)控經常執(zhí)行哪些方法代碼優(yōu)化這些代碼以提高速度。更為復雜的優(yōu)化是消除函數(shù)調用(即“內聯(lián)”)梳侨。即使編譯器知道哪些類已經加載蛉威。給予當前加載的類集,如果特定的函數(shù)不會被覆蓋走哺,就可以使用內聯(lián)蚯嫌。

摘抄自java核心技術,不是很理解丙躏。

由于靜態(tài)方法不能覆寫择示,所以它門也被分配在方法區(qū)中(final修飾的方法也不可覆寫,也分配在方法區(qū)晒旅?)栅盲。

總結

一旦類加載執(zhí)行完,JVM就可以方便地在方法區(qū)中就找到它們(類變量废恋,靜態(tài)方法谈秫,靜態(tài)代碼塊)。所以static修飾的對象鱼鼓,可以在類實例化之前調用拟烫,無需持有相應對象的引用。

特點

被static修飾的成員變量和成員方法是獨立于該類的迄本,它不依賴于某個特定的實例變量硕淑,也就是說它被該類的所有實例共享。即便創(chuàng)建無數(shù)個對象岸梨,也不會有靜態(tài)變量的副本喜颁。同時靜態(tài)方法無法被覆寫。

static變量

static變量曹阔,一般稱之為靜態(tài)變量半开,也可以稱為類變量。與之相對應的是實例變量赃份。它們兩者的區(qū)別在于:

對于靜態(tài)變量在內存中只有一個拷貝(節(jié)省內存)寂拆,JVM只為靜態(tài)分配一次內存奢米,在加載類的過程中完成靜態(tài)變量的內存分配,可用類名直接訪問(方便)纠永,當然也可以通過對象來訪問(不應該這么做鬓长,概念混淆)。

對于實例變量尝江,每創(chuàng)建一個實例涉波,就會為實例變量分配一次內存。實例變量可以在內存中有多個拷貝炭序,互不影響(靈活)啤覆。

static方法

靜態(tài)方法,可以通過類名直接調用惭聂,任何實例來調用窗声。所以靜態(tài)方法中不能使用this和super關鍵字。

靜態(tài)方法不能直接訪問實例變量辜纲,調用實例方法笨觅。可以通過創(chuàng)建對象后調用實例方法耕腾,實例變量(例如主方法中)见剩。

由于靜態(tài)方法不依賴任何實例,所以靜態(tài)方法必須實現(xiàn)幽邓,而不能是抽象的炮温。

靜態(tài)代碼塊

靜態(tài)代碼塊會在類加載最后階段初始化中執(zhí)行火脉,利用靜態(tài)代碼塊可以做一些初始化牵舵,例如類變量的賦值...

靜態(tài)方法的局限

  1. 它只能直接訪問靜態(tài)變量
  2. 它只能直接調用其他靜態(tài)方法
  3. 不能以任何形式引用this或者super
  4. 不能被覆寫

上述1,2兩點針對的是本類中的其他靜態(tài)方法和靜態(tài)變量倦挂。

public class Base {
    public static void method(int i) {
        System.out.println(i);
    }
}

public class Son extends Base {
    @Override
    public static void method(int i) {
        i += 1;
        System.out.println(i);
    }
}

編譯Son.java后畸颅,Console輸出:

靜態(tài)方法不能被覆寫.png

說明靜態(tài)方法不能被覆寫。

public class A {
    public static void method() {
        System.out.println("This method action in father");
    }
}

public class B extends A{
    public static void method() {
        System.out.println("This method action by son");
    }
}

public class Test {
    public static void main(String[] args) {
        //Son.method(20);
        A a = new B();
        a.method();
        B.method();
    }
}

但是這樣的代碼可以編譯通過方援,執(zhí)行測試類后没炒,Console輸出:This method action in father This method action by son

分析:覆寫指的是根據運行時對象來決定調用哪個方法,而不是根據編譯時的類型犯戏。

聲明為A類型的變量名存儲在棧中送火,而指向堆內存的卻是B的實例。如果調用變量a的非靜態(tài)方法先匪,解釋器會從堆內存中找到指向的B類型實例种吸,然后調用它的方法。而靜態(tài)方法屬于類方法呀非,在編譯階段就已經確定了它屬于A類的靜態(tài)方法坚俗,所以執(zhí)行的是A類的方法镜盯。所以達不到覆寫的效果。

總結猖败,靜態(tài)方法的覆寫只是形式上的速缆,實際上達不到覆寫的效果(也就是多態(tài)),只能隱藏(也就是通過子類類名調用靜態(tài)方法恩闻,執(zhí)行的是子類實現(xiàn)的方法)艺糜。而編譯器沒有報錯,是因為編譯器認為這是子類實現(xiàn)的新方法幢尚,如果加上注解@Override會去檢查父類是否有相同方法名的方法倦踢,由于靜態(tài)方法覆寫無效果,無法覆寫侠草,那么就無法編譯通過辱挥。

參考子類為什么不能重寫父類的靜態(tài)方法
可以重寫靜態(tài)方法嗎边涕?

一個實例對象有兩個類型:表明類型(Apparent Type)和實際類型(Actual Type)晤碘。表面類型是聲明時的類型,實際類型是對象創(chuàng)建時的類型功蜓。語句A a = new B();變量a表面類型是A园爷,實際類型是B。非靜態(tài)方法根據實際類型來執(zhí)行式撼,而對于靜態(tài)方法童社,通過對象來調用,JVM會通過表面類型查找到靜態(tài)方法入口來執(zhí)行著隆。

參考建議33: 不要覆寫靜態(tài)方法

參考

java提高篇(七)-----關鍵字static

Java關鍵字final扰楼、static使用總結

Static 關鍵字

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市美浦,隨后出現(xiàn)的幾起案子弦赖,更是在濱河造成了極大的恐慌,老刑警劉巖浦辨,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異流酬,居然都是意外死亡币厕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門芽腾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旦装,“玉大人,你說我怎么就攤上這事晦嵌⊥保” “怎么了拷姿?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旱函。 經常有香客問我响巢,道長,這世上最難降的妖魔是什么棒妨? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任踪古,我火速辦了婚禮,結果婚禮上券腔,老公的妹妹穿的比我還像新娘伏穆。我一直安慰自己,他們只是感情好纷纫,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布枕扫。 她就那樣靜靜地躺著,像睡著了一般辱魁。 火紅的嫁衣襯著肌膚如雪烟瞧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天染簇,我揣著相機與錄音参滴,去河邊找鬼。 笑死锻弓,一個胖子當著我的面吹牛砾赔,可吹牛的內容都是我干的。 我是一名探鬼主播青灼,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼暴心,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了聚至?” 一聲冷哼從身側響起酷勺,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤本橙,失蹤者是張志新(化名)和其女友劉穎扳躬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體甚亭,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡贷币,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了亏狰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片役纹。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖暇唾,靈堂內的尸體忽然破棺而出促脉,到底是詐尸還是另有隱情辰斋,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布瘸味,位于F島的核電站宫仗,受9級特大地震影響,放射性物質發(fā)生泄漏旁仿。R本人自食惡果不足惜藕夫,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枯冈。 院中可真熱鬧毅贮,春花似錦、人聲如沸尘奏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炫加。三九已至铸题,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間琢感,已是汗流浹背丢间。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驹针,地道東北人烘挫。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像柬甥,于是被迫代替她去往敵國和親饮六。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容