Java เป็น "pass-by-reference" หรือ "pass-by-value"?

Sep 03 2008

ผมคิดเสมอว่าการใช้งาน Java ผ่านโดยการอ้างอิง

อย่างไรก็ตามฉันเคยเห็นบล็อกโพสต์สองสามรายการ (เช่นบล็อกนี้ ) ที่อ้างว่าไม่ใช่ (บล็อกโพสต์บอกว่า Java ใช้pass-by-value )

ฉันไม่คิดว่าฉันเข้าใจความแตกต่างที่พวกเขาสร้างขึ้น

มีคำอธิบายอย่างไร?

คำตอบ

6114 erlando Sep 03 2008 at 03:25

Java เป็นpass-by-valueเสมอ
แต่น่าเสียดายที่เราไม่เคยจัดการกับวัตถุที่ทุกแทนเล่นกลจับวัตถุที่เรียกว่าการอ้างอิง (ซึ่งจะถูกส่งผ่านโดยค่าของหลักสูตร) คำศัพท์และความหมายที่เลือกได้ง่ายทำให้ผู้เริ่มต้นหลายคนสับสน

มันจะเป็นแบบนี้:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // 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
    aDog == oldDog; // true
}

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"ค่าaDogภายในmainจะไม่เปลี่ยนแปลงในฟังก์ชันที่fooมีการDog "Fifi"ส่งผ่านค่าเนื่องจากการอ้างอิงอ็อบเจ็กต์ ถ้ามันถูกส่งผ่านโดยการอ้างอิงแล้วaDog.getName()ในmainจะกลับมาหลังจากที่โทรไป"Fifi"foo

ในทำนองเดียวกัน:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // 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(...)การดำเนินการใด ๆ ที่fooดำเนินการdเป็นเช่นนั้นเพื่อวัตถุประสงค์ในทางปฏิบัติทั้งหมดจะดำเนินการaDogแต่ไม่สามารถเปลี่ยนค่าของตัวแปรaDogเองได้

3192 ScottStanchfield Sep 16 2008 at 21:37

ฉันเพิ่งสังเกตเห็นว่าคุณอ้างถึงบทความของฉัน

Java Spec บอกว่าทุกอย่างใน Java เป็นแบบ pass-by-value ไม่มีสิ่งที่เรียกว่า "pass-by-reference" ใน Java

กุญแจสำคัญในการทำความเข้าใจสิ่งนี้คือสิ่งที่ต้องการ

Dog myDog;

คือไม่ได้สุนัข; จริงๆแล้วมันเป็นตัวชี้ไปที่สุนัข

นั่นหมายความว่าเมื่อคุณมี

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

คุณกำลังส่งที่อยู่ของDogวัตถุที่สร้างขึ้นไปยังfooเมธอดเป็นหลัก

(ฉันพูดเป็นหลักเพราะตัวชี้ Java ไม่ใช่ที่อยู่โดยตรง แต่ง่ายที่สุดที่จะคิดอย่างนั้น)

สมมติว่าDogวัตถุอยู่ที่ที่อยู่หน่วยความจำ 42 ซึ่งหมายความว่าเราส่ง 42 ไปยังเมธอด

ถ้าวิธีการถูกกำหนดเป็น

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

มาดูกันว่าเกิดอะไรขึ้น

  • พารามิเตอร์someDogถูกตั้งค่าเป็นค่า 42
  • ที่บรรทัด "AAA"
    • someDogตามไปที่Dogมันชี้ไปที่ ( Dogวัตถุที่อยู่ 42)
    • ที่Dog(ที่อยู่ 42) ถูกขอให้เปลี่ยนชื่อเป็น Max
  • ที่บรรทัด "BBB"
    • ใหม่Dogถูกสร้างขึ้น สมมติว่าเขาอยู่ที่ 74
    • เรากำหนดพารามิเตอร์someDogเป็น 74
  • ที่บรรทัด "CCC"
    • someDog ตามไปที่Dogมันชี้ไปที่ ( Dogวัตถุที่อยู่ 74)
    • ที่Dog(ที่อยู่ 74) ถูกขอให้เปลี่ยนชื่อเป็น Rowlf
  • จากนั้นเรากลับ

ทีนี้ลองคิดดูว่าเกิดอะไรขึ้นนอกวิธีการ:

ไม่myDogเปลี่ยนแปลง?

มีกุญแจสำคัญ

โปรดทราบว่าmyDogเป็นตัวชี้ไม่ใช่ตัวชี้Dogวัดคำตอบคือไม่ myDogยังคงมีค่า 42; มันยังคงชี้ไปที่ต้นฉบับDog(แต่โปรดทราบว่าเนื่องจากบรรทัด "AAA" ตอนนี้ชื่อของมันคือ "Max" - ยังคงเป็น Dog เหมือนเดิมmyDogค่าของไม่เปลี่ยนแปลง)

สามารถติดตามที่อยู่ได้อย่างสมบูรณ์และเปลี่ยนแปลงสิ่งที่อยู่ในตอนท้าย ที่ไม่เปลี่ยนแปลงตัวแปรอย่างไรก็ตาม

Java ทำงานเหมือนกับ C. คุณสามารถกำหนดตัวชี้ส่งผ่านตัวชี้ไปยังวิธีการทำตามตัวชี้ในวิธีการและเปลี่ยนข้อมูลที่ชี้ไป อย่างไรก็ตามคุณไม่สามารถเปลี่ยนตำแหน่งที่ตัวชี้นั้นชี้ได้

ใน C ++, Ada, Pascal และภาษาอื่น ๆ ที่รองรับ pass-by-reference คุณสามารถเปลี่ยนตัวแปรที่ส่งผ่านไปได้

หาก Java มีความหมายแบบ pass-by-reference fooวิธีที่เรากำหนดไว้ข้างต้นจะเปลี่ยนตำแหน่งที่myDogชี้เมื่อกำหนดsomeDogบนบรรทัด BBB

ให้คิดว่าพารามิเตอร์อ้างอิงเป็นนามแฝงสำหรับตัวแปรที่ส่งผ่านเข้ามาเมื่อมีการกำหนดนามแฝงนั้นตัวแปรที่ส่งผ่านก็เช่นกัน

1853 Eng.Fouad Sep 15 2012 at 01:22

Java ส่งผ่านอาร์กิวเมนต์ตามค่าเสมอไม่ใช่โดยการอ้างอิง


ให้ฉันอธิบายสิ่งนี้ผ่านตัวอย่าง :

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. ประกาศอ้างอิงชื่อfประเภทFooและกำหนดวัตถุใหม่ชนิดที่มีแอตทริบิวต์Foo"f"

     Foo f = new Foo("f");
    

  2. จากด้านข้างวิธีการอ้างอิงจากประเภทFooที่มีชื่อถูกประกาศและจะได้รับมอบหมายแรกanull

     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, ไม่ได้ ของวัตถุที่มีแอตทริบิวต์f"b"

  6. ในขณะที่คุณเรียกmodifyReference(Foo c)วิธีการอ้างอิงจะถูกสร้างขึ้นและได้รับมอบหมายวัตถุที่มีแอตทริบิวต์c"f"

  7. c.setAttribute("c");จะเปลี่ยนแอตทริบิวต์ของวัตถุที่อ้างอิงcชี้ไปและเป็นวัตถุเดียวกันกับที่อ้างอิงfชี้ไป

ฉันหวังว่าคุณจะเข้าใจแล้วว่าการส่งผ่านวัตถุเป็นอาร์กิวเมนต์ทำงานอย่างไรใน Java :)

746 Gevorg Aug 12 2011 at 08:22

สิ่งนี้จะให้ข้อมูลเชิงลึกเกี่ยวกับวิธีการทำงานของ Java จนถึงจุดที่ในการสนทนาครั้งต่อไปของคุณเกี่ยวกับการส่งผ่าน Java โดยการอ้างอิงหรือการส่งผ่านค่าคุณจะยิ้มได้ :-)

ขั้นตอนที่หนึ่งโปรดลบออกจากใจของคุณคำที่ขึ้นต้นด้วย 'p' "_ _ _ _ _ _ _ _" โดยเฉพาะอย่างยิ่งถ้าคุณมาจากภาษาโปรแกรมอื่น Java และ 'p' ไม่สามารถเขียนในหนังสือฟอรัมหรือแม้แต่ txt เดียวกันได้

ขั้นตอนที่สองจำไว้ว่าเมื่อคุณส่ง Object ไปยังเมธอดคุณจะส่งผ่าน Object reference ไม่ใช่ Object นั้นเอง

  • นักเรียน : อาจารย์หมายความว่า Java เป็น pass-by-reference หรือเปล่า?
  • Master : Grasshopper, No.

