Câu hỏi Java là "pass-by-reference" hay "pass-by-value"?


Tôi luôn nghĩ Java là thông qua tham chiếu.

Tuy nhiên, tôi đã thấy một vài bài đăng trên blog (ví dụ: blog này) tuyên bố rằng nó không phải là.

Tôi không nghĩ rằng tôi hiểu sự khác biệt mà họ đang làm.

Giải thích là gì?


5495


gốc


Tôi tin rằng phần lớn sự nhầm lẫn về vấn đề này phải làm với thực tế là những người khác nhau có định nghĩa khác nhau của thuật ngữ "tham chiếu". Mọi người đến từ nền C ++ giả định rằng "tham chiếu" phải có ý nghĩa trong C ++, mọi người từ nền C giả định "tham chiếu" phải giống như "con trỏ" trong ngôn ngữ của họ, v.v. Cho dù đó là đúng khi nói rằng Java đi qua tham chiếu thực sự phụ thuộc vào những gì có nghĩa là "tham khảo". - Gravity
Tôi cố gắng sử dụng thuật ngữ liên tục tại Chiến lược đánh giá bài báo. Cần lưu ý rằng, mặc dù bài viết chỉ ra các điều khoản khác nhau rất nhiều bởi cộng đồng, nó nhấn mạnh rằng ngữ nghĩa cho call-by-value và call-by-reference khác nhau theo cách rất quan trọng. (Cá nhân tôi thích sử dụng call-by-object-sharing những ngày này qua call-by-value[-of-the-reference], vì điều này mô tả ngữ nghĩa ở mức cao và không tạo ra xung đột call-by-value, cái nào Là triển khai cơ bản.)
@ Trọng lực: Bạn có thể đi và đặt bình luận của bạn trên một bảng quảng cáo HUGE hoặc một cái gì đó? Đó là toàn bộ vấn đề trong một nutshell. Và nó cho thấy toàn bộ điều này là ngữ nghĩa. Nếu chúng tôi không đồng ý về định nghĩa cơ sở của một tham chiếu, thì chúng tôi sẽ không đồng ý với câu trả lời cho câu hỏi này :) - MadConan
Tôi nghĩ sự nhầm lẫn là "vượt qua tham chiếu" so với "ngữ nghĩa tham chiếu". Java là pass-by-value với ngữ nghĩa tham chiếu. - spraff
@Gravity, trong khi bạn chắc chắn rồi chính xác trong đó folks đến từ C + + bản năng sẽ có một trực giác khác nhau thiết lập liên quan đến thuật ngữ "tham khảo", cá nhân tôi tin rằng cơ thể được chôn cất hơn trong "bởi". "Vượt qua" là khó hiểu ở chỗ nó hoàn toàn khác biệt với "Chuyển một" trong Java. Trong C ++, tuy nhiên nó không phải là thông tục. Trong C ++, bạn có thể nói "chuyển một tham chiếu" và hiểu rằng nó sẽ vượt qua swap(x,y) kiểm tra.


Các câu trả lời:


Java luôn là giá trị vượt qua. Thật không may, họ quyết định gọi vị trí của một đối tượng là "tham chiếu". Khi chúng ta chuyển giá trị của một đối tượng, chúng ta sẽ truyền tài liệu tham khảo cho nó. Điều này gây nhầm lẫn cho người mới bắt đầu.

Nó như thế này:

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
}

Trong ví dụ trên aDog.getName() vẫn sẽ trở lại "Max". Giá trị aDog trong main không thay đổi trong hàm foo với Dog  "Fifi" khi tham chiếu đối tượng được truyền theo giá trị. Nếu nó được chuyển qua tham chiếu, thì aDog.getName() trong main sẽ trở lại "Fifi" sau cuộc gọi đến foo.

Tương tự như vậy:

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");
}

Trong ví dụ trên, Fifi là tên của con chó sau khi gọi foo(aDog) bởi vì tên của đối tượng được đặt bên trong foo(...). Bất kỳ hoạt động nào foo thực hiện trên d là như vậy, cho tất cả các mục đích thực tế, chúng được thực hiện trên aDog chính nó (trừ khi d được thay đổi để trỏ đến một Dog ví dụ như d = new Dog("Boxer")).


4741



