java reference用法 reference教學 - Java是“傳遞引用”還是“按值傳遞”?




15 Answers

Java始終是按值傳遞的 。 不幸的是,他們決定將對象的位置稱為“引用”。 當我們傳遞一個對象的值時,我們將引用傳遞給它。 這對初學者來說很困惑。

它是這樣的:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false 
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

在上面的示例中, aDog.getName()仍將返回"Max" 。 使用Dog "Fifi"在函數foo不更改main的值aDog ,因為對象引用按值傳遞。 如果它是通過引用傳遞的,那麼mainaDog.getName()將在調用foo後返回"Fifi"

同樣:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

在上面的例子中, Fifi是調用foo(aDog)之後的狗的名字,因為對象的名字是在foo(...)food上執行的任何操作都是這樣的,出於所有實際目的,它們是在aDog本身上執行的(除非d被更改為指向不同的Dog實例,例如d = new Dog("Boxer") )。

call by

我一直認為Java是傳遞引用的

但是,我看過一些博客文章(例如, 這個博客 )聲稱它不是。

我不認為我理解他們所做的區別。

解釋是什麼?




Java總是按值而不是通過引用傳遞參數。

讓我通過一個example解釋一下:

public class Main{
     public static void main(String[] args){
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }
     public static void changeReference(Foo a){
          Foo b = new Foo("b");
          a = b;
     }
     public static void modifyReference(Foo c){
          c.setAttribute("c");
     }
}

我將逐步解釋這個:

  1. 聲明名為fFoo類型的引用,並將其分配給類型為Foo的新對象,其屬性為"f"

    Foo f = new Foo("f");
    

  2. 從方法方面,聲明了具有名稱a的類型為Foo的引用,並且它最初被賦值為null

    public static void changeReference(Foo a)
    

  3. 當您調用方法changeReference ,引用a將被分配給作為參數傳遞的對象。

    changeReference(f);
    

  4. 聲明名為b的類型為Foo的引用,並將其分配給類型為Foo的新對象,其屬性為"b"

    Foo b = new Foo("b");
    

  5. a = b將引用a NOT f重新分配給其屬性為"b"的對象。

  6. 當您調用modifyReference(Foo c)方法時,將創建引用c並將其分配給具有屬性"f"的對象。

  7. c.setAttribute("c"); 將更改引用c指向它的對象的屬性,並且它是引用f指向它的同一對象。

我希望你現在明白如何將對像作為參數傳遞在Java中:)




Java總是按值傳遞,沒有例外。

那麼如何讓任何人都對此感到困惑,並相信Java是通過引用傳遞的,或者認為他們有一個Java作為傳遞參考的例子? 關鍵是Java在任何情況下都不會直接訪問對象本身的值。 對對象的唯一訪問是通過對該對象的引用 。 因為Java對象總是通過引用而不是直接訪問,所以通常將字段和變量以及方法參數作為對象進行討論 ,而當它們只是對對象的引用時這種混淆源於這種(嚴格來說,不正確的)命名法的變化。

所以,在調用方法時

  • 對於原始參數( intlong等),pass by value是原語的實際值 (例如,3)。
  • 對於對象,pass by value是對象引用的值。

因此,如果你有doSomething(foo)public void doSomething(Foo foo) { .. }那麼兩個Foos已經復制了指向相同對象的引用

當然,通過值傳遞對對象的引用看起來非常像(並且在實踐中無法區分)通過引用傳遞對象。




我覺得爭論“傳遞引用與傳遞價值”並不是非常有用。

如果你說“Java是任意的(參考/值)”,在任何一種情況下,你都沒有提供完整的答案。 這裡有一些額外的信息,有助於理解記憶中發生的事情。

在我們進入Java實現之前,堆棧/堆上的崩潰過程:值以一種非常有序的方式在堆棧中上下移動,就像在自助餐廳的堆棧一樣。 堆中的內存(也稱為動態內存)是雜亂無章的。 JVM只是在任何地方找到空間,並釋放它,因為不再需要使用它的變量。

好的。 首先,本地原語進入堆棧。 所以這段代碼:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

結果如下:

聲明和實例化對象時。 實際的對像在堆上。 什麼在堆棧上? 堆上對象的地址。 C ++程序員會將此稱為指針,但是一些Java開發人員反對“指針”這個詞。 隨你。 只要知道對象的地址就在堆棧上。

像這樣:

int problems = 99;
String name = "Jay-Z";

數組是一個對象,所以它也在堆上。 那陣列中的對象怎麼樣? 它們獲得自己的堆空間,每個對象的地址都在數組內部。

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

那麼,當你調用一個方法時會傳入什麼? 如果傳入一個對象,那麼實際傳入的是對象的地址。 有些人可能會說地址的“價值”,有些人說它只是對象的引用。 這是“參考”和“價值”支持者之間聖戰的起源。 你所說的並不像你理解的那樣重要,傳入的是對象的地址。

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