ตอนนี้คิดว่าการอ้างอิง / ตัวแปรของวัตถุทำ / คืออะไร:

  1. ตัวแปรเก็บบิตที่บอก JVM ว่าจะไปยัง Object ที่อ้างอิงในหน่วยความจำ (Heap) ได้อย่างไร
  2. เมื่อผ่านการขัดแย้งที่จะเป็นวิธีการที่คุณยังไม่ได้ผ่านตัวแปรอ้างอิง แต่คัดลอกบิตในตัวแปรอ้างอิง สิ่งนี้: 3bad086a 3bad086a แสดงถึงวิธีการไปยังวัตถุที่ส่งผ่าน
  3. คุณก็แค่ส่ง 3bad086a นั่นก็คือค่าของข้อมูลอ้างอิง
  4. คุณกำลังส่งผ่านค่าของการอ้างอิงไม่ใช่การอ้างอิง (ไม่ใช่วัตถุ)
  5. ค่านี้จะถูกคัดลอกจริงและได้รับกับวิธีการ

ดังต่อไปนี้ (โปรดอย่าพยายามรวบรวม / ดำเนินการนี้ ... ):

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. }

เกิดอะไรขึ้น?

  • บุคคลตัวแปรถูกสร้างขึ้นในบรรทัด # 1 และเป็นโมฆะที่จุดเริ่มต้น
  • วัตถุบุคคลใหม่ถูกสร้างขึ้นในบรรทัด # 2 เก็บไว้ในหน่วยความจำและตัวแปรบุคคลจะได้รับการอ้างอิงถึงวัตถุบุคคล นั่นคือที่อยู่ของมัน สมมติว่า 3bad086a
  • ตัวแปรบุคคลที่ถือแอดเดรสของ Object จะถูกส่งผ่านไปยังฟังก์ชันในบรรทัด # 3
  • ในบรรทัด # 4 คุณสามารถฟังเสียงแห่งความเงียบได้
  • ตรวจสอบความคิดเห็นในบรรทัด # 5
  • ตัวแปรโลคัลเมธอด - anotherReferenceToTheSamePersonObject - ถูกสร้างขึ้นจากนั้นเวทมนตร์ในบรรทัด # 6:
    • ตัวแปร / บุคคลอ้างอิงจะถูกคัดลอกทีละบิตและส่งต่อไปยังanotherReferenceToTheSamePersonObjectภายในฟังก์ชัน
    • ไม่มีการสร้างอินสแตนซ์ใหม่ของบุคคล
    • ทั้ง " person " และ " anotherReferenceToTheSamePersonObject " มีค่า 3bad086a เท่ากัน
    • อย่าลอง แต่ person == anotherReferenceToTheSamePersonObject จะเป็นจริง
    • ตัวแปรทั้งสองมีสำเนาประจำตัวของการอ้างอิงและทั้งสองอ้างถึงวัตถุบุคคลเดียวกันวัตถุเดียวกันบนกองและไม่ใช่สำเนา

ภาพที่มีค่าพันคำ:

โปรดสังเกตว่าลูกศร anotherReferenceToTheSamePersonObject มุ่งตรงไปยัง Object และไม่ใช่ไปที่ตัวแปร person!

หากคุณไม่ได้รับมันแล้วก็เชื่อใจฉันและจำไว้ว่ามันจะดีกว่าที่จะบอกว่าJava เป็นผ่านค่า ดีผ่านค่าอ้างอิง โอ้ยิ่งดีไปกว่านั้นคือpass-by-copy-of-the-variable-value! ;)

ตอนนี้อย่าลังเลที่จะเกลียดฉัน แต่โปรดทราบว่าเนื่องจากสิ่งนี้ไม่มีความแตกต่างระหว่างการส่งผ่านประเภทข้อมูลดั้งเดิมและ Objectsเมื่อพูดถึงข้อโต้แย้งของวิธีการ

คุณส่งสำเนาบิตของค่าอ้างอิงเสมอ!

  • หากเป็นประเภทข้อมูลดั้งเดิมบิตเหล่านี้จะมีค่าของชนิดข้อมูลดั้งเดิมเอง
  • ถ้าเป็น Object บิตจะมีค่าของแอดเดรสที่บอก JVM ว่าจะไปยัง Object ได้อย่างไร

Java เป็น pass-by-value เนื่องจากภายในเมธอดคุณสามารถแก้ไขอ็อบเจ็กต์ที่อ้างอิงได้มากเท่าที่คุณต้องการ แต่ไม่ว่าคุณจะพยายามแค่ไหนคุณก็จะไม่สามารถแก้ไขตัวแปรที่ส่งผ่านซึ่งจะทำการอ้างอิงต่อไป (ไม่ใช่ p _ _ _ _ _ _ _) วัตถุเดียวกันไม่ว่าจะเกิดอะไรขึ้น!


ฟังก์ชัน changeName ด้านบนจะไม่สามารถแก้ไขเนื้อหาจริง (ค่าบิต) ของการอ้างอิงที่ส่งผ่าน ในคำอื่น changeName ไม่สามารถทำให้บุคคลบุคคลอ้างถึงวัตถุอื่นได้


แน่นอนคุณสามารถตัดมันให้สั้นและพูดได้ว่าJava เป็น pass-by-value!

720 SCdF Sep 03 2008 at 03:19

Java จะผ่านเสมอโดยค่าโดยไม่มีข้อยกเว้นที่เคย

แล้วใคร ๆ ก็สับสนกับเรื่องนี้ได้อย่างไรและเชื่อว่า Java ผ่านการอ้างอิงหรือคิดว่าพวกเขามีตัวอย่างของ Java ที่ทำหน้าที่เป็น pass by reference? ประเด็นสำคัญคือ Java ไม่เคยให้การเข้าถึงค่าของอ็อบเจ็กต์โดยตรงไม่ว่าในกรณีใด ๆ การเข้าถึงวัตถุเพียงอย่างเดียวคือผ่านการอ้างอิงไปยังวัตถุนั้น เพราะวัตถุ Java เป็นเสมอเข้าถึงได้ผ่านการอ้างอิงโดยตรงมากกว่ามันเป็นเรื่องธรรมดาที่จะพูดคุยเกี่ยวกับเขตข้อมูลและตัวแปรและข้อโต้แย้งวิธีการในฐานะที่เป็นวัตถุเมื่ออวดพวกเขาเป็นเพียงการอ้างอิงไปยังวัตถุ ความสับสนเกิดจากการเปลี่ยนแปลงในระบบการตั้งชื่อนี้ (พูดอย่างเคร่งครัดไม่ถูกต้อง)

ดังนั้นเมื่อเรียกใช้เมธอด

  • สำหรับอาร์กิวเมนต์ดั้งเดิม ( int, longฯลฯ ) ผ่านค่าคือค่าที่แท้จริงของดั้งเดิม (เช่น 3)
  • สำหรับวัตถุผ่านค่าคือค่าของการอ้างอิงไปยังวัตถุ

ดังนั้นหากคุณมีdoSomething(foo)และpublic void doSomething(Foo foo) { .. }Foos ทั้งสองได้คัดลอกการอ้างอิงที่ชี้ไปที่วัตถุเดียวกัน

โดยธรรมชาติแล้วการส่งผ่านค่าการอ้างอิงไปยังวัตถุนั้นดูคล้ายมาก (และแยกไม่ออกในทางปฏิบัติ) การส่งผ่านวัตถุโดยการอ้างอิง

345 ScArcher2 Sep 03 2008 at 03:20

Java ส่งผ่านการอ้างอิงตามค่า

คุณจึงไม่สามารถเปลี่ยนข้อมูลอ้างอิงที่ส่งผ่านได้

249 cutmancometh Sep 11 2013 at 18:34

ฉันรู้สึกว่าการเถียงกันเกี่ยวกับ "pass-by-reference vs pass-by-value" นั้นไม่เป็นประโยชน์อย่างยิ่ง

หากคุณพูดว่า "Java คือ pass-by-any (การอ้างอิง / ค่า)" ไม่ว่าในกรณีใดคุณจะไม่ได้ให้คำตอบที่สมบูรณ์ นี่คือข้อมูลเพิ่มเติมที่หวังว่าจะช่วยในการทำความเข้าใจสิ่งที่เกิดขึ้นในความทรงจำ

หลักสูตรความผิดพลาดในสแต็ก / ฮีปก่อนที่เราจะนำไปใช้งาน 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);
}

One String ถูกสร้างขึ้นและมีการจัดสรรพื้นที่ในฮีปและแอดเดรสของสตริงจะถูกเก็บไว้ในสแต็กและกำหนดตัวระบุhisNameเนื่องจากแอดเดรสของสตริงที่สองจะเหมือนกับสตริงแรกจึงไม่มีการสร้างสตริงใหม่และ ไม่มีการจัดสรรพื้นที่ฮีปใหม่ แต่มีการสร้างตัวระบุใหม่บนสแต็ก จากนั้นเราเรียกว่าshout(): สร้างสแต็กเฟรมใหม่และตัวระบุใหม่nameถูกสร้างและกำหนดที่อยู่ของสตริงที่มีอยู่แล้ว

ดังนั้นค่าอ้างอิง? คุณพูดว่า "มันฝรั่ง"

199 Eclipse Sep 18 2008 at 00:38