Không phải là nó hơi khó hiểu vấn đề với các chi tiết nội bộ? Không có sự khác biệt về khái niệm giữa 'truyền tham chiếu' và 'chuyển giá trị của tham chiếu', giả sử rằng bạn có nghĩa là 'giá trị của con trỏ bên trong đối tượng'. - izb
Nhưng có một sự khác biệt tinh tế. Nhìn vào ví dụ đầu tiên. Nếu nó hoàn toàn vượt qua tham chiếu, aDog.name sẽ là "Fifi". Nó không phải là - các tài liệu tham khảo bạn đang nhận được là một tham chiếu giá trị rằng nếu ghi đè sẽ được khôi phục khi thoát khỏi chức năng. - erlando
@Lorenzo: Không, trong Java mọi thứ đều được truyền theo giá trị. Các giá trị nguyên thủy được truyền theo giá trị và các tham chiếu đối tượng được truyền theo giá trị. Bản thân các đối tượng không bao giờ được truyền vào một phương thức, nhưng các đối tượng luôn ở trong vùng heap và chỉ một tham chiếu tới đối tượng được truyền cho phương thức. - Esko Luontola
Nỗ lực của tôi là một cách hay để trực quan hóa việc truyền đối tượng: Hãy tưởng tượng một quả bóng. Gọi một fxn giống như việc buộc một chuỗi thứ hai vào quả bóng và đưa đường thẳng tới fxn. parameter = new Balloon () sẽ cắt chuỗi đó và tạo ra một quả bóng mới (nhưng điều này không ảnh hưởng đến quả bóng ban đầu). parameter.pop () vẫn sẽ bật nó mặc dù vì nó theo chuỗi đến cùng một quả bóng gốc. Java được truyền theo giá trị, nhưng giá trị được truyền không sâu, nó ở mức cao nhất, tức là một nguyên thủy hoặc một con trỏ. Đừng nhầm lẫn điều đó với một giá trị vượt qua theo chiều sâu nơi đối tượng được nhân bản hoàn toàn và được truyền đi. - dhackner
Điều gây nhầm lẫn là các tham chiếu đối tượng thực sự là con trỏ. Ban đầu SUN gọi họ là con trỏ. Sau đó tiếp thị thông báo rằng "con trỏ" là một từ xấu. Nhưng bạn vẫn thấy danh pháp "đúng" trong NullPointerException. - Prof. Falken


Tôi vừa nhận thấy bạn đã tham chiếu bài báo của tôi.

Java Spec nói rằng tất cả mọi thứ trong Java là pass-by-value. Không có thứ như "pass-by-reference" trong Java.

Chìa khóa để hiểu điều này là một cái gì đó như

Dog myDog;

không phải con chó; nó thực sự là một con trỏ cho một con chó.

Điều đó có nghĩa là, là khi bạn có

Dog myDog = new Dog("Rover");
foo(myDog);

về cơ bản bạn đang vượt qua địa chỉ nhà của tạo Dog đối tượng với foo phương pháp.

(Tôi nói về cơ bản vì các con trỏ Java không phải là địa chỉ trực tiếp, nhưng dễ nhất để nghĩ về chúng theo cách đó)

Giả sử Dog đối tượng cư trú tại địa chỉ bộ nhớ 42. Điều này có nghĩa là chúng ta chuyển 42 cho phương thức.

nếu Phương thức được định nghĩa là

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

chúng ta hãy nhìn vào những gì đang xảy ra.

  • thông số someDog được đặt thành giá trị 42
  • tại dòng "AAA"
    • someDog được theo sau Dog nó trỏ tới ( Dog đối tượng tại địa chỉ 42)
    • cái đó Dog (tại địa chỉ 42) được yêu cầu đổi tên thành Max
  • tại dòng "BBB"
    • một cái mới Dog được tạo ra. Giả sử anh ta ở địa chỉ 74
    • chúng tôi chỉ định tham số someDog đến 74
  • tại dòng "CCC"
    • someDog được theo sau Dog nó trỏ tới ( Dog đối tượng tại địa chỉ 74)
    • cái đó Dog (một tại địa chỉ 74) được yêu cầu đổi tên thành Rowlf
  • sau đó, chúng tôi trở lại

Bây giờ hãy suy nghĩ về những gì xảy ra bên ngoài phương thức:

Đã làm myDog thay đổi?

Có chìa khóa.

Hãy nhớ rằng myDog là một con trỏvà không phải là Dog, câu trả lời là không. myDog vẫn có giá trị 42; nó vẫn trỏ đến bản gốc Dog(nhưng lưu ý rằng vì dòng "AAA", tên của nó bây giờ là "Max" - vẫn là cùng một con chó; myDogGiá trị của nó không thay đổi.)

Nó hoàn toàn hợp lệ để theo một địa chỉ và thay đổi những gì ở cuối của nó; Tuy nhiên, điều đó không thay đổi biến.