創建一個String,並在堆中分配空間,並將字符串的地址存儲在堆棧中並賦予標識符hisName ,因為第二個String的地址與第一個String的地址相同,因此不會創建新的String並且沒有分配新的堆空間,但是在堆棧上創建了新的標識符。 然後我們調用shout() :創建一個新的堆棧幀,並創建一個新的標識符, name並分配已存在的String的地址。

那麼,價值,參考? 你說“土豆”。




Java按值傳遞對象的引用。




我無法相信沒人提到Barbara Liskov。當她在1974年設計CLU時,她遇到了同樣的術語問題,並且通過共享(也稱為通過對象共享調用按對象調用)發明了這個特定情況的“通過值調用,其值為參考“。




在java中,所有內容都是引用,所以當你有類似的東西時:Point pnt1 = new Point(0,0);Java確實如下:

  1. 創建新的Point對象
  2. 創建新的Point引用並初始化該引用以指向先前創建的Point對象(參考)
  3. 從這裡開始,通過Point對像生命,您將通過pnt1引用訪問該對象。所以我們可以說在Java中你通過它的引用操作對象。

Java不通過引用傳遞方法參數; 它按值傳遞它們。我將使用此站點中的示例:

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

程序流程:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

使用兩個不同的引用關聯創建兩個不同的Point對象。

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

正如預期的輸出將是:

X1: 0     Y1: 0
X2: 0     Y2: 0

在這條線上'傳遞價值'進入遊戲......

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

參考文獻pnt1pnt2按值傳遞給棘手的方法,這意味著現在你參考pnt1,並pnt2有自己的copies命名arg1arg2。所以pnt1,並arg1 到同一個對象。(與pnt2和相同arg2

tricky方法中:

 arg1.x = 100;
 arg1.y = 100;

接下來的tricky方法

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

在這裡,您首先創建新的tempPoint引用,它將指向相同的位置,如arg1引用。然後您將參考arg1像同一個地方arg2的參考。最後arg2指向同一個地方temp

從這裡的範圍tricky方法走了,你沒有獲得任何更多的引用:arg1arg2temp但重要的是,當你在生活中使用這些引用時所做的一切都會永久地影響它們所指向的對象。

所以在執行方法之後tricky,當你返回時main,你有這種情況:

所以現在,完全執行程序將是:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0



Java總是按值傳遞,而不是通過引用傳遞

首先,我們需要了解通過值傳遞的內容以及通過引用傳遞的內容。

按值傳遞意味著您在內存中復制傳入的實際參數值。這是實際參數內容的副本

通過引用傳遞(也稱為按地址傳遞)意味著存儲實際參數的地址的副本

有時Java可以給出通過引用傳遞的錯覺。讓我們看看它是如何工作的,使用下面的例子:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

該程序的輸出是:

changevalue

讓我們一步一步地理解:

Test t = new Test();

眾所周知,它將在堆中創建一個對象並將引用值返回給t。例如,假設t的值是0x100234(我們不知道實際的JVM內部值,這只是一個例子)。

new PassByValue().changeValue(t);

將參考t傳遞給函數時,它不會直接傳遞對象測試的實際參考值,但會創建t的副本,然後將其傳遞給函數。由於它是通過值傳遞的,因此它傳遞變量的副本而不是它的實際引用。由於我們說t的值是0x100234,t和f都將具有相同的值,因此它們將指向同一個對象。

如果使用引用f更改函數中的任何內容,它將修改對象的現有內容。這就是我們獲得輸出的原因changevalue,該輸出在函數中更新。

為了更清楚地理解這一點,請考慮以下示例:

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

這會拋出NullPointerException嗎?不,因為它只傳遞了引用的副本。在通過引用傳遞的情況下,它可能拋出一個NullPointerException,如下所示:

希望這會有所幫助。




不,它不是通過引用傳遞。

根據Java語言規範,Java是按值傳遞的:

當調用方法或構造函數(第15.12節)時,實際參數表達式的值在執行方法體或構造函數體之前初始化新創建的參數變量,每個聲明的類型。出現在DeclaratorId中的標識符可以用作方法或構造函數體中的簡單名稱來引用形式參數




您永遠不能通過Java中的引用傳遞,並且一種明顯的方法是當您想要從方法調用返回多個值時。考慮C ++中的以下代碼:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

有時你想在Java中使用相同的模式,但你不能; 至少不是直接的。相反,你可以做這樣的事情:

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

正如之前的答案中所解釋的那樣,在Java中,您將指向數組的指針作為值傳入getValues。這就足夠了,因為該方法然後修改數組元素,按照慣例,您期望元素0包含返回值。顯然,您可以通過其他方式執行此操作,例如構造代碼以使其不必要,或者構造可以包含返回值或允許設置它的類。但是,在C ++中可用的簡單模式在Java中不可用。




我想我會提供這個答案,以便從規格中添加更多細節。

首先,通過引用傳遞與傳遞值之間的區別是什麼?

通過引用傳遞意味著被調用函數的參數將與調用者的傳遞參數相同(不是值,而是標識 - 變量本身)。

按值傳遞意味著被調用函數的參數將是調用者傳遞的參數的副本。

或者來自維基百科,關於傳遞參考的主題

在逐個引用的評估(也稱為pass-by-reference)中,函數接收對用作參數的變量的隱式引用,而不是其值的副本。這通常意味著函數可以修改(即賦值)用作參數的變量 - 其調用者將看到的內容。

對主題傳遞的價值

在call-by-value中,計算參數表達式,並將結果值綁定到函數[...]中的相應變量。如果函數或過程能夠為其參數賦值,則僅指定其本地副本[...]。

其次,我們需要知道Java在其方法調用中使用了什麼。在Java語言規範狀態

當調用方法或構造函數(第15.12節)時,實際參數表達式的值在執行方法體或構造函數體之前初始化新創建的參數變量,每個聲明的類型。

因此它將參數的值賦予(或綁定)到相應的參數變量。

論證的價值是什麼?

讓我們考慮引用類型,在Java虛擬機規範狀態

有三種引用類型:類類型,數組類型和接口類型。它們的值分別是對動態創建的類實例,數組或類實例或實現接口的數組的引用。

Java語言規範還規定

引用值(通常只是引用)是指向這些對象的指針,以及一個特殊的空引用,它指的是沒有對象。

參數(某種引用類型)的值是指向對象的指針。請注意,變量,具有引用類型返回類型的方法的調用以及實例創建表達式(new ...)都將解析為引用類型值。

所以

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

all將String實例引用的值綁定到方法新創建的參數中param。這正是pass-by-value的定義所描述的。因此,Java是按值傳遞的

您可以按照引用來調用方法或訪問引用對象的字段這一事實與對話完全無關。傳遞參考的定義是

這通常意味著函數可以修改(即賦值)用作參數的變量 - 其調用者將看到的內容。

在Java中,修改變量意味著重新分配它。在Java中,如果您在方法中重新分配了變量,那麼調用者就不會注意到它。修改變量引用的對象完全是一個不同的概念。

here還在Java虛擬機規範中定義了原始值。該類型的值是相應的積分或浮點值,適當編碼(8,16,32,64等位)。




正如許多人之前提到的那樣,Java總是按價值傳遞

這是另一個幫助您理解差異的示例經典交換示例):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