เพียงเพื่อแสดงความคมชัดให้เปรียบเทียบตัวอย่างC ++และJavaต่อไปนี้:

ใน C ++: หมายเหตุ: รหัสไม่ถูกต้อง - หน่วยความจำรั่ว! แต่มันแสดงให้เห็นถึงประเด็น

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

ใน 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 มีการส่งผ่านสองประเภทเท่านั้น: ตามค่าสำหรับชนิดในตัวและตามค่าของตัวชี้สำหรับชนิดอ็อบเจ็กต์

188 JohnChanning Sep 03 2008 at 03:23

Java ส่งผ่านการอ้างอิงไปยังอ็อบเจ็กต์ตามค่า

180 HankGay Sep 03 2008 at 03:21

โดยพื้นฐานแล้วการกำหนดพารามิเตอร์ Object ใหม่จะไม่ส่งผลต่ออาร์กิวเมนต์เช่น

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

จะพิมพ์ออกมาแทน"Hah!" nullด้วยเหตุนี้การทำงานเป็นเพราะbarเป็นสำเนาของมูลค่าซึ่งเป็นเพียงการอ้างอิงถึงbaz "Hah!"ถ้ามันมีการอ้างอิงตัวจริงแล้วfooจะมีนิยามใหม่ให้กับbaznull

155 JörgWMittag Sep 08 2010 at 05:07

ฉันไม่อยากจะเชื่อเลยว่ายังไม่มีใครพูดถึง Barbara Liskov เมื่อเธอออกแบบ CLU ในปีพ. ศ. 2517 เธอประสบปัญหาคำศัพท์เดียวกันนี้และเธอได้คิดค้นคำเรียกโดยการแชร์ (หรือที่เรียกว่าการเรียกโดยการแชร์วัตถุและการเรียกตามวัตถุ ) สำหรับกรณีเฉพาะนี้ของ "การเรียกตามค่าโดยที่ค่าเป็น เอกสารอ้างอิง ".

121 JacquesB Jan 13 2009 at 03:50

ประเด็นสำคัญคือการอ้างอิงคำในนิพจน์ "pass by reference" หมายถึงสิ่งที่แตกต่างอย่างสิ้นเชิงจากความหมายปกติของการอ้างอิงคำใน Java

มักจะอยู่ใน Java อ้างอิงหมายความ AA อ้างอิงถึงวัตถุ แต่คำศัพท์ทางเทคนิคผ่านการอ้างอิง / ค่าจากทฤษฎีภาษาการเขียนโปรแกรมกำลังพูดถึงการอ้างอิงถึงเซลล์หน่วยความจำที่ถือตัวแปรซึ่งเป็นสิ่งที่แตกต่างอย่างสิ้นเชิง

87 Srle Nov 21 2013 at 23:04

ใน java ทุกอย่างอ้างอิงดังนั้นเมื่อคุณมีสิ่งที่ต้องการ: Point pnt1 = new Point(0,0);Java ทำตาม:

  1. สร้างวัตถุจุดใหม่
  2. สร้างการอ้างอิงจุดใหม่และเริ่มต้นการอ้างอิงนั้นไปยังจุด (อ้างถึง)บนวัตถุจุดที่สร้างไว้ก่อนหน้านี้
  3. จากที่นี่ผ่าน Point object life คุณจะเข้าถึงออบเจ็กต์นั้นผ่านการอ้างอิง 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);

การสร้างวัตถุสองจุดที่แตกต่างกันโดยมีการอ้างอิงที่แตกต่างกันสองรายการที่เกี่ยวข้อง

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

ในบรรทัดนี้ 'pass-by-value' เข้าสู่การเล่น ...

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

การอ้างอิงpnt1และpnt2ถูกส่งต่อด้วยค่าไปยังวิธีการที่ยุ่งยากซึ่งหมายความว่าตอนนี้การอ้างอิงของคุณpnt1และpnt2มีcopiesชื่อarg1และarg2ดังนั้นpnt1และarg1 ชี้ไปที่วัตถุเดียวกัน (เหมือนกันสำหรับpnt2และarg2)

ในtrickyวิธีการ:

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

ถัดไปในtrickyวิธีการ

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

ขั้นแรกให้คุณสร้างtempการอ้างอิงจุดใหม่ซึ่งจะชี้ไปที่เดียวกันเช่น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
86 Ganesh Oct 17 2013 at 14:54

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? ไม่เพราะมันส่งผ่านสำเนาของข้อมูลอ้างอิงเท่านั้น ในกรณีของการส่งผ่านโดยการอ้างอิงอาจมีการโยน a NullPointerExceptionดังที่แสดงด้านล่าง:

หวังว่านี่จะช่วยได้

79 Sanjeev Jan 26 2019 at 04:37

มีคำตอบที่ดีอยู่แล้วที่ครอบคลุมเรื่องนี้ ฉันต้องการมีส่วนร่วมเล็กน้อยโดยการแบ่งปันตัวอย่างง่ายๆ (ซึ่งจะรวบรวม) ที่ตัดกันพฤติกรรมระหว่าง Pass-by-reference ใน c ++ และ Pass-by-value ใน Java

บางจุด:

  1. คำว่า "การอ้างอิง" เป็นคำที่มีสองความหมายแยกกันมากเกินไป ใน Java หมายถึงตัวชี้ แต่ในบริบทของ "Pass-by-reference" หมายถึงตัวจัดการตัวแปรดั้งเดิมที่ส่งผ่านเข้ามา
  2. Java เป็นผ่านโดยค่า Java สืบเชื้อสายมาจาก C (ในภาษาอื่น ๆ ) ก่อนหน้า C ภาษาก่อนหน้านี้หลายภาษา (แต่ไม่ใช่ทั้งหมด) เช่น FORTRAN และ COBOL รองรับ PBR แต่ C ไม่รองรับ PBR อนุญาตให้ภาษาอื่น ๆ เหล่านี้ทำการเปลี่ยนแปลงตัวแปรที่ส่งผ่านภายในรูทีนย่อย เพื่อให้บรรลุสิ่งเดียวกัน (เช่นเปลี่ยนค่าของตัวแปรภายในฟังก์ชัน) โปรแกรมเมอร์ C ส่งพอยน์เตอร์ไปยังตัวแปรเป็นฟังก์ชัน ภาษาที่ได้รับแรงบันดาลใจจาก C เช่น Java ยืมแนวคิดนี้และส่งต่อตัวชี้ไปยังวิธีการเช่นเดียวกับ C ยกเว้น Java เรียกตัวชี้การอ้างอิง อีกครั้งนี่เป็นการใช้คำว่า "Reference" ที่แตกต่างจากใน "Pass-By-Reference"
  3. C ++ ช่วยให้ Pass-by-referenceโดยการประกาศพารามิเตอร์อ้างอิงโดยใช้อักขระ "&" ​​(ซึ่งเป็นอักขระเดียวกับที่ใช้ระบุ "ที่อยู่ของตัวแปร" ทั้งใน C และ C ++) ตัวอย่างเช่นหากเราส่งผ่านตัวชี้โดยการอ้างอิงพารามิเตอร์และอาร์กิวเมนต์ไม่ได้ชี้ไปที่วัตถุเดียวกันเท่านั้น แต่เป็นตัวแปรเดียวกัน หากได้รับการตั้งค่าเป็นที่อยู่อื่นหรือเป็นโมฆะอีกอันก็เช่นกัน
  4. ใน C ++ ตัวอย่างด้านล่างฉันผ่านตัวชี้ไปโมฆะยกเลิกสตริงโดยอ้างอิง และในตัวอย่าง Java ด้านล่างฉันกำลังส่งการอ้างอิง Java ไปยัง String (อีกครั้งเช่นเดียวกับตัวชี้ไปยัง String) ตามค่า สังเกตผลลัพธ์ในความคิดเห็น

C ++ ผ่านตัวอย่างอ้างอิง:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Java pass "a Java reference" ตามตัวอย่างค่า

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

แก้ไข

หลายคนเขียนความคิดเห็นซึ่งดูเหมือนจะบ่งชี้ว่าพวกเขาไม่ได้ดูตัวอย่างของฉันหรือไม่ได้รับตัวอย่าง c ++ ไม่แน่ใจว่าปลดตรงไหน แต่เดาตัวอย่าง c ++ ไม่ชัดเจน ฉันโพสต์ตัวอย่างเดียวกันนี้ในภาษาปาสคาลเพราะฉันคิดว่าพาส - บาย - การอ้างอิงดูสะอาดกว่าในภาษาปาสคาล แต่ฉันคิดผิด ฉันอาจจะทำให้คนอื่นสับสนมากขึ้น ฉันหวังว่าจะไม่

ในภาษาปาสคาลพารามิเตอร์ที่ส่งผ่านโดยการอ้างอิงเรียกว่า "พารามิเตอร์ var" ในขั้นตอน setToNil ด้านล่างโปรดสังเกตคำหลัก 'var' ซึ่งนำหน้าพารามิเตอร์ 'ptr' เมื่อมีการชี้ถูกส่งไปยังขั้นตอนนี้ก็จะถูกส่งผ่านโดยการอ้างอิง สังเกตพฤติกรรม: เมื่อโพรซีเดอร์นี้ตั้งค่า ptr เป็น nil (นั่นคือ pascal พูดแทน NULL) มันจะตั้งค่าอาร์กิวเมนต์เป็นศูนย์ - คุณไม่สามารถทำได้ใน Java

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