Java hoạt động chính xác như C. Bạn có thể gán một con trỏ, chuyển con trỏ tới một phương thức, theo con trỏ trong phương thức và thay đổi dữ liệu được trỏ tới. Tuy nhiên, bạn không thể thay đổi nơi con trỏ trỏ đến đâu.

Trong ngôn ngữ C ++, Ada, Pascal và các ngôn ngữ khác hỗ trợ tham chiếu theo từng bước, bạn thực sự có thể thay đổi biến đã được chuyển.

Nếu Java có ngữ nghĩa học theo tham chiếu, foo phương pháp chúng tôi đã xác định ở trên sẽ thay đổi myDog đã chỉ khi nó được chỉ định someDog trên dòng BBB.

Hãy suy nghĩ về tham số tham chiếu như là bí danh cho biến được truyền vào. Khi bí danh đó được gán, thì biến đó đã được chuyển vào.


2639



Đây là lý do tại sao các thông thường refrain "Java không có con trỏ" là như vậy gây hiểu lầm. - Beska
Bạn sai, imho. "Hãy nhớ rằng myDog là một con trỏ, và không phải là một Dog thực sự, câu trả lời là NO. MyDog vẫn có giá trị 42, nó vẫn trỏ đến Dog gốc." myDog có giá trị 42 nhưng đối số tên của nó bây giờ chứa "Max", thay vì "Rover" trên // dòng AAA. - Özgür
Hãy suy nghĩ về nó theo cách này. Ai đó có địa chỉ của Ann Arbor, MI (quê hương của tôi, GO BLUE!) Trên một tờ giấy gọi là "annArborLocation". Bạn sao chép nó xuống trên một mảnh giấy gọi là "myDestination". Bạn có thể lái xe đến "myDestination" và trồng một cây. Bạn có thể đã thay đổi một cái gì đó về thành phố tại địa điểm đó, nhưng nó không thay đổi LAT / LON được viết trên một trong hai giấy. Bạn có thể thay đổi LAT / LON trên "myDestination" nhưng nó không thay đổi "annArborLocation". cái đó có giúp ích không? - Scott Stanchfield
@Scott Stanchfield: Tôi đã đọc bài viết của bạn khoảng một năm trước và nó thực sự đã giúp mọi thứ rõ ràng cho tôi. Cảm ơn! Tôi có thể đề nghị thêm một chút: bạn nên đề cập rằng có một thuật ngữ cụ thể mô tả dạng "gọi theo giá trị mà giá trị là một tham chiếu" được phát minh bởi Barbara Liskov để mô tả chiến lược đánh giá ngôn ngữ CLU của cô ấy 1974, để tránh nhầm lẫn, chẳng hạn như bài viết mà bạn viết: gọi bằng cách chia sẻ (đôi khi được gọi là gọi bằng cách chia sẻ đối tượng hoặc đơn giản gọi theo đối tượng), trong đó khá nhiều mô tả hoàn hảo ngữ nghĩa. - Jörg W Mittag
@Gevorg - Các ngôn ngữ C / C ++ không sở hữu khái niệm "con trỏ". Có các ngôn ngữ khác sử dụng con trỏ nhưng không cho phép cùng một loại thao tác của con trỏ mà C / C ++ cho phép. Java có con trỏ; chúng chỉ được bảo vệ chống nghịch ngợm. - Scott Stanchfield


Java luôn chuyển đối số theo giá trị NOT bằng tham chiếu.


Hãy để tôi giải thích điều này thông qua một thí dụ:

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");
     }
}

Tôi sẽ giải thích điều này theo các bước:

  1. Khai báo một tham chiếu có tên f loại Foo và gán nó cho một đối tượng kiểu mới Foo với một thuộc tính "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Từ phía phương thức, tham chiếu kiểu Foo với một cái tên a được khai báo và ban đầu nó được gán cho null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Khi bạn gọi phương thức changeReference, tham khảo a sẽ được gán cho đối tượng được chuyển làm đối số.

    changeReference(f);
    

    enter image description here

  4. Khai báo một tham chiếu có tên b loại Foo và gán nó cho một đối tượng kiểu mới Foo với một thuộc tính "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b đang gán lại tham chiếu a KHÔNG PHẢI f đối tượng có thuộc tính của nó "b".

    enter image description here


  6. Như bạn gọi modifyReference(Foo c) phương pháp, tham chiếu c được tạo và gán cho đối tượng với thuộc tính "f".

    enter image description here

  7. c.setAttribute("c"); sẽ thay đổi thuộc tính của đối tượng tham chiếu c chỉ vào nó, và cùng một đối tượng tham chiếu f chỉ vào nó.

    enter image description here

