Java內存管理-Stackoverflow問答-Java是傳值仍是傳引用?(十一)

作一個積極的人java

編碼、改bug、提高本身編程

我有一個樂園,面向編程,春暖花開!設計模式

推薦閱讀數組

第一季

0、Java的線程安全、單例模式、JVM內存結構等知識梳理安全

一、Java內存管理-程序運行過程(一)微信

二、Java內存管理-初始JVM和JVM啓動流程(二)多線程

三、Java內存管理-JVM內存模型以及JDK7和JDK8內存模型對比總結(三)學習

四、Java內存管理-掌握虛擬機類加載機制(四)this

五、Java內存管理-掌握虛擬機類加載器(五)編碼

六、Java內存管理-類加載器的核心源碼和設計模式(六)

七、Java內存管理-掌握自定義類加載器的實現(七)
第一季總結:由淺入深JAVA內存管理 Core Story

第二季

八、Java內存管理-愚人節new一個對象送給你(八)

【福利】JVM系列學習資源無套路贈送

九、Java內存管理-」一文掌握虛擬機建立對象的祕密」(九)

十、Java內存管理-你真的理解Java中的數據類型嗎(十)

十一、Java內存管理-Stackoverflow問答-Java是傳值仍是傳引用?(十一)

十二、Java內存管理-探索Java中字符串String(十二)

實戰

一文學會Java死鎖和CPU 100% 問題的排查技巧

分享一位老師的人工智能教程。零基礎!通俗易懂!風趣幽默!你們能夠看看是否對本身有幫助,點擊這裏查看【人工智能教程】。接下來進入正文。

勿在流沙築高臺,出來混早晚要還的。

本文導圖:

分享一位老師的人工智能教程。零基礎!通俗易懂!風趣幽默!你們能夠看看是否對本身有幫助,點擊這裏查看【人工智能教程】。接下來進入正文。

@[toc]

1、由一個提問引起的思考

Stack Overflow 看到這樣一個問題:

Is Java 「pass-by-reference」 or 「pass-by-value」?

翻譯成中文:

Java是傳值仍是傳引用?

請先不要看下面的內容,思考10秒後,在繼續閱讀!!!

爲何建議先思考,在閱讀內容呢?

咱們天天可能會利用碎片化的時間閱讀不少內容,有不少信息和知識其實在大腦過一下,而後就忘記了!如何才能高效的利用碎片化時間掌握或者記住更多的內容和知識,我本身碎片化閱讀的理解和技巧:閱讀一篇本身感興趣技術文章,在時間容許的時間下,必定是一次性閱讀完,在閱讀中帶着本身的問題,閱讀後有本身的簡單總結。 千萬不要閱讀 一段內容,看到微信有人發消息給你,就切換聊天框回覆消息,而後回覆完再切換回來閱讀技術文章。這種 大腦 的切換是須要耗費資源的,也影響閱讀的效果和效率(大腦在多個任務切換相似cpu多線程調度,線程的頻繁切換。就如多線程不必定能提供效率,頻繁的線程/任務切換耗費cpu大量資源)!

扯遠 了,切換回到本文正題,Java是傳值仍是傳引用?

我相信你閱讀完本篇後必定可以回答上面的問題,而且在工做在寫相似傳參的代碼也會有更深刻的理解。開啓探索之旅,Let's go!

2、爲何有傳值仍是傳引用的說法

在Java程序中會包含方法,方法會分爲方法聲明和方法實現,在方法聲明中又有參數列表,參數根據調用後的效果不一樣,也就是是否改變參數的原始值,能夠劃分爲兩種: 按值傳遞參數和按引用傳遞參數。

  • 按值傳遞參數 == 傳值
  • 按引用傳遞參數 == 傳引用

也就是以前介紹過的Java的基本類型和引用類型,若是方法參數中傳遞的基本類型就認爲是 按值傳遞(傳值),方法參數中傳遞的是引用類型,就稱之爲按引用傳遞(傳引用)。

3、圖解傳值和傳引用過程

一段簡單的代碼:

public class PrettyGirl {
    /**
     * 芳齡幾何
     */
    int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static void main(String[] args) {

        // 引用類型
        PrettyGirl prettyGirl = new PrettyGirl();
        prettyGirl.setAge(28);
        // 基本類型
        int num = 50;
        // 數組arrs也引用類型
        int[] arrs = new int[]{2,0,1,9};

        System.out.println("mian 中 num  = " + num);
        System.out.println("mian 中 arrs[3] = " + arrs[3]);
        System.out.println("mian 中 prettyGirl.getAge() = " + prettyGirl.getAge());
        System.out.println("-----------------------------------------");

        // 調用 change方法
        prettyGirl.change(num, arrs, prettyGirl);

        System.out.println("調用change 後 mian 中 num  = " + num);
        System.out.println("調用change 後 mian 中 arrs[3] = " + arrs[3]);
        System.out.println("調用change 後 mian 中 prettyGirl.getAge() = " + prettyGirl.getAge());

    }