แก้ไข 2

ข้อความที่ตัดตอนมาบางส่วนจาก"THE Java Programming Language"โดย Ken Arnold, James Gosling (ชายผู้คิดค้น Java)และ David Holmes บทที่ 2 ส่วน 2.6.5

พารามิเตอร์ทั้งหมดวิธีการจะถูกส่งผ่าน "โดยค่า" กล่าวอีกนัยหนึ่งค่าของตัวแปรพารามิเตอร์ในวิธีการคือสำเนาของผู้เรียกใช้ที่ระบุเป็นอาร์กิวเมนต์

เขายังคงให้ประเด็นเดียวกันเกี่ยวกับวัตถุ . .

คุณควรทราบว่าเมื่อพารามิเตอร์การอ้างอิงวัตถุมันเป็นวัตถุอ้างอิงไม่ได้วัตถุเองที่จะถูกส่งผ่านไป "โดยค่า"

และในตอนท้ายของส่วนเดียวกันเขาได้กล่าวให้กว้างขึ้นเกี่ยวกับ java ว่าเป็นเพียงการส่งผ่านค่าและไม่ผ่านการอ้างอิง

ภาษาโปรแกรม Java ไม่ส่งผ่านวัตถุโดยการอ้างอิง มัน ผ่านไปอ้างอิงวัตถุโดยค่า เนื่องจากสำเนาสองชุดของการอ้างอิงเดียวกันอ้างถึงวัตถุจริงเดียวกันการเปลี่ยนแปลงที่เกิดขึ้นผ่านตัวแปรอ้างอิงหนึ่งจึงมองเห็นได้ผ่านอีกตัวแปรหนึ่ง มีพารามิเตอร์เดียวที่ผ่านโหมดผ่านค่า - และช่วยให้สิ่งต่างๆง่ายขึ้น

ส่วนนี้ของหนังสือเล่มนี้มีคำอธิบายที่ดีเยี่ยมเกี่ยวกับพารามิเตอร์ที่ส่งผ่านใน Java และความแตกต่างระหว่าง pass-by-reference และ pass-by-value และเป็นของผู้สร้าง Java ฉันขอแนะนำให้ทุกคนอ่านโดยเฉพาะอย่างยิ่งหากคุณยังไม่มั่นใจ

ฉันคิดว่าความแตกต่างระหว่างสองรุ่นนั้นมีความละเอียดอ่อนมากและถ้าคุณไม่ได้เขียนโปรแกรมที่คุณใช้ pass-by-reference จริงๆแล้วมันก็ง่ายที่จะพลาดตรงที่ทั้งสองรุ่นต่างกัน

ฉันหวังว่าสิ่งนี้จะยุติการอภิปราย แต่อาจจะไม่เป็นเช่นนั้น

แก้ไข 3

ฉันอาจจะหมกมุ่นอยู่กับกระทู้นี้เล็กน้อย อาจเป็นเพราะฉันรู้สึกว่าผู้สร้าง Java เผยแพร่ข้อมูลที่ผิดโดยไม่ได้ตั้งใจ ถ้าแทนที่จะใช้คำว่า "reference" สำหรับคำชี้ที่พวกเขาใช้อย่างอื่นให้พูดว่า dingleberry ก็ไม่มีปัญหา คุณสามารถพูดได้ว่า "Java ส่งผ่าน dingleberries ด้วยมูลค่าไม่ใช่โดยการอ้างอิง" ก็ไม่มีใครสับสน

นั่นเป็นเหตุผลที่มีเพียงนักพัฒนา Java เท่านั้นที่มีปัญหากับสิ่งนี้ พวกเขามองไปที่คำว่า "การอ้างอิง" และคิดว่าพวกเขารู้ดีว่ามันหมายถึงอะไรดังนั้นพวกเขาจึงไม่ต้องกังวลที่จะพิจารณาข้อโต้แย้งของฝ่ายตรงข้าม

อย่างไรก็ตามฉันสังเกตเห็นความคิดเห็นในโพสต์ที่เก่ากว่าซึ่งทำให้คล้ายคลึงกับบอลลูนที่ฉันชอบมาก มากจนฉันตัดสินใจที่จะรวมคลิปอาร์ตเข้าด้วยกันเพื่อสร้างชุดการ์ตูนเพื่อแสดงให้เห็นประเด็น

การส่งผ่านการอ้างอิงตามค่า - การเปลี่ยนแปลงการอ้างอิงจะไม่แสดงในขอบเขตของผู้เรียก แต่การเปลี่ยนแปลงของออบเจ็กต์คือ เนื่องจากการอ้างอิงถูกคัดลอก แต่ทั้งต้นฉบับและสำเนาอ้างถึงวัตถุเดียวกัน

ผ่านการอ้างอิง -ไม่มีสำเนาเอกสารอ้างอิง การอ้างอิงเดี่ยวจะใช้ร่วมกันโดยทั้งผู้เรียกและฟังก์ชันที่ถูกเรียก การเปลี่ยนแปลงการอ้างอิงหรือข้อมูลของวัตถุจะแสดงในขอบเขตของผู้โทร

แก้ไข 4

ฉันได้เห็นโพสต์ในหัวข้อนี้ซึ่งอธิบายถึงการใช้พารามิเตอร์ในระดับต่ำใน Java ซึ่งฉันคิดว่าดีและมีประโยชน์มากเพราะมันทำให้แนวคิดนามธรรมเป็นรูปธรรม อย่างไรก็ตามสำหรับฉันคำถามเกี่ยวกับพฤติกรรมที่อธิบายไว้ในข้อกำหนดภาษามากกว่าเกี่ยวกับการใช้งานทางเทคนิคของพฤติกรรม นี่เป็นการคัดลอกมาจากข้อกำหนดภาษา Java ส่วน 8.4.1 :

เมื่อเรียกใช้เมธอดหรือคอนสตรัคเตอร์ (§15.12) ค่าของนิพจน์อาร์กิวเมนต์ที่แท้จริงจะเริ่มต้นตัวแปรพารามิเตอร์ที่สร้างขึ้นใหม่แต่ละประเภทที่ประกาศก่อนที่จะดำเนินการเนื้อหาของเมธอดหรือตัวสร้าง ตัวระบุที่ปรากฏใน DeclaratorId อาจใช้เป็นชื่อธรรมดาในเนื้อความของวิธีการหรือตัวสร้างเพื่ออ้างถึงพารามิเตอร์ที่เป็นทางการ

ซึ่งหมายความว่า java จะสร้างสำเนาของพารามิเตอร์ที่ผ่านก่อนที่จะเรียกใช้เมธอด เช่นเดียวกับคนส่วนใหญ่ที่เรียนคอมไพเลอร์ในวิทยาลัยฉันใช้"The Dragon Book"ซึ่งเป็นหนังสือTHE compilers มีคำอธิบายที่ดีของ "Call-by-value" และ "Call-by-Reference" ในบทที่ 1 คำอธิบาย Call-by-value ตรงกับ Java Specs ทุกประการ

ย้อนกลับไปตอนที่ฉันเรียนคอมไพเลอร์ในช่วงทศวรรษที่ 90 ฉันใช้หนังสือรุ่นแรกจากปี 1986 ซึ่งก่อนวันที่ Java ประมาณ 9 หรือ 10 ปี อย่างไรก็ตามฉันเพิ่งพบสำเนาของการแก้ไขครั้งที่ 2จากปี 2550 ซึ่งกล่าวถึง Java! ส่วนที่ 1.6.6 ระบุว่า "กลไกการส่งผ่านพารามิเตอร์" อธิบายถึงพารามิเตอร์ที่ส่งผ่านได้อย่างสวยงาม นี่คือข้อความที่ตัดตอนมาภายใต้หัวข้อ "Call-by-value" ซึ่งกล่าวถึง Java:

ในการเรียกตามค่าพารามิเตอร์จริงจะถูกประเมิน (ถ้าเป็นนิพจน์) หรือคัดลอก (ถ้าเป็นตัวแปร) ค่าถูกวางไว้ในตำแหน่งที่เป็นของพารามิเตอร์ที่เป็นทางการที่เกี่ยวข้องของโพรซีเดอร์ที่เรียก วิธีนี้ใช้ใน C และ Java และเป็นตัวเลือกทั่วไปใน C ++ เช่นเดียวกับภาษาอื่น ๆ ส่วนใหญ่

73 Himanshuarora Apr 08 2017 at 12:51

Java เป็นการเรียกตามค่า