Tôi hy vọng bạn hiểu bây giờ làm thế nào đi qua các đối tượng như là đối số hoạt động trong Java :)


1392



+1 Nội dung hay. sơ đồ tốt. Tôi cũng tìm thấy một trang gọn gàng đẹp ở đây adp-gmbh.ch/php/pass_by_reference.html OK Tôi thừa nhận nó được viết bằng PHP, nhưng nó là nguyên tắc của sự hiểu biết sự khác biệt mà tôi nghĩ là quan trọng (và làm thế nào để thao tác sự khác biệt đó với nhu cầu của bạn). - DaveM
@ Eng.Fouad Đó là một lời giải thích tốt đẹp nhưng nếu a trỏ đến cùng một đối tượng f (và không bao giờ nhận được bản sao của đối tượng f trỏ đến), mọi thay đổi đối với đối tượng được thực hiện bằng a nên sửa đổi f tốt (vì cả hai đều làm việc với cùng một đối tượng), vì vậy tại một số điểm a phải tự mình sở hữu sao chép của đối tượng f chỉ tới. - Mr D
@MrD khi 'a' trỏ đến cùng một đối tượng 'f' cũng trỏ tới, thì bất kỳ thay đổi nào được thực hiện đối tượng đó qua 'a' cũng có thể quan sát được qua 'f', NHƯNG nó KHÔNG CHƯA THAY 'f'. 'f' vẫn trỏ đến cùng một đối tượng. Bạn hoàn toàn có thể thay đổi đối tượng, nhưng bạn không bao giờ có thể thay đổi điểm 'f' thành gì. Đây là vấn đề cơ bản mà vì một số lý do mà một số người không thể hiểu được. - Mike Braun
Đây là lời giải thích tốt nhất mà tôi đã tìm thấy. Nhân tiện, tình hình cơ bản là gì? Ví dụ, đối số yêu cầu một kiểu int, nó vẫn truyền bản sao của một biến int cho đối số? - allenwang
Sơ đồ rất hữu ích - Ivan Kaloyanov


Điều này sẽ cung cấp cho bạn một số thông tin chi tiết về cách Java thực sự hoạt động đến điểm mà trong cuộc thảo luận tiếp theo của bạn về Java đi qua tham chiếu hoặc truyền theo giá trị bạn sẽ chỉ mỉm cười :-)

Bước một xin vui lòng xóa từ tâm trí của bạn rằng từ đó bắt đầu với 'p' "_ _ _ _ _ _ _", đặc biệt là nếu bạn đến từ các ngôn ngữ lập trình khác. Java và 'p' không thể được viết trong cùng một cuốn sách, diễn đàn hoặc thậm chí là txt.

Bước hai hãy nhớ rằng khi bạn truyền một Object vào một phương thức bạn đang truyền tham chiếu Object và không phải là Object.

  • Sinh viên: Thưa Sư Phụ, điều này có nghĩa là Java được truyền qua tham chiếu?
  • Bậc thầy: Châu chấu, Không.

Bây giờ hãy nghĩ về tham chiếu / biến của đối tượng là gì / là:

  1. Một biến chứa các bit cho JVM biết cách truy cập đối tượng được tham chiếu trong bộ nhớ (Heap).
  2. Khi chuyển đối số cho một phương thức bạn KHÔNG chuyển biến tham chiếu, nhưng một bản sao của các bit trong biến tham chiếu. Một cái gì đó như thế này: 3bad086a. 3bad086a đại diện cho một cách để tới đối tượng được truyền.
  3. Vì vậy, bạn chỉ cần vượt qua 3bad086a rằng đó là giá trị của tham chiếu.
  4. Bạn đang chuyển giá trị của tham chiếu chứ không phải tham chiếu (và không phải đối tượng).
  5. Giá trị này thực sự là COPIED và được cung cấp cho phương thức.

