成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

探究final在java中的作用

番茄西紅柿 / 2979人閱讀

摘要:關(guān)鍵字的字面意思是最終的不可修改的這似乎是一個(gè)看見(jiàn)名字就大概能知道怎么用的語(yǔ)法但你是否有深究過(guò)在各個(gè)場(chǎng)景中的具體使用方法注意事項(xiàng)以及背后涉及的設(shè)計(jì)思想呢一修飾變量基礎(chǔ)修飾基本數(shù)據(jù)類型變量和引用數(shù)據(jù)類型變量相信大家都具備基本的常識(shí)被修飾的變量

final關(guān)鍵字的字面意思是最終的, 不可修改的. 這似乎是一個(gè)看見(jiàn)名字就大概能知道怎么用的語(yǔ)法, 但你是否有深究過(guò)final在各個(gè)場(chǎng)景中的具體使用方法, 注意事項(xiàng), 以及背后涉及的Java設(shè)計(jì)思想呢);

 

一. final修飾變量 1. 基礎(chǔ): final修飾基本數(shù)據(jù)類型變量和引用數(shù)據(jù)類型變量.

相信大家都具備基本的常識(shí): 被final修飾的變量是不能夠被改變的. 但是這里的"不能夠被改變"對(duì)于不同的數(shù)據(jù)類型是有不同的含義的.

當(dāng)final修飾的是一個(gè)基本數(shù)據(jù)類型數(shù)據(jù)時(shí), 這個(gè)數(shù)據(jù)的值在初始化后將不能被改變; 當(dāng)final修飾的是一個(gè)引用類型數(shù)據(jù)時(shí), 也就是修飾一個(gè)對(duì)象時(shí), 引用在初始化后將永遠(yuǎn)指向一個(gè)內(nèi)存地址, 不可修改. 但是該內(nèi)存地址中保存的對(duì)象信息, 是可以進(jìn)行修改的.

上一段話可能比較抽象, 希望下面的圖能有助于你理解, 你會(huì)發(fā)現(xiàn)雖說(shuō)有不同的含義, 但本質(zhì)還是一樣的.

首先是final修飾基本數(shù)據(jù)類型時(shí)的內(nèi)存示意圖

如上圖, 變量a在初始化后將永遠(yuǎn)指向003這塊內(nèi)存, 而這塊內(nèi)存在初始化后將永遠(yuǎn)保存數(shù)值100.

下面是final修飾引用數(shù)據(jù)類型的示意圖

在上圖中, 變量p指向了0003這塊內(nèi)存, 0003內(nèi)存中保存的是對(duì)象p的句柄(存放對(duì)象p數(shù)據(jù)的內(nèi)存地址), 這個(gè)句柄值是不能被修改的, 也就是變量p永遠(yuǎn)指向p對(duì)象. 但是p對(duì)象的數(shù)據(jù)是可以修改的.

// 代碼示例
public static void main(String[] args) {
    final Person p = new Person(20, "炭燒生蠔");
    p.setAge(18);   //可以修改p對(duì)象的數(shù)據(jù)
    System.out.println(p.getAge()); //輸出18

    Person pp = new Person(30, "蠔生燒炭");
    p = pp; //這行代碼會(huì)報(bào)錯(cuò), 不能通過(guò)編譯, 因?yàn)閜經(jīng)final修飾永遠(yuǎn)指向上面定義的p對(duì)象, 不能指向pp對(duì)象. 
}

不難看出final修飾變量的本質(zhì): final修飾的變量會(huì)指向一塊固定的內(nèi)存, 這塊內(nèi)存中的值不能改變.

引用類型變量所指向的對(duì)象之所以可以修改, 是因?yàn)橐米兞坎皇侵苯又赶驅(qū)ο蟮臄?shù)據(jù), 而是指向?qū)ο蟮囊玫? 所以被final修飾的引用類型變量將永遠(yuǎn)指向一個(gè)固定的對(duì)象, 不能被修改; 對(duì)象的數(shù)據(jù)值可以被修改.

 

2. 進(jìn)階: 被final修飾的常量在編譯階段會(huì)被放入常量池中

final是用于定義常量的, 定義常量的好處是: 不需要重復(fù)地創(chuàng)建相同的變量. 而常量池是Java的一項(xiàng)重要技術(shù), 由final修飾的變量會(huì)在編譯階段放入到調(diào)用類的常量池中.