มันทำงานอย่างไร

  • คุณส่งสำเนาบิตของค่าอ้างอิงเสมอ!

  • หากเป็นประเภทข้อมูลดั้งเดิมบิตเหล่านี้มีค่าของชนิดข้อมูลดั้งเดิมด้วยเหตุนี้หากเราเปลี่ยนค่าของส่วนหัวภายในวิธีการนั้นจะไม่สะท้อนการเปลี่ยนแปลงภายนอก

  • หากเป็นประเภทข้อมูลออบเจ็กต์เช่นFoo foo = new Foo ()จากนั้นในกรณีนี้สำเนาของที่อยู่ของวัตถุจะผ่านไปเช่นทางลัดไฟล์สมมติว่าเรามีไฟล์ข้อความabc.txtที่C: \ desktopและสมมติว่าเราสร้างทางลัดของ ไฟล์เดียวกันและวางไว้ในC: \ desktop \ abc-shortcutดังนั้นเมื่อคุณเข้าถึงไฟล์จากC: \ desktop \ abc.txtและเขียน'Stack Overflow'และปิดไฟล์จากนั้นคุณจะเปิดไฟล์อีกครั้งจากทางลัด เขียน'เป็นชุมชนออนไลน์ที่ใหญ่ที่สุดสำหรับโปรแกรมเมอร์ในการเรียนรู้'จากนั้นการเปลี่ยนแปลงไฟล์ทั้งหมดจะเป็น'Stack Overflow เป็นชุมชนออนไลน์ที่ใหญ่ที่สุดสำหรับโปรแกรมเมอร์ในการเรียนรู้'ซึ่งหมายความว่าไม่สำคัญว่าคุณจะเปิดไฟล์จากที่ใดในแต่ละครั้งที่เราเข้าถึง ไฟล์เดียวกันที่นี่เราสามารถสมมติFooเป็นไฟล์และสมมติว่า foo เก็บไว้ที่123hd7h (ที่อยู่เดิมเช่นที่อยู่C: \ desktop \ abc.txt ) และ234jdid (ที่อยู่ที่คัดลอกเช่นC: \ desktop \ abc-shortcutซึ่งจริงๆแล้วมี ที่อยู่เดิมของไฟล์ภายใน) .. ดังนั้นเพื่อความเข้าใจที่ดีขึ้นให้สร้าง Shortcut file และรู้สึก ..

72 karatedog Dec 13 2013 at 19:23

การอ้างอิงเป็นค่าเสมอเมื่อแสดงไม่ว่าคุณจะใช้ภาษาอะไร

รับมุมมองนอกกรอบมาดูที่ Assembly หรือการจัดการหน่วยความจำระดับต่ำ ในระดับ CPU การอ้างอิงถึงสิ่งใด ๆ จะกลายเป็นค่าทันทีหากถูกเขียนไปยังหน่วยความจำหรือลงทะเบียนซีพียูตัวใดตัวหนึ่ง (นั่นคือเหตุผลที่ตัวชี้เป็นคำจำกัดความที่ดีมันคือค่าซึ่งมีจุดประสงค์ในเวลาเดียวกัน)

ข้อมูลในหน่วยความจำมีตำแหน่งและที่ตำแหน่งนั้นจะมีค่า (ไบต์คำหรืออะไรก็ตาม) ในแอสเซมบลีเรามีวิธีแก้ปัญหาที่สะดวกในการตั้งชื่อให้กับตำแหน่ง (ตัวแปร aka) แต่เมื่อคอมไพล์โค้ดแอสเซมเบลอร์จะแทนที่ชื่อด้วยตำแหน่งที่กำหนดเช่นเดียวกับเบราว์เซอร์ของคุณแทนที่ชื่อโดเมนด้วยที่อยู่ IP

ในทางเทคนิคเป็นไปไม่ได้ที่จะส่งการอ้างอิงไปยังสิ่งใด ๆ ในภาษาใด ๆ โดยไม่ต้องแทนค่า (เมื่อมันกลายเป็นค่าในทันที)

สมมติว่าเรามีตัวแปร Foo ตำแหน่งของมันอยู่ที่ 47 ไบต์ในหน่วยความจำและค่าของมันคือ 5 เรามีตัวแปรRef2Fooอีกตัวหนึ่งซึ่งอยู่ที่ 223 ไบต์ในหน่วยความจำและค่าของมันจะเท่ากับ 47 Ref2Foo นี้อาจเป็นตัวแปรทางเทคนิค ไม่ได้สร้างขึ้นโดยโปรแกรมอย่างชัดเจน หากคุณเพียงแค่มองไปที่ 5 และ 47 โดยไม่มีข้อมูลอื่น ๆ ที่คุณจะเห็นเพียงสองค่า หากคุณใช้เป็นข้อมูลอ้างอิงเพื่อเข้าถึง5เราต้องเดินทาง:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

นี่คือวิธีการทำงานของตารางกระโดด

หากเราต้องการเรียกใช้ method / function / procedure ด้วยค่าของ Foo มีสองสามวิธีที่เป็นไปได้ในการส่งผ่านตัวแปรไปยังเมธอดทั้งนี้ขึ้นอยู่กับภาษาและโหมดการเรียกใช้เมธอดต่างๆ:

  1. 5 ถูกคัดลอกไปยังหนึ่งในการลงทะเบียน CPU (เช่น EAX)
  2. 5 ได้รับ PUSHd ไปยังสแต็ก
  3. 47 ถูกคัดลอกไปยังหนึ่งในการลงทะเบียน CPU
  4. 47 PUSHd ไปที่สแต็ก
  5. 223 ถูกคัดลอกไปยังหนึ่งในการลงทะเบียน CPU
  6. 223 ได้รับ PUSHd ไปยังสแต็ก

ในทุกกรณีที่อยู่เหนือค่า - มีการสร้างสำเนาของค่าที่มีอยู่แล้วตอนนี้ก็เป็นวิธีการรับที่จะจัดการกับมัน เมื่อคุณเขียน "Foo" ภายในเมธอดมันจะถูกอ่านจาก EAX หรือยกเลิกการอ้างอิงโดยอัตโนมัติหรือการอ้างอิงสองครั้งกระบวนการจะขึ้นอยู่กับวิธีการทำงานของภาษาและ / หรือประเภทของ Foo ที่กำหนด สิ่งนี้ถูกซ่อนจากนักพัฒนาจนกว่าเธอจะหลีกเลี่ยงขั้นตอนการอ้างอิง ดังนั้นการอ้างอิงจึงเป็นค่าเมื่อแสดงเนื่องจากการอ้างอิงเป็นค่าที่ต้องดำเนินการ (ในระดับภาษา)

ตอนนี้เราได้ส่ง Foo ไปยังวิธีการ:

  • ในกรณีที่ 1. และ 2. หากคุณเปลี่ยน Foo ( Foo = 9) จะมีผลเฉพาะขอบเขตเฉพาะที่คุณมีสำเนาของค่า จากภายในวิธีการเราไม่สามารถระบุได้ว่า Foo ดั้งเดิมอยู่ที่ใดในหน่วยความจำ
  • ในกรณีที่ 3. และ 4. หากคุณใช้โครงสร้างภาษาเริ่มต้นและเปลี่ยน Foo ( Foo = 11) มันสามารถเปลี่ยน Foo ได้ทั่วโลก (ขึ้นอยู่กับภาษาเช่น Java หรือเหมือนกับprocedure findMin(x, y, z: integer;var mของ Pascal : integer);) แต่ถ้าภาษาที่ช่วยให้คุณสามารถที่จะหลีกเลี่ยงกระบวนการ dereference คุณสามารถเปลี่ยนพูดกับ47 49เมื่อถึงจุดนั้น Foo ดูเหมือนจะเปลี่ยนไปถ้าคุณอ่านเพราะคุณเปลี่ยนตัวชี้ท้องถิ่นเป็นมัน และถ้าคุณแก้ไข Foo นี้ภายในเมธอด ( Foo = 12) คุณอาจจะ FUBAR การดำเนินการของโปรแกรม (aka. segfault) เนื่องจากคุณจะเขียนไปยังหน่วยความจำที่แตกต่างจากที่คาดไว้คุณยังสามารถปรับเปลี่ยนพื้นที่ที่กำหนดให้สามารถปฏิบัติการได้ โปรแกรมและการเขียนมันจะแก้ไขโค้ดที่กำลังทำงานอยู่ (ตอนนี้ Foo ไม่อยู่ที่47) แต่ค่าของ Foo 47ไม่ได้เปลี่ยนไปทั่วโลกมีเพียงค่าเดียวที่อยู่ในเมธอดเท่านั้นเนื่องจาก47เป็นสำเนาของเมธอดด้วย
  • ในกรณีที่ 5. และ 6. หากคุณแก้ไข223ภายในเมธอดมันจะสร้างการทำร้ายร่างกายเช่นเดียวกับในข้อ 3. หรือ 4 (ตัวชี้ที่ชี้ไปยังค่าที่ไม่ดีในขณะนี้ซึ่งถูกใช้เป็นตัวชี้อีกครั้ง) แต่ยังคงเป็นแบบโลคัล ปัญหาเป็น 223 ถูกคัดลอก อย่างไรก็ตามหากคุณสามารถหักล้างการอ้างอิงRef2Foo(นั่นคือ223) เข้าถึงและแก้ไขค่าที่ชี้ได้47กล่าวว่าถึง49จะส่งผลต่อ Foo ทั่วโลกเพราะในกรณีนี้เมธอดมีสำเนา223แต่มีการอ้างอิง47เพียงครั้งเดียวและเปลี่ยนสิ่งนั้น to 49จะทำให้ทุกRef2Fooการอ้างอิงสองครั้งมีค่าที่ไม่ถูกต้อง