Trong phần sau (xin đừng cố biên dịch / thực hiện điều này ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Chuyện gì xảy ra?

  • Biến người được tạo ra trong dòng # 1 và nó là null lúc đầu.
  • Một đối tượng Person mới được tạo ra trong dòng # 2, được lưu trữ trong bộ nhớ và biến người được đưa ra tham chiếu đến đối tượng Person. Đó là, địa chỉ của nó. Giả sử 3bad086a.
  • Biến người giữ địa chỉ của đối tượng được chuyển đến hàm trong dòng # 3.
  • Trong dòng # 4 bạn có thể nghe âm thanh của sự im lặng
  • Kiểm tra nhận xét trên dòng # 5
  • Một biến cục bộ của phương thức -anotherReferenceToTheSamePersonObject- được tạo ra và sau đó đến ma thuật trong dòng # 6:
    • Biến / tham chiếu người được sao chép từng bit một và chuyển đến anotherReferenceToTheSamePersonObject bên trong hàm.
    • Không có phiên bản mới nào của Person được tạo.
    • Cả hai "người"và"anotherReferenceToTheSamePersonObject"giữ cùng giá trị 3bad086a.
    • Đừng thử điều này nhưng người == anotherReferenceToTheSamePersonObject sẽ là đúng.
    • Cả hai biến đều có IDENTAL COPIES của tham chiếu và cả hai đều tham chiếu đến cùng một đối tượng Person, đối tượng CÙNG trên Heap và NOT COPY.

Một bưc tranh đang gia ngan lơi noi:

Pass by Value

Lưu ý rằng các mũi tên anotherReferenceToTheSamePersonObject được hướng về phía đối tượng và không hướng tới người biến!

Nếu bạn không nhận được nó thì hãy tin tưởng tôi và nhớ rằng tốt hơn nên nói rằng Java được truyền theo giá trị. Tốt, vượt qua bằng giá trị tham chiếu. Ồ, tốt hơn nữa là pass-by-copy-of-the-biến-giá trị! ;)

Bây giờ cảm thấy tự do để ghét tôi nhưng lưu ý rằng cho điều này không có sự khác biệt giữa việc truyền các kiểu dữ liệu nguyên thủy và các đối tượng khi nói về các đối số phương thức.

Bạn luôn truyền một bản sao của các bit của giá trị của tham chiếu!

  • Nếu đó là một kiểu dữ liệu nguyên thủy, các bit này sẽ chứa giá trị của kiểu dữ liệu nguyên thủy.
  • Nếu đó là một đối tượng, các bit sẽ chứa giá trị của địa chỉ cho JVM biết cách truy cập vào đối tượng.

Java là pass-by-value bởi vì bên trong một method bạn có thể sửa đổi đối tượng tham chiếu nhiều như bạn muốn nhưng không có vấn đề làm thế nào cứng bạn thử bạn sẽ không bao giờ có thể sửa đổi biến được thông qua sẽ tiếp tục tham chiếu (không p _ _ _ _ _ _ _) cùng một đối tượng không có vấn đề gì!


Hàm changeName ở trên sẽ không bao giờ có thể sửa đổi nội dung thực tế (các giá trị bit) của tham chiếu đã truyền. Trong từ changeName khác không thể làm cho Person person tham chiếu đến Object khác.


Tất nhiên bạn có thể cắt ngắn và chỉ nói rằng Java là giá trị vượt qua!


651



Bạn có nghĩa là con trỏ? .. Nếu tôi nhận được nó một cách chính xác, trong public void foo(Car car){ ... }, car là địa phương để foo và nó chứa vị trí đống của Object? Vì vậy, nếu tôi thay đổi cargiá trị của car = new Car(), nó sẽ trỏ đến Object khác nhau trên heap? và nếu tôi thay đổi cartài sản của valu bởi car.Color = "Red", chiếc Object trong heap chỉ car sẽ được sửa đổi. Ngoài ra, nó là như nhau trong C #? Xin hãy trả lời! Cảm ơn! - dpp
@domanokz Bạn đang giết tôi, xin đừng nói từ đó một lần nữa! ;) Lưu ý rằng tôi có thể trả lời câu hỏi này mà không nói 'tham chiếu'. Đó là một vấn đề về thuật ngữ và 'p làm cho nó tồi tệ hơn. Tôi và Scot có quan điểm khác về điều này thật không may. Tôi nghĩ rằng bạn đã làm thế nào nó hoạt động trong Java, bây giờ bạn có thể gọi nó vượt qua giá trị, chia sẻ đối tượng, by-copy-of-the-biến-giá trị, hoặc cảm thấy tự do để đến với cái gì khác! Tôi không thực sự quan tâm miễn là bạn đã làm thế nào nó hoạt động và những gì trong một loại đối tượng: chỉ là một địa chỉ PO Box! ;) - Gevorg
Nghe có vẻ như bạn đã chuyển một tham chiếu? Tôi sẽ thuyết phục thực tế rằng Java vẫn là một ngôn ngữ được truyền qua tham chiếu đã sao chép. Thực tế là nó là một tham chiếu sao chép không thay đổi thuật ngữ. Cả hai TÀI LIỆU THAM KHẢO vẫn trỏ đến cùng một đối tượng. Đây là đối số của chủ nghĩa thuần túy ... - John Strickler
Tôi đoán Java được thiết kế với con trỏ trong tâm trí. Nếu không, tại sao NullPointerException tồn tại? Tuy nhiên, với lời giải thích tuyệt vời này, việc tìm ra con trỏ sẽ khiến nó trở nên phức tạp - brain storm
Vì vậy, thả vào dòng lý thuyết # 9 System.out.println(person.getName()); những gì sẽ hiển thị? "Tom" hay "Jerry"? Đây là điều cuối cùng sẽ giúp tôi vượt qua sự nhầm lẫn này. - TheBrenny