請(qǐng)看下面這段演示代碼. 這個(gè)示例是專門(mén)為了演示而設(shè)計(jì)的, 希望能方便大家理解這個(gè)知識(shí)點(diǎn).

public static void main(String[] args) {
    int n1 = 2019;          //普通變量
    final int n2 = 2019;    //final修飾的變量

    String s = "20190522";  
    String s1 = n1 + "0522";	//拼接字符串"20190512"
    String s2 = n2 + "0522";	

    System.out.println(s == s1);	//false
    System.out.println(s == s2);	//true
}

首先要介紹一點(diǎn): 整數(shù)-127-128是默認(rèn)加載到常量池里的, 也就是說(shuō)如果涉及到-127-128的整數(shù)操作, 默認(rèn)在編譯期就能確定整數(shù)的值. 所以這里我故意選用數(shù)字2019(大于128), 避免數(shù)字默認(rèn)就存在常量池中.

上面的代碼運(yùn)作過(guò)程是這樣的:

首先根據(jù)final修飾的常量會(huì)在編譯期放到常量池的原則, n2會(huì)在編譯期間放到常量池中.

然后s變量所對(duì)應(yīng)的"20190522"字符串會(huì)放入到字符串常量池中, 并對(duì)外提供一個(gè)引用返回給s變量.

這時(shí)候拼接字符串s1, 由于n1對(duì)應(yīng)的數(shù)據(jù)沒(méi)有放入常量池中, 所以s1暫時(shí)無(wú)法拼接, 需要等程序加載運(yùn)行時(shí)才能確定s1對(duì)應(yīng)的值.

但在拼接s2的時(shí)候, 由于n2已經(jīng)存在于常量池, 所以可以直接與"0522"拼接, 拼接出的結(jié)果是"20190522". 這時(shí)系統(tǒng)會(huì)查看字符串常量池, 發(fā)現(xiàn)已經(jīng)存在字符串20190522, 所以直接返回20190522的引用. 所以s2和s指向的是同一個(gè)引用, 這個(gè)引用指向的是字符串常量池中的20190522.

 

當(dāng)程序執(zhí)行時(shí), n1變量才有具體的指向.

當(dāng)拼接s1的時(shí)候, 會(huì)創(chuàng)建一個(gè)新的String類型對(duì)象, 也就是說(shuō)字符串常量池中的20190522會(huì)對(duì)外提供一個(gè)新的引用.

所以當(dāng)s1與s用"=="判斷時(shí), 由于對(duì)應(yīng)的引用不同, 會(huì)返回false. 而s2和s指向同一個(gè)引用, 返回true.

總結(jié): 這個(gè)例子想說(shuō)明的是: 由于被final修飾的常量會(huì)在編譯期進(jìn)入常量池, 如果有涉及到該常量的操作, 很有可能在編譯期就已經(jīng)完成.

 

3. 探索: 為什么局部/匿名內(nèi)部類在使用外部局部變量時(shí), 只能使用被final修飾的變量);

提示: 在JDK1.8以后, 通過(guò)內(nèi)部類訪問(wèn)外部局部變量時(shí), 無(wú)需顯式把外部局部變量聲明為final. 不是說(shuō)不需要聲明為final了, 而是這件事情在編譯期間系統(tǒng)幫我們做了. 但是我們還是有必要了解為什么要用final修飾外部局部變量.

public class Outter {
    public static void main(String[] args) {
        final int a = 10;
        new Thread(){
            @Override
            public void run() {
                System.out.println(a);
            }
        }.start();
    }
}

在上面這段代碼, 如果沒(méi)有給外部局部變量a加上final關(guān)鍵字, 是無(wú)法通過(guò)編譯的. 可以試著想想: 當(dāng)main方法已經(jīng)執(zhí)行完后, main方法的棧幀將會(huì)彈出, 如果此時(shí)Thread對(duì)象的生命周期還沒(méi)有結(jié)束, 還沒(méi)有執(zhí)行打印語(yǔ)句的話, 將無(wú)法訪問(wèn)到外部的a變量.

那么為什么加上final關(guān)鍵字就能正常編譯呢);