การมองข้ามรายละเอียดที่ไม่สำคัญแม้แต่ภาษาที่ผ่านการอ้างอิงจะส่งผ่านค่าไปยังฟังก์ชัน แต่ฟังก์ชันเหล่านั้นทราบดีว่าต้องใช้เพื่อวัตถุประสงค์ในการอ้างอิง นี้ผ่านที่อ้างอิงเป็นค่าที่ถูกซ่อนอยู่เพียงโปรแกรมเมอร์เพราะมันจะไม่ได้ผลในทางปฏิบัติและคำศัพท์เป็นเพียงการส่งผ่านโดยการอ้างอิง

pass-by-value ที่เข้มงวดก็ไม่มีประโยชน์เช่นกันหมายความว่าต้องคัดลอกอาร์เรย์ 100 Mbyte ทุกครั้งที่เราเรียกเมธอดที่มีอาร์เรย์ว่าอาร์กิวเมนต์ดังนั้น Java จึงไม่สามารถเป็น pass-by-value ที่เข้มงวดได้ ทุกภาษาจะส่งการอ้างอิงไปยังอาร์เรย์ขนาดใหญ่นี้ (เป็นค่า) และใช้กลไกการคัดลอกเมื่อเขียนหากอาร์เรย์นั้นสามารถเปลี่ยนแปลงได้ภายในวิธีการหรืออนุญาตให้เมธอด (เช่นเดียวกับ Java) เพื่อแก้ไขอาร์เรย์ทั่วโลก (จาก มุมมองของผู้โทร) และบางภาษาอนุญาตให้แก้ไขค่าของการอ้างอิงเอง

ดังนั้นในระยะสั้นและในคำศัพท์ของตัวเองของ Java, Java คือผ่านโดยมีมูลค่าที่คุ้มค่าสามารถ: ทั้งมูลค่าที่แท้จริงหรือค่าที่เป็นตัวแทนของการอ้างอิง

58 kukudas Apr 02 2009 at 04:33

เท่าที่ฉันรู้ Java รู้แค่การเรียกตามค่าเท่านั้น ซึ่งหมายความว่าสำหรับประเภทข้อมูลดั้งเดิมคุณจะทำงานกับสำเนาและสำหรับวัตถุคุณจะทำงานกับสำเนาการอ้างอิงถึงวัตถุ อย่างไรก็ตามฉันคิดว่ามีข้อผิดพลาดบางอย่าง ตัวอย่างเช่นสิ่งนี้จะไม่ทำงาน:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

สิ่งนี้จะเติมข้อมูล Hello World ไม่ใช่ World Hello เนื่องจากในฟังก์ชัน swap คุณใช้ copys ซึ่งไม่มีผลกระทบต่อการอ้างอิงใน main แต่ถ้าวัตถุของคุณไม่เปลี่ยนรูปคุณสามารถเปลี่ยนได้เช่น:

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

สิ่งนี้จะเติม Hello World ในบรรทัดคำสั่ง ถ้าคุณเปลี่ยน StringBuffer เป็น String มันจะสร้างแค่ Hello เพราะ String ไม่เปลี่ยนรูป ตัวอย่างเช่น:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

อย่างไรก็ตามคุณสามารถสร้าง wrapper สำหรับ String เช่นนี้ซึ่งจะทำให้สามารถใช้กับ Strings ได้:

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

แก้ไข: ฉันเชื่อว่านี่เป็นเหตุผลที่ต้องใช้ StringBuffer เมื่อพูดถึงการ "เพิ่ม" สองสตริงเพราะคุณสามารถแก้ไขวัตถุดั้งเดิมที่คุณไม่สามารถใช้กับวัตถุที่ไม่เปลี่ยนรูปได้เช่น String is

56 rorrohprog Oct 12 2012 at 10:00

ไม่มันไม่ผ่านการอ้างอิง

Java ส่งผ่านค่าตามข้อกำหนดภาษา Java:

เมื่อเรียกใช้เมธอดหรือคอนสตรัคเตอร์ (§15.12) ค่าของนิพจน์อาร์กิวเมนต์ที่แท้จริงจะเริ่มต้นตัวแปรพารามิเตอร์ที่สร้างขึ้นใหม่แต่ละประเภทที่ประกาศก่อนที่จะดำเนินการเนื้อหาของเมธอดหรือตัวสร้าง ตัวบ่งชี้ที่ปรากฏใน DeclaratorId อาจจะใช้เป็นชื่อที่ง่ายในร่างกายของวิธีการหรือสร้างเพื่ออ้างถึงพารามิเตอร์อย่างเป็นทางการ

53 spiderman May 13 2015 at 04:24

ให้ฉันพยายามอธิบายความเข้าใจของฉันด้วยความช่วยเหลือจากสี่ตัวอย่าง Java เป็น pass-by-value และไม่ใช่ pass-by-reference

/ **

ส่งผ่านค่า

ใน Java พารามิเตอร์ทั้งหมดจะถูกส่งผ่านด้วยค่ากล่าวคือการกำหนดอาร์กิวเมนต์เมธอดจะไม่ปรากฏให้ผู้เรียกเห็น

* /

ตัวอย่างที่ 1:

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

ผลลัพธ์

output : output
value : Nikhil
valueflag : false

ตัวอย่างที่ 2:

/ ** * * ผ่านค่า * * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

ผลลัพธ์

output : output
value : Nikhil
valueflag : false

ตัวอย่างที่ 3:

/ ** 'Pass By Value นี้มีความรู้สึก' Pass By Reference '

บางคนบอกว่าประเภทดั้งเดิมและ 'String' คือ 'pass by value' และ object คือ 'pass by reference'

แต่จากตัวอย่างนี้เราสามารถเข้าใจได้ว่า infact pass by value เท่านั้นโปรดทราบว่าที่นี่เรากำลังส่งข้อมูลอ้างอิงเป็นค่า กล่าวคือ: การอ้างอิงถูกส่งผ่านด้วยค่า นั่นเป็นเหตุผลที่สามารถเปลี่ยนแปลงได้และยังคงเป็นจริงหลังจากขอบเขตท้องถิ่น แต่เราไม่สามารถเปลี่ยนการอ้างอิงจริงนอกขอบเขตเดิมได้ ความหมายดังกล่าวแสดงให้เห็นโดยตัวอย่างถัดไปของ PassByValueObjectCase2

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

ผลลัพธ์

output : output
student : Student [id=10, name=Anand]

ตัวอย่างที่ 4:

/ **

นอกเหนือจากสิ่งที่กล่าวถึงใน Example3 (PassByValueObjectCase1.java) เราไม่สามารถเปลี่ยนข้อมูลอ้างอิงจริงนอกขอบเขตเดิมได้ "

หมายเหตุ: ฉันไม่ได้วางรหัสสำหรับprivate class Student. นิยามคลาสสำหรับStudentเหมือนกับ Example3

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

ผลลัพธ์

output : output
student : Student [id=10, name=Nikhil]
51 JaredOberhaus Mar 08 2009 at 13:28

คุณไม่สามารถส่งผ่านการอ้างอิงใน 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

49 SotiriosDelimanolis Jul 03 2014 at 13:48

ฉันคิดว่าฉันจะให้คำตอบนี้เพื่อเพิ่มรายละเอียดเพิ่มเติมจากข้อมูลจำเพาะ

อันดับแรกอะไรคือความแตกต่างระหว่างการส่งผ่านข้อมูลอ้างอิงกับการส่งผ่านค่า?

การส่งผ่านการอ้างอิงหมายถึงพารามิเตอร์ของฟังก์ชันที่เรียกว่าจะเหมือนกับอาร์กิวเมนต์ที่ส่งผ่านของผู้โทร (ไม่ใช่ค่า แต่เป็นตัวระบุ - ตัวแปรเอง)

การส่งผ่านค่าหมายถึงพารามิเตอร์ของฟังก์ชันที่เรียกว่าจะเป็นสำเนาของอาร์กิวเมนต์ที่ส่งผ่านของผู้โทร

หรือจากวิกิพีเดียเรื่อง pass-by-reference

ในการประเมินการเรียกโดยการอ้างอิง (เรียกอีกอย่างว่า pass-by-reference) ฟังก์ชันจะได้รับการอ้างอิงโดยปริยายไปยังตัวแปรที่ใช้เป็นอาร์กิวเมนต์แทนที่จะเป็นสำเนาของค่า โดยทั่วไปหมายความว่าฟังก์ชันสามารถแก้ไข (เช่นกำหนดให้) ตัวแปรที่ใช้เป็นอาร์กิวเมนต์ - สิ่งที่ผู้เรียกจะเห็น