Java luôn đi qua giá trị, không có ngoại lệ, không bao giờ.

Vì vậy, làm thế nào là nó mà bất cứ ai có thể được ở tất cả các bối rối bởi điều này, và tin rằng Java là đi qua tham chiếu, hoặc nghĩ rằng họ có một ví dụ về Java hành động như vượt qua bằng cách tham khảo? Điểm mấu chốt là Java không bao giờ cung cấp quyền truy cập trực tiếp vào các giá trị của đối tượng, trong bất kì hoàn cảnh. Quyền truy cập duy nhất vào đối tượng là thông qua tài liệu tham khảo cho đối tượng đó. Vì các đối tượng Java luôn luôn truy cập thông qua một tham chiếu, chứ không phải trực tiếp, nó là phổ biến để nói về các lĩnh vực và các biến và các đối số phương thức như là các đối tượng, khi họ chỉ có mặt đạo đức tham chiếu đến đối tượng. Sự nhầm lẫn xuất phát từ sự thay đổi này (nói đúng, sai) trong danh pháp.

Vì vậy, khi gọi một phương thức

  • Đối với các đối số nguyên thủy (int, long, v.v.), giá trị vượt qua là giá trị thực tế của nguyên thủy (ví dụ, 3).
  • Đối với các đối tượng, giá trị truyền theo giá trị là giá trị của tham chiếu đến đối tượng.

Vì vậy, nếu bạn có doSomething(foo) và public void doSomething(Foo foo) { .. } hai Foos đã sao chép tài liệu tham khảo điểm đó cho cùng một đối tượng.

Đương nhiên, đi qua giá trị một tham chiếu đến một đối tượng trông rất giống (và không thể phân biệt trong thực tế) truyền một đối tượng bằng tham chiếu.


561



Vì các giá trị của các nguyên thủy là không thay đổi (như String), sự khác biệt giữa hai trường hợp không thực sự có liên quan. - Paŭlo Ebermann
Chính xác. Đối với tất cả những gì bạn có thể nói qua hành vi JVM quan sát được, các nguyên thủy có thể được truyền qua tham chiếu và có thể sống trên heap. Họ không, nhưng điều đó không thực sự quan sát được bằng bất kỳ cách nào. - Gravity
nguyên thủy là bất biến? là mới trong Java 7? - Carlos Heuberger
@CarlosHeuberger, "nguyên thủy là bất biến?". Vấn đề là bạn có thể giả vờ 'nguyên thủy' thực sự là (có thể thay đổi) tài liệu tham khảo đối tượng bất biến. - Aaron McDaid
Con trỏ là bất biến, nguyên thủy nói chung là có thể thay đổi. Chuỗi cũng không phải là nguyên thủy, nó là một đối tượng. Hơn nữa, cấu trúc bên dưới của String là một mảng có thể thay đổi được. Điều bất biến duy nhất về nó là chiều dài, đó là bản chất vốn có của mảng. - kingfrito_5005


Java chuyển các tham chiếu theo giá trị.

Vì vậy, bạn không thể thay đổi tham chiếu được chuyển vào.


279