    public void change(int pnum, int[] parrs, PrettyGirl ppg) {
        //在change中 改變值類型pnum的值
        pnum = pnum + 50;
        //在change中 改變引用類型 parrs,ppg 的值
        parrs[3] = 8;
        // 從28變18
        ppg.setAge(18);

        System.out.println("change 中 pnum  = " + pnum);
        System.out.println("change 中 parrs[3] = " + parrs[3]);
        System.out.println("change 中 ppg.getAge() = " + ppg.getAge());
        System.out.println("-----------------------------------------");
    }

}複製代碼

思考一下,打印的結果是什麼?

打印結果以下

mian 中 num  = 50
mian 中 arrs[3] = 9
mian 中 prettyGirl.getAge() = 28
-----------------------------------------
change 中 pnum  = 100
change 中 parrs[3] = 8
change 中 ppg.getAge() = 18
-----------------------------------------
調用change 後 mian 中 num  = 50
調用change 後 mian 中 arrs[3] = 8
調用change 後 mian 中 prettyGirl.getAge() = 18複製代碼

下面開啓分析之旅,結合以前學過的Java內存模型來畫上面代碼執行的內存變化的圖

注:下圖只是爲了演示講解說明,真實內存地址不必定是這樣!

int 類型變量num在棧中分配一塊內存,而 parrs 與 ppg 分配兩塊內存,棧中一塊,堆中一塊。當調用change方法時,建立三個變量 pnum,parrs,ppg這裏至關於把棧中的數據全備份一份給這三個數值,則有

在change方法中對傳遞的參數進行修改,此時pnum的值修改成 100,堆中ppg指向的對象年齡由28改成18,數組中parrs[3]修改成8。也就是 ppg與 parrs 改變了堆中的具體數值,而 pnum 改變的只是棧中的數值。

最後,當change方法調用結束,change棧幀被彈出,則對應的pnum,ppg,parrs 三個變量也消亡,此時只有main棧幀狀況以下圖:

經過上圖的演示,上面代碼的打印結果就很清晰明瞭了。

tips:回顧 java 棧

Java棧中存放的是一個個的棧幀,每一個棧幀對應一個被調用的方法,在棧幀中包括局部變量表(Local Variables)、操做數棧(Operand Stack)、指向當前方法所屬的類的運行時常量池(運行時常量池的概念在方法區部分會談到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。當線程執行一個方法時,就會隨之建立一個對應的棧幀,並將創建的棧幀壓棧。當方法執行完畢以後,便會將棧幀出棧。所以可知,線程當前執行的方法所對應的棧幀一定位於Java棧的頂部。

根據上面例子中這樣的內存變換,想必你應該知道按值傳遞與按引用傳遞的深層緣由了吧!

從上圖中看全部的參數傳遞 本質都是按址值傳遞, 也就是內存地址的值 。 基本類型由於棧內存地址中保存就是其自己值,全部在參數傳遞的時候,拷貝自己的值進行傳遞,而引用類型在棧內存地址中保存的是引用的值,經過棧內存保存引用的值指向堆中獲取對象真是的值,在參數傳遞的時候,拷貝的是引用的值。

全部在方法傳遞參數後,若是基本類型的值在傳遞的方法中有修改,不會影響傳遞前方法中的值。而引用類型就不一樣了,由於它修改的是引用地址指向堆中數據,這部分數據在參數傳遞的時候不會拷貝一份,就如上面圖解標識出的同樣。

4、本文總結

在Java中,對象經過引用傳遞,基本類型按值傳遞。

這句話的描述有一半是不許確的。就如上面咱們圖中看到的那樣基本類型是按照值傳遞的; 引用類型是拷貝引用的址值傳遞的,也即對象經過引用傳遞。正確的描述語句是對象引用也是按值傳遞

其實在Java語言規範(JLS)中描述:Java 嚴格按值傳遞,能夠理解與C徹底相同,也就是Java中參數傳遞的本質是按址值傳遞。

若是你在閱讀完本篇後,對上面問題有本身的深刻的理解,有歡迎文末留言一塊兒探討!

備註: 因爲本人能力有限,文中如有錯誤之處,歡迎指正。

參考文章

Is Java 「pass-by-reference」 or 「pass-by-value」?

Java is Pass-by-Value, Dammit!

謝謝你的閱讀,若是您以爲這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到!祝你天天開心愉快!

Java編程技術樂園:一個分享編程知識的公衆號。跟着老司機一塊兒學習乾貨技術知識,天天進步一點點,讓小的積累,帶來大的改變!

掃描關注,後臺回覆【資源】,獲取珍藏乾貨! 99.9%的夥伴都很喜歡

image.png | center| 747x519

© 天天都在變得更好的阿飛雲
相關文章
相關標籤/搜索