我們可以先通過(guò)javac編譯得到.class文件(用IDE編譯也可以), 然后在命令行輸入javap -c .class文件的絕對(duì)路徑, 就能查看.class文件的反編譯代碼. 以上的Outter類經(jīng)過(guò)編譯產(chǎn)生兩個(gè).class文件, 分別是Outter.class和Outter$1.class, 也就是說(shuō)內(nèi)部類會(huì)多帶帶編譯成一個(gè).class文件. 下面給出Outter$1.class的反編譯代碼.

Compiled from "Outter.java"
final class forTest.Outter$1 extends java.lang.Thread {
  forTest.Outter$1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Thread."":()V
       4: return

  public void run();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: bipush        10
       5: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
       8: return
}

定位到run()方法反編譯代碼中的第3行:

3: bipush 10

我們看到a的值在內(nèi)部類的run()方法執(zhí)行過(guò)程中是以壓棧的形式存儲(chǔ)到本地變量表中的, 也就是說(shuō)在內(nèi)部類打印變量a的值時(shí), 這個(gè)變量a不是外部的局部變量a, 因?yàn)槿绻峭獠烤植孔兞康脑? 應(yīng)該會(huì)使用load指令加載變量的值. 也就是說(shuō)系統(tǒng)以拷貝的形式把外部局部變量a復(fù)制了一個(gè)副本到內(nèi)部類中, 內(nèi)部類有一個(gè)變量指向外部變量a所指向的值.

 

但研究到這里好像和final的關(guān)系還不是很大, 不加final似乎也可以拷貝一份變量副本, 只不過(guò)不能在編譯期知道變量的值罷了. 這時(shí)該思考一個(gè)新問(wèn)題了: 現(xiàn)在我們知道內(nèi)部類的變量a和外部局部變量a是兩個(gè)完全不同的變量, 那么如果在執(zhí)行run()方法的過(guò)程中, 內(nèi)部類中修改了a變量所指向的值, 就會(huì)產(chǎn)生數(shù)據(jù)不一致問(wèn)題.

正因?yàn)槲覀兊脑馐莾?nèi)部類和外部類訪問(wèn)的是同一個(gè)a變量, 所以當(dāng)在內(nèi)部類中使用外部局部變量的時(shí)候應(yīng)該用final修飾局部變量, 這樣局部變量a的值就永遠(yuǎn)不會(huì)改變, 也避免了數(shù)據(jù)不一致問(wèn)題的發(fā)生.

 

二. final修飾方法

使用final修飾方法有兩個(gè)作用, 首要作用是鎖定方法, 不讓任何繼承類對(duì)其進(jìn)行修改.

另外一個(gè)作用是在編譯器對(duì)方法進(jìn)行內(nèi)聯(lián), 提升效率. 但是現(xiàn)在已經(jīng)很少這么使用了, 近代的Java版本已經(jīng)把這部分的優(yōu)化處理得很好了. 但是為了滿足求知欲還是了解一下什么是方法內(nèi)斂.

方法內(nèi)斂: 當(dāng)調(diào)用一個(gè)方法時(shí), 系統(tǒng)需要進(jìn)行保存現(xiàn)場(chǎng)信息, 建立棧幀, 恢復(fù)線程等操作, 這些操作都是相對(duì)比較耗時(shí)的. 如果使用final修飾一個(gè)了一個(gè)方法a, 在其他調(diào)用方法a的類進(jìn)行編譯時(shí), 方法a的代碼會(huì)直接嵌入到調(diào)用a的代碼塊中.

//原代碼
public static void test(){
    String s1 = "包夾方法a";
    a();
    String s2 = "包夾方法a";
}

public static final void a(){
    System.out.println("我是方法a中的代碼");
    System.out.println("我是方法a中的代碼");
}

//經(jīng)過(guò)編譯后
public static void test(){
    String s1 = "包夾方法a";
    System.out.println("我是方法a中的代碼");
    System.out.println("我是方法a中的代碼");
    String s2 = "包夾方法a";
}

在方法非常龐大的時(shí)候, 這樣的內(nèi)嵌手段是幾乎看不到任何性能上的提升的, 在最近的Java版本中,不需要使用final方法進(jìn)行這些優(yōu)化了. --《Java編程思想》

 

三. final修飾類

使用final修飾類的目的簡(jiǎn)單明確: 表明這個(gè)類不能被繼承.

當(dāng)程序中有永遠(yuǎn)不會(huì)被繼承的類時(shí), 可以使用final關(guān)鍵字修飾

被final修飾的類所有成員方法都將被隱式修飾為final方法.

   

參考資料