Nhưng điều tiếp tục lặp đi lặp lại "bạn không thể thay đổi giá trị của các đối tượng được truyền trong các đối số" rõ ràng là sai. Bạn có thể không làm cho họ tham chiếu đến một đối tượng khác, nhưng bạn có thể thay đổi nội dung của họ bằng cách gọi các phương thức của họ. IMO điều này có nghĩa là bạn mất tất cả các lợi ích của tài liệu tham khảo và không được bảo đảm thêm. - Timmmm
Tôi không bao giờ nói "bạn không thể thay đổi giá trị của các đối tượng được truyền trong các đối số". Tôi sẽ nói "Bạn không thể thay đổi giá trị của tham chiếu đối tượng được truyền vào như một đối số phương thức", đó là một tuyên bố đúng về ngôn ngữ Java. Rõ ràng bạn có thể thay đổi trạng thái của đối tượng (miễn là nó không thay đổi). - ScArcher2
Hãy nhớ rằng bạn không thể thực sự truyền các đối tượng trong java; các đối tượng ở lại trên đống. Con trỏ cho các đối tượng có thể được truyền (được sao chép vào khung ngăn xếp cho phương thức được gọi). Vì vậy, bạn không bao giờ thay đổi giá trị được truyền vào (con trỏ), nhưng bạn tự do theo dõi nó và thay đổi điều trên heap mà nó trỏ tới. Đó là giá trị vượt qua. - Scott Stanchfield
Nghe có vẻ như bạn đã chuyển một tham chiếu? Tôi sẽ thuyết phục thực tế rằng Java vẫn là một ngôn ngữ được truyền qua tham chiếu đã sao chép. Thực tế là nó là một tham chiếu sao chép không thay đổi thuật ngữ. Cả hai TÀI LIỆU THAM KHẢO vẫn trỏ đến cùng một đối tượng. Đây là đối số của chủ nghĩa thuần túy ... - John Strickler
Java không vượt qua một đối tượng, nó chuyển giá trị của con trỏ tới đối tượng. Điều này tạo ra một con trỏ mới đến vị trí bộ nhớ của đối tượng ban đầu trong một biến mới. Nếu bạn thay đổi giá trị (địa chỉ bộ nhớ mà nó trỏ đến) của biến con trỏ này trong một phương thức, thì con trỏ ban đầu được sử dụng trong trình gọi phương thức vẫn chưa được sửa đổi. Nếu bạn đang gọi tham số một tham chiếu thì thực tế là nó là một sao chép của tham chiếu ban đầu, không phải là tham chiếu gốc, do đó hiện có hai tham chiếu đến đối tượng có nghĩa là nó là giá trị truyền theo giá trị - theferrit32


Tôi cảm thấy tranh luận về "pass-by-reference vs pass-by-value" không phải là siêu hữu ích.

Nếu bạn nói, "Java là pass-by-bất cứ điều gì (tham khảo / giá trị)", trong cả hai trường hợp, bạn không cung cấp một câu trả lời hoàn chỉnh. Dưới đây là một số thông tin bổ sung hy vọng sẽ giúp hiểu được những gì đang xảy ra trong bộ nhớ.

Crash khóa học trên stack / heap trước khi chúng tôi nhận được để thực hiện Java: Giá trị đi vào và ra khỏi ngăn xếp trong một thời trang trật tự tốt đẹp, giống như một chồng đĩa tại một quán cà phê. Bộ nhớ trong heap (còn được gọi là bộ nhớ động) là sự lộn xộn và vô tổ chức. JVM chỉ tìm thấy không gian bất cứ nơi nào có thể và giải phóng nó khi các biến sử dụng nó không còn cần thiết nữa.

Đuợc. Trước hết, các nguyên thủy cục bộ đi trên stack. Vì vậy, mã này:

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

kết quả trong này:

primitives on the stack

Khi bạn khai báo và khởi tạo một đối tượng. Các đối tượng thực tế đi trên heap. Điều gì xảy ra trên stack? Địa chỉ của đối tượng trên heap. Các lập trình viên C ++ sẽ gọi đây là một con trỏ, nhưng một số nhà phát triển Java đang chống lại từ "con trỏ". Bất cứ điều gì. Chỉ cần biết rằng địa chỉ của đối tượng đi vào ngăn xếp.

Giống như vậy:

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

a b*7ch aint one!

Một mảng là một đối tượng, do đó, nó đi trên heap là tốt. Và những gì về các đối tượng trong mảng? Họ nhận được không gian heap riêng của họ, và địa chỉ của mỗi đối tượng đi vào bên trong mảng.

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

marx brothers

Vì vậy, những gì được thông qua trong khi bạn gọi một phương pháp? Nếu bạn vượt qua trong một đối tượng, những gì bạn đang thực sự đi vào là địa chỉ của đối tượng. Một số có thể nói "giá trị" của địa chỉ, và một số nói rằng nó chỉ là một tham chiếu đến đối tượng. Đây là nguồn gốc của cuộc chiến thánh giữa những người ủng hộ "tham khảo" và "giá trị". Những gì bạn gọi nó không quan trọng như bạn hiểu rằng những gì nhận được thông qua trong là địa chỉ cho đối tượng.

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);
}