และในเรื่องของ pass-by-value

ในการเรียกตามค่านิพจน์อาร์กิวเมนต์จะได้รับการประเมินและค่าผลลัพธ์จะถูกผูกไว้กับตัวแปรที่เกี่ยวข้องในฟังก์ชัน [... ] หากฟังก์ชันหรือโพรซีเดอร์สามารถกำหนดค่าให้กับพารามิเตอร์ได้จะมีการกำหนดเฉพาะสำเนาโลคัลเท่านั้น [... ]

ประการที่สองเราต้องรู้ว่า Java ใช้อะไรในการเรียกใช้เมธอด Java Language ข้อกำหนดรัฐ

เมื่อเรียกใช้เมธอดหรือคอนสตรัคเตอร์ (§15.12) ค่าของนิพจน์อาร์กิวเมนต์ที่แท้จริงจะเริ่มต้นตัวแปรพารามิเตอร์ที่สร้างขึ้นใหม่แต่ละประเภทที่ประกาศก่อนที่จะดำเนินการเนื้อหาของเมธอดหรือตัวสร้าง

ดังนั้นจึงกำหนด (หรือผูก) ค่าของอาร์กิวเมนต์ให้กับตัวแปรพารามิเตอร์ที่เกี่ยวข้อง

ค่าของอาร์กิวเมนต์คืออะไร?

ลองพิจารณาประเภทอ้างอิงJava เครื่องเสมือนจำเพาะรัฐ

ประเภทอ้างอิงมีสามประเภทได้แก่ ประเภทคลาสประเภทอาร์เรย์และประเภทอินเทอร์เฟซ ค่าของพวกเขาคือการอ้างอิงถึงอินสแตนซ์คลาสอาร์เรย์หรืออินสแตนซ์คลาสหรืออาร์เรย์ที่สร้างขึ้นแบบไดนามิกที่ใช้อินเทอร์เฟซตามลำดับ

Java Language ข้อกำหนดยังระบุ

ค่าอ้างอิง (มักเป็นเพียงการอ้างอิง) เป็นตัวชี้ไปยังวัตถุเหล่านี้และการอ้างอิงว่างพิเศษซึ่งอ้างถึงไม่มีวัตถุ

ค่าของอาร์กิวเมนต์ (ของการอ้างอิงบางประเภท) เป็นตัวชี้ไปยังวัตถุ โปรดสังเกตว่าตัวแปรการเรียกใช้เมธอดที่มีชนิดการอ้างอิงการส่งคืนและนิพจน์การสร้างอินสแตนซ์ ( new ...) ทั้งหมดจะแก้ไขเป็นค่าชนิดการอ้างอิง

ดังนั้น

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

ทั้งหมดผูกค่าของการอ้างอิงถึงเป็นตัวอย่างให้กับพารามิเตอร์ที่สร้างขึ้นใหม่ของวิธีการString paramนี่คือสิ่งที่อธิบายคำจำกัดความของ pass-by-value เช่นJava เป็นผ่านโดยค่า

ความจริงที่ว่าคุณสามารถทำตามการอ้างอิงเพื่อเรียกใช้เมธอดหรือเข้าถึงฟิลด์ของวัตถุที่อ้างอิงนั้นไม่เกี่ยวข้องกับการสนทนาโดยสิ้นเชิง คำจำกัดความของ pass-by-reference คือ

โดยทั่วไปหมายความว่าฟังก์ชันสามารถแก้ไข (เช่นกำหนดให้) ตัวแปรที่ใช้เป็นอาร์กิวเมนต์ - สิ่งที่ผู้เรียกจะเห็น

ใน Java การแก้ไขตัวแปรหมายถึงการกำหนดตัวแปรใหม่ ใน Java หากคุณกำหนดตัวแปรใหม่ภายในเมธอดมันจะไม่มีใครสังเกตเห็นผู้เรียกใช้ การแก้ไขวัตถุที่อ้างอิงโดยตัวแปรเป็นแนวคิดที่แตกต่างกันโดยสิ้นเชิง


ค่าดั้งเดิมมีการกำหนดไว้ยังอยู่ใน Java เครื่องเสมือนข้อมูลจำเพาะที่นี่ ค่าของประเภทคือค่าอินทิกรัลหรือทศนิยมที่สอดคล้องกันซึ่งเข้ารหัสอย่างเหมาะสม (บิต 8, 16, 32, 64 ฯลฯ )

45 user1767316 Jul 24 2016 at 15:29

ใน Java จะส่งผ่านการอ้างอิงเท่านั้นและถูกส่งผ่านด้วยค่า:

อาร์กิวเมนต์ Java ถูกส่งผ่านด้วยค่าทั้งหมด (การอ้างอิงจะถูกคัดลอกเมื่อใช้โดยวิธีการ):

ในกรณีของชนิดดั้งเดิมพฤติกรรมของ Java นั้นเรียบง่าย: ค่าจะถูกคัดลอกในอินสแตนซ์อื่นของชนิดดั้งเดิม

ในกรณีของ Object ตัวแปรนี้จะเหมือนกัน: ตัวแปร Object คือพอยน์เตอร์ (ที่เก็บข้อมูล) ซึ่งถือเฉพาะแอดเดรสของ Object ที่สร้างขึ้นโดยใช้คีย์เวิร์ด "new" และจะถูกคัดลอกเหมือนประเภทดั้งเดิม

ลักษณะการทำงานอาจแตกต่างจากประเภทดั้งเดิม: เนื่องจากตัวแปรอ็อบเจ็กต์ที่คัดลอกมีแอดเดรสเดียวกัน (ไปยังอ็อบเจ็กต์เดียวกัน) เนื้อหา / สมาชิกของออบเจ็กต์อาจยังคงได้รับการแก้ไขภายในวิธีการและการเข้าถึงภายนอกในภายหลังทำให้เกิดภาพลวงตาว่า (มี) อ็อบเจ็กต์นั้นถูกส่งผ่านโดยการอ้างอิง

วัตถุ "สตริง" ดูเหมือนจะเป็นตัวอย่างที่ดีสำหรับตำนานเมืองที่บอกว่า "วัตถุถูกส่งต่อโดยการอ้างอิง":

โดยใช้วิธีการคุณจะไม่สามารถอัปเดตค่าของสตริงที่ส่งผ่านเป็นอาร์กิวเมนต์ได้:

String Object เก็บอักขระโดยอาร์เรย์ที่ประกาศขั้นสุดท้ายซึ่งไม่สามารถแก้ไขได้ เฉพาะที่อยู่ของวัตถุเท่านั้นที่อาจถูกแทนที่โดยใช้ "ใหม่" การใช้ "new" เพื่ออัปเดตตัวแปรจะไม่อนุญาตให้เข้าถึง Object จากภายนอกเนื่องจากตัวแปรถูกส่งผ่านค่าและคัดลอกในตอนแรก

43 shsteimer Sep 04 2008 at 02:42

ความแตกต่างหรืออาจเป็นเพียงวิธีที่ฉันจำได้ในขณะที่ฉันเคยอยู่ภายใต้ความประทับใจเดียวกันกับโปสเตอร์ต้นฉบับคือ Java มักจะส่งผ่านค่า อ็อบเจ็กต์ทั้งหมด (ใน Java, ทุกอย่างยกเว้นสำหรับ primitives) ใน Java เป็นการอ้างอิง การอ้างอิงเหล่านี้จะส่งผ่านค่า

41 pek Sep 04 2008 at 03:01

ดังที่หลาย ๆ คนเคยกล่าวไว้ก่อนหน้านี้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 จะเปลี่ยนเฉพาะในขอบเขตเฉพาะที่และไม่อยู่นอกวิธีนี้

35 Michael Apr 04 2018 at 06:48

แตกต่างจากภาษาอื่น ๆ Java ไม่อนุญาตให้คุณเลือกระหว่าง pass-by-value และ pass-by-reference - อาร์กิวเมนต์ทั้งหมดจะส่งผ่านค่า การเรียกเมธอดสามารถส่งผ่านค่าสองประเภทไปยังเมธอด - สำเนาของค่าดั้งเดิม (เช่นค่าของ int และ double) และสำเนาการอ้างอิงไปยังอ็อบเจ็กต์

เมื่อเมธอดแก้ไขพารามิเตอร์ชนิดดั้งเดิมการเปลี่ยนแปลงพารามิเตอร์จะไม่มีผลกับค่าอาร์กิวเมนต์ดั้งเดิมในวิธีการเรียก

เมื่อพูดถึงวัตถุวัตถุเองจะไม่สามารถส่งผ่านไปยังวิธีการได้ ดังนั้นเราจึงส่งข้อมูลอ้างอิง (ที่อยู่) ของวัตถุ เราสามารถปรับแต่งวัตถุดั้งเดิมโดยใช้การอ้างอิงนี้