www.cnblogs.com/ChenLLang/p…

www.cnblogs.com/xrq730/p/48…

gitbook.cn/books/5c6e1…

www.cnblogs.com/xrq730/p/48…

www.cnblogs.com/dolphin0520…

www.cnblogs.com/dolphin0520…


 

最后歡迎關(guān)注我的免費(fèi)知識(shí)星球, 我會(huì)在星球中持續(xù)更新系統(tǒng)的Java后端面試題分析, 將會(huì)囊括Java基礎(chǔ)知識(shí)到主流框架原理. 還會(huì)分享關(guān)于編程的趣味漫畫(huà).

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7169.html

相關(guān)文章

  • 探究finaljava中的作用

    摘要:關(guān)鍵字的字面意思是最終的不可修改的這似乎是一個(gè)看見(jiàn)名字就大概能知道怎么用的語(yǔ)法但你是否有深究過(guò)在各個(gè)場(chǎng)景中的具體使用方法注意事項(xiàng)以及背后涉及的設(shè)計(jì)思想呢一修飾變量基礎(chǔ)修飾基本數(shù)據(jù)類型變量和引用數(shù)據(jù)類型變量相信大家都具備基本的常識(shí)被修飾的變量 final關(guān)鍵字的字面意思是最終的, 不可修改的. 這似乎是一個(gè)看見(jiàn)名字就大概能知道怎么用的語(yǔ)法, 但你是否有深究過(guò)final在各個(gè)場(chǎng)景中的具體使用方法...

    Baaaan 評(píng)論0 收藏0
  • 探究finaljava中的作用

    摘要:關(guān)鍵字的字面意思是最終的不可修改的這似乎是一個(gè)看見(jiàn)名字就大概能知道怎么用的語(yǔ)法但你是否有深究過(guò)在各個(gè)場(chǎng)景中的具體使用方法注意事項(xiàng)以及背后涉及的設(shè)計(jì)思想呢一修飾變量基礎(chǔ)修飾基本數(shù)據(jù)類型變量和引用數(shù)據(jù)類型變量相信大家都具備基本的常識(shí)被修飾的變量 final關(guān)鍵字的字面意思是最終的, 不可修改的. 這似乎是一個(gè)看見(jiàn)名字就大概能知道怎么用的語(yǔ)法, 但你是否有深究過(guò)final在各個(gè)場(chǎng)景中的具體使用方法...

    番茄西紅柿 評(píng)論0 收藏0
  • Java中線程池ThreadPoolExecutor原理探究

    摘要:類型是位二進(jìn)制標(biāo)示,其中高位用來(lái)表示線程池狀態(tài),后面位用來(lái)記錄線程池線程個(gè)數(shù)。創(chuàng)建一個(gè)最小線程個(gè)數(shù)為,最大為,阻塞隊(duì)列為的線程池。 一、 前言 線程池主要解決兩個(gè)問(wèn)題:一方面當(dāng)執(zhí)行大量異步任務(wù)時(shí)候線程池能夠提供較好的性能,這是因?yàn)槭褂镁€程池可以使每個(gè)任務(wù)的調(diào)用開(kāi)銷減少(因?yàn)榫€程池線程是可以復(fù)用的)。另一方面線程池提供了一種資源限制和管理的手段,比如當(dāng)執(zhí)行一系列任務(wù)時(shí)候?qū)€程的管理,每個(gè)...

    lavor 評(píng)論0 收藏0
  • Java中線程池ThreadPoolExecutor原理探究

    摘要:類型是位二進(jìn)制標(biāo)示,其中高位用來(lái)表示線程池狀態(tài),后面位用來(lái)記錄線程池線程個(gè)數(shù)。創(chuàng)建一個(gè)最小線程個(gè)數(shù)為,最大為,阻塞隊(duì)列為的線程池。 一、 前言 線程池主要解決兩個(gè)問(wèn)題:一方面當(dāng)執(zhí)行大量異步任務(wù)時(shí)候線程池能夠提供較好的性能,這是因?yàn)槭褂镁€程池可以使每個(gè)任務(wù)的調(diào)用開(kāi)銷減少(因?yàn)榫€程池線程是可以復(fù)用的)。另一方面線程池提供了一種資源限制和管理的手段,比如當(dāng)執(zhí)行一系列任務(wù)時(shí)候?qū)€程的管理,每個(gè)...

    zzir 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<