Một String được tạo ra và không gian cho nó được cấp phát trong heap, và địa chỉ cho chuỗi được lưu trữ trên stack và được định danh hisName, vì địa chỉ của chuỗi thứ hai giống như địa chỉ đầu tiên, không có chuỗi mới nào được tạo và không có vùng đệm mới được cấp phát, nhưng một số nhận dạng mới được tạo trên ngăn xếp. Sau đó chúng tôi gọi shout(): một khung ngăn xếp mới được tạo và một số nhận dạng mới, name được tạo và gán địa chỉ của chuỗi đã tồn tại.

la da di da da da da

Vì vậy, giá trị, tham khảo? Bạn nói "khoai tây".


198



Tuy nhiên, bạn nên theo dõi với một ví dụ phức tạp hơn, nơi một hàm xuất hiện để thay đổi một biến có địa chỉ có tham chiếu. - Brian Peterson
Mọi người không "nhảy múa xung quanh vấn đề thực sự" của stack vs heap, bởi vì đó là không phải vấn đề thực sự. Đó là một chi tiết thực hiện tốt nhất, và hết sức sai lầm tồi tệ nhất. (Nó hoàn toàn có thể cho các đối tượng để sống trên stack, google "thoát phân tích". Và một số lượng lớn các đối tượng chứa nguyên thủy có thể không sống trên stack.) Vấn đề thực sự là chính xác sự khác biệt giữa các loại tham chiếu và kiểu giá trị - cụ thể là giá trị của biến kiểu tham chiếu là tham chiếu chứ không phải đối tượng mà nó tham chiếu. - cHao
Đó là một "chi tiết triển khai" trong Java đó là không bao giờ cần thiết để thực sự cho bạn thấy nơi một đối tượng sống trong bộ nhớ, và trên thực tế dường như xác định tránh rò rỉ thông tin đó. Nó có thể đặt vật thể lên chồng và bạn không bao giờ biết. Nếu bạn quan tâm, bạn đang tập trung vào điều sai - và trong trường hợp này, điều đó có nghĩa là bỏ qua vấn đề thực sự. - cHao
Và một trong hai cách, "nguyên thủy đi trên ngăn xếp" là không chính xác. Nguyên thủy biến cục bộ đi trên ngăn xếp. (Dĩ nhiên, nếu chúng chưa được tối ưu hóa), nhưng sau đó, do đó, làm biến tham chiếu cục bộ. Và các thành viên nguyên thủy được định nghĩa trong một đối tượng sống bất cứ nơi nào đối tượng sống. - cHao
Đồng ý với các ý kiến ​​ở đây. Stack / heap là một vấn đề phụ, và không liên quan. Một số biến có thể nằm trong ngăn xếp, một số biến nằm trong bộ nhớ tĩnh (biến tĩnh) và rất nhiều biến trên heap (tất cả các biến thành viên của đối tượng). NONE của các biến này có thể được chuyển qua tham chiếu: từ một phương thức được gọi là KHÔNG BAO GIỜ có thể thay đổi giá trị của một biến được chuyển thành đối số. Do đó, không có tham chiếu pass-by-by trong Java. - fishinear


Chỉ để hiển thị độ tương phản, so sánh các mục sau C ++ và Java đoạn trích:

Trong C ++: Lưu ý: Mã lỗi - rò rỉ bộ nhớ!  Nhưng nó thể hiện quan điểm.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

Trong Java,

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java chỉ có hai kiểu truyền: theo giá trị cho các kiểu dựng sẵn và theo giá trị của con trỏ cho các kiểu đối tượng.


161



+1 Tôi cũng sẽ thêm Dog **objPtrPtr với ví dụ C ++, theo cách đó chúng ta có thể sửa đổi con trỏ "trỏ tới". - Amro
Đây là một trong những câu trả lời tốt nhất mà tôi đã thấy cho đến nay. Nó chủ yếu tránh các đối số ngữ nghĩa không liên quan của "tham chiếu so với giá trị của tham chiếu" ở nơi khác và thay vào đó thảo luận về cơ chế của những gì thực sự xảy ra. Tôi phải đưa ra hầu hết các câu trả lời "trả theo giá trị" khác -1 vì nội dung tự mâu thuẫn hoặc thực tế sai của họ, nhưng thay vào đó, nội dung này nhận được +1. - Aaron


Java chuyển các tham chiếu đến các đối tượng theo giá trị.


145



Đơn giản vậy thôi ! - Ivan Kaloyanov