Java สร้างและจัดเก็บวัตถุอย่างไร:เมื่อเราสร้างวัตถุเราจะจัดเก็บที่อยู่ของวัตถุไว้ในตัวแปรอ้างอิง ลองวิเคราะห์ข้อความต่อไปนี้

Account account1 = new Account();

“ บัญชี account1” คือประเภทและชื่อของตัวแปรอ้างอิง“ =” คือตัวดำเนินการกำหนด“ ใหม่” จะขอจำนวนพื้นที่ที่ต้องการจากระบบ ตัวสร้างทางด้านขวาของคีย์เวิร์ดใหม่ซึ่งสร้างอ็อบเจกต์เรียกโดยปริยายโดยคีย์เวิร์ดใหม่ ที่อยู่ของออบเจ็กต์ที่สร้างขึ้น (ผลลัพธ์ของค่าที่ถูกต้องซึ่งเป็นนิพจน์ที่เรียกว่า "นิพจน์การสร้างอินสแตนซ์คลาส") ถูกกำหนดให้กับค่าทางซ้าย (ซึ่งเป็นตัวแปรอ้างอิงที่มีชื่อและประเภทที่ระบุ) โดยใช้ตัวดำเนินการกำหนด

แม้ว่าการอ้างอิงของอ็อบเจ็กต์จะถูกส่งผ่านด้วยค่า แต่เมธอดยังคงสามารถโต้ตอบกับอ็อบเจ็กต์ที่อ้างอิงได้โดยเรียกใช้เมธอดสาธารณะโดยใช้สำเนาการอ้างอิงของอ็อบเจ็กต์ เนื่องจากการอ้างอิงที่เก็บไว้ในพารามิเตอร์คือสำเนาของการอ้างอิงที่ส่งผ่านเป็นอาร์กิวเมนต์พารามิเตอร์ในเมธอดที่เรียกและอาร์กิวเมนต์ในวิธีการเรียกจะอ้างถึงวัตถุเดียวกันในหน่วยความจำ

การส่งผ่านการอ้างอิงไปยังอาร์เรย์แทนที่จะเป็นอ็อบเจ็กต์อาร์เรย์ด้วยตัวเองนั้นเหมาะสมสำหรับเหตุผลด้านประสิทธิภาพ เนื่องจากทุกอย่างใน Java ถูกส่งผ่านด้วยค่าหากวัตถุอาร์เรย์ถูกส่งผ่านสำเนาของแต่ละองค์ประกอบจะถูกส่งผ่าน สำหรับอาร์เรย์ขนาดใหญ่สิ่งนี้จะเสียเวลาและใช้พื้นที่จัดเก็บข้อมูลจำนวนมากสำหรับสำเนาขององค์ประกอบ

ในภาพด้านล่างคุณจะเห็นว่าเรามีตัวแปรอ้างอิงสองตัว (สิ่งเหล่านี้เรียกว่าพอยน์เตอร์ใน C / C ++ และฉันคิดว่าคำนั้นทำให้เข้าใจคุณสมบัตินี้ได้ง่ายขึ้น) ในวิธีการหลัก ตัวแปรดั้งเดิมและตัวแปรอ้างอิงจะถูกเก็บไว้ในหน่วยความจำสแต็ก (ด้านซ้ายในภาพด้านล่าง) ตัวแปรอ้างอิง array1 และ array2 "point" (ตามที่โปรแกรมเมอร์ C / C ++ เรียกมัน) หรืออ้างอิงไปยังอาร์เรย์ a และ b ตามลำดับซึ่งเป็นวัตถุ (ค่าตัวแปรอ้างอิงเหล่านี้ถือเป็นที่อยู่ของวัตถุ) ในหน่วยความจำฮีป (ด้านขวาในภาพด้านล่าง) .

ถ้าเราส่งค่าของตัวแปรอ้างอิง array1 เป็นอาร์กิวเมนต์ไปยังเมธอด reverseArray ตัวแปรอ้างอิงจะถูกสร้างขึ้นในเมธอดและตัวแปรอ้างอิงนั้นจะเริ่มชี้ไปที่อาร์เรย์เดียวกัน (a)

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

ดังนั้นถ้าเราพูด

array1[0] = 5;

ในเมธอด reverseArray มันจะทำการเปลี่ยนแปลงในอาร์เรย์ a.

เรามีตัวแปรอ้างอิงอีกตัวในเมธอด reverseArray (array2) ที่ชี้ไปยังอาร์เรย์ c ถ้าเราจะพูด

array1 = array2;

ในเมธอด reverseArray จากนั้นตัวแปรอ้างอิง array1 ในเมธอด reverseArray จะหยุดชี้ไปที่อาร์เรย์ a และเริ่มชี้ไปที่อาร์เรย์ c (เส้นประในภาพที่สอง)

ถ้าเราคืนค่าของตัวแปรอ้างอิง array2 เป็นค่าส่งกลับของเมธอด reverseArray และกำหนดค่านี้ให้กับตัวแปรอ้างอิง array1 ในเมธอดหลัก array1 ใน main จะเริ่มชี้ไปที่อาร์เรย์ c

ลองเขียนทุกสิ่งที่เราทำไปพร้อม ๆ กัน

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

และตอนนี้เมธอด reverseArray สิ้นสุดลงแล้วตัวแปรอ้างอิง (array1 และ array2) จะหายไป ซึ่งหมายความว่าตอนนี้เรามีเพียงสองตัวแปรอ้างอิงในเมธอดอาเรย์ 1 และอาร์เรย์ 2 ซึ่งชี้ไปที่อาร์เรย์ c และ b ตามลำดับ ไม่มีตัวแปรอ้างอิงที่ชี้ไปที่วัตถุ (อาร์เรย์) ก. ดังนั้นจึงมีสิทธิ์ได้รับการเก็บขยะ

คุณยังสามารถกำหนดค่าของ array2 ใน main ให้กับ array1 array1 จะเริ่มชี้ไปที่ b

33 SWD Sep 08 2008 at 21:48

ฉันมักจะคิดว่า "ส่งต่อสำเนา" เป็นสำเนาของค่าไม่ว่าจะเป็นแบบดั้งเดิมหรือการอ้างอิง ถ้ามันเป็นแบบดั้งเดิมมันคือสำเนาของบิตที่เป็นค่าและถ้ามันเป็นวัตถุมันก็คือสำเนาของการอ้างอิง

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

ผลลัพธ์ของ java PassByCopy:

ชื่อ = Maxx
ชื่อ = Fido

คลาสของ wrapper ดั้งเดิมและสตริงไม่เปลี่ยนรูปดังนั้นตัวอย่างใด ๆ ที่ใช้ประเภทเหล่านั้นจะไม่ทำงานเหมือนกับประเภท / อ็อบเจ็กต์อื่น ๆ

33 Gaurav Jul 10 2013 at 13:31

Java มีค่าส่งผ่านเท่านั้น ตัวอย่างง่ายๆในการตรวจสอบสิ่งนี้

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}
29 sven Sep 08 2008 at 21:54

ผมได้สร้างด้ายที่ทุ่มเทให้กับชนิดของคำถามเหล่านี้สำหรับการใด ๆการเขียนโปรแกรมภาษาที่นี่

Java นอกจากนี้ยังกล่าวถึง นี่คือสรุปสั้น ๆ :

  • Java ส่งผ่านพารามิเตอร์ตามค่า
  • "ตามค่า" เป็นวิธีเดียวใน java ในการส่งผ่านพารามิเตอร์ไปยังเมธอด
  • การใช้วิธีการจากอ็อบเจ็กต์ที่กำหนดให้เป็นพารามิเตอร์จะเปลี่ยนอ็อบเจ็กต์เนื่องจากการอ้างอิงชี้ไปที่อ็อบเจ็กต์ดั้งเดิม (หากวิธีนั้นเปลี่ยนแปลงค่าบางอย่าง)
28 RustyShackleford Dec 27 2009 at 03:19

การแก้ไขบางโพสต์

C ไม่สนับสนุนการส่งผ่านโดยการอ้างอิง มันมักจะผ่านไปด้วยคุณค่า C ++ รองรับ pass by reference แต่ไม่ใช่ค่าเริ่มต้นและค่อนข้างอันตราย

ไม่สำคัญว่าค่าใน Java จะเป็นอย่างไร: ดั้งเดิมหรือแอดเดรส (โดยประมาณ) ของอ็อบเจ็กต์มันจะถูกส่งผ่านด้วยค่าเสมอ

หากออบเจ็กต์ Java "ทำงาน" เหมือนกำลังถูกส่งผ่านโดยการอ้างอิงนั่นเป็นคุณสมบัติของความไม่แน่นอนและไม่มีส่วนเกี่ยวข้องกับกลไกการส่งผ่าน

ฉันไม่แน่ใจว่าทำไมมันจึงสับสนมากอาจเป็นเพราะ "โปรแกรมเมอร์" Java จำนวนมากไม่ได้รับการฝึกฝนอย่างเป็นทางการจึงไม่เข้าใจว่าเกิดอะไรขึ้นในหน่วยความจำจริงๆ?