打印:

之前:a = 2,b = 3
之後:a = 2,b = 3

發生這種情況是因為iA和iB是新的局部引用變量,它們具有相同的傳遞引用值(它們分別指向a和b)。因此,嘗試更改iA或iB的引用只會在本地範圍內更改,而不會在此方法之外。




在Java中,只傳遞引用並按值傳遞:

Java參數都是按值傳遞的(當方法使用時會復制引用):

對於基本類型,Java行為很簡單:將值複製到基本類型的另一個實例中。

在Objects的情況下,這是相同的:對像變量是僅包含使用“new”關鍵字創建的Object 地址的指針(桶),並且像原始類型一樣被複製。

行為可能與原始類型不同:因為複制的對像變量包含相同的地址(對於同一個對象)對象的內容/成員可能仍然在方法中被修改並且稍後訪問外部,給出了(包含)對象的錯覺本身是通過引用傳遞的。

“字符串”對像似乎是城市傳說中“對象通過引用傳遞” 的完美反例

實際上,在一個永遠不可能的方法中,更新作為參數傳遞的String的值:

String對象,由聲明為final的數組保存,不能修改。只有Object的地址可能被另一個使用“new”替換。使用“new”更新變量,不會讓Object從外部訪問,因為變量最初是按值傳遞並複制的。




我已經為here任何編程語言創建了一個致力於這類問題的線程。here

還提到了Java。以下是簡短摘要:

  • Java按值傳遞參數
  • “by value”是java將參數傳遞給方法的唯一方法
  • 使用作為參數給出的對象的方法將改變對象,因為引用指向原始對象。(如果該方法本身改變某些值)



簡而言之,Java對象具有一些非常特殊的屬性。

一般來說,Java有原始類型(intboolchardouble,等等)直接由值來傳遞。然後Java有對象(從中派生出來的所有東西java.lang.Object)。實際上,對象總是通過引用來處理(引用是一個你無法觸摸的指針)。這意味著實際上,對像是通過引用傳遞的,因為引用通常不是很有趣。但它確實意味著您無法更改指向的對象,因為引用本身是按值傳遞的。

這聽起來有點奇怪和令人困惑嗎?讓我們考慮C如何通過引用傳遞並按值傳遞。在C中,默認約定是按值傳遞。void foo(int x)按值傳遞int。void foo(int *x)是一個不需要的函數int a,而是一個指向int的指針:foo(&a)。可以使用它與&運算符傳遞變量地址。

把它帶到C ++,我們有參考。引用基本上(在此上下文中)隱藏等式的指針部分的語法糖:void foo(int &x)被調用foo(a),其中編譯器本身知道它是引用,並且a應該傳遞非引用的地址。在Java中,所有引用對象的變量實際上都是引用類型,實際上強制通過引用調用大多數意圖和目的,而沒有由例如C ++提供的細粒度控制(和復雜性)。




Related