Java ผ่านการอ้างอิงหรือส่งผ่านค่า?

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

รับซอร์สโค้ด

รับรหัสสำหรับ Java Challenger นี้ คุณสามารถเรียกใช้การทดสอบของคุณเองในขณะที่ทำตามตัวอย่าง

การอ้างอิงอ็อบเจ็กต์จะถูกส่งผ่านค่า

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

 public class ObjectReferenceExample { public static void main(String... doYourBest) { Simpson simpson = new Simpson(); transformIntoHomer(simpson); System.out.println(simpson.name); } static void transformIntoHomer(Simpson simpson) { simpson.name = "Homer"; } } class Simpson { String name; } 

คุณคิดว่าsimpson.nameจะเป็นอย่างไรหลังจากใช้transformIntoHomerวิธีนี้

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

หากคุณยังไม่ชัดเจนว่ามันทำงานอย่างไรลองดูรูปด้านล่าง

ราฟาเอลชิเนลาโตเดลเนโร

ประเภทดั้งเดิมถูกส่งผ่านโดยมูลค่าหรือไม่?

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

 public class PrimitiveByValueExample { public static void main(String... primitiveByValue) { int homerAge = 30; changeHomerAge(homerAge); System.out.println(homerAge); } static void changeHomerAge(int homerAge) { homerAge = 35; } } 

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

การส่งผ่านการอ้างอิงวัตถุที่ไม่เปลี่ยนรูป

จะเกิดอะไรขึ้นถ้าเราทำการทดสอบเดียวกันกับStringวัตถุที่ไม่เปลี่ยนรูป?

JDK มีคลาสที่ไม่เปลี่ยนรูปมากมาย ตัวอย่าง ได้แก่ ประเภทกระดาษห่อInteger, Double, Float, Long, Boolean, BigDecimalและแน่นอนที่รู้จักกันเป็นอย่างดีStringระดับ

ในตัวอย่างถัดไปสังเกตว่าจะเกิดอะไรขึ้นเมื่อเราเปลี่ยนค่าของไฟล์String.

 public class StringValueChange { public static void main(String... doYourBest) { String name = ""; changeToHomer(name); System.out.println(name); } static void changeToHomer(String name) { name = "Homer"; } } 

คุณคิดว่าผลลัพธ์จะเป็นอย่างไร? ถ้าคุณเดาได้“” ก็ขอแสดงความยินดีด้วย! สิ่งนี้เกิดขึ้นเนื่องจากStringวัตถุไม่เปลี่ยนรูปซึ่งหมายความว่าฟิลด์ภายในStringเป็นขั้นสุดท้ายและไม่สามารถเปลี่ยนแปลงได้

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

สตริงและอื่น ๆ

เรียนรู้เพิ่มเติมเกี่ยวกับStringคลาสของ Java และอื่น ๆ : ดูโพสต์ทั้งหมดของ Rafael ในซีรีส์ Java Challengers

การส่งผ่านการอ้างอิงอ็อบเจ็กต์ที่ไม่แน่นอน

แตกต่างจากStringวัตถุส่วนใหญ่ใน JDK นั้นเปลี่ยนแปลงได้เช่นเดียวกับStringBuilderคลาส ตัวอย่างด้านล่างคล้ายกับตัวอย่างก่อนหน้า แต่มีคุณสมบัติStringBuilderมากกว่าString:

 static class MutableObjectReference { public static void main(String... mutableObjectExample) { StringBuilder name = new StringBuilder("Homer "); addSureName(name); System.out.println(name); } static void addSureName(StringBuilder name) { name.append("Simpson"); } } 

คุณสามารถอนุมานผลลัพธ์สำหรับตัวอย่างนี้ได้หรือไม่? ในกรณีนี้เนื่องจากเรากำลังทำงานกับวัตถุที่เปลี่ยนแปลงได้ผลลัพธ์จะเป็น "Homer Simpson" คุณสามารถคาดหวังพฤติกรรมเดียวกันจากอ็อบเจ็กต์ที่เปลี่ยนแปลงได้อื่น ๆ ใน Java

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

รับความท้าทายในการอ้างอิงวัตถุ!

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

 public class DragonWarriorReferenceChallenger { public static void main(String... doYourBest) { StringBuilder warriorProfession = new StringBuilder("Dragon "); String warriorWeapon = "Sword "; changeWarriorClass(warriorProfession, warriorWeapon); System.out.println("Warrior=" + warriorProfession + " Weapon=" + warriorWeapon); } static void changeWarriorClass(StringBuilder warriorProfession, String weapon) { warriorProfession.append("Knight"); weapon = "Dragon " + weapon; weapon = null; warriorProfession = null; } } 

นี่คือตัวเลือกตรวจสอบส่วนท้ายของบทความนี้เพื่อดูคีย์คำตอบ

A : Warrior = null Weapon = null

B : นักรบ = อาวุธมังกร = มังกร

C : นักรบ = อาวุธอัศวินมังกร = ดาบมังกร

D : นักรบ = อาวุธอัศวินมังกร = ดาบ

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

พารามิเตอร์แรกในตัวอย่างข้างต้นคือwarriorProfessionตัวแปรซึ่งเป็นวัตถุที่เปลี่ยนแปลงไม่ได้ พารามิเตอร์ที่สองคืออาวุธไม่เปลี่ยนรูปString:

 static void changeWarriorClass(StringBuilder warriorProfession, String weapon) { ... } 

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

 warriorProfession.append("Knight"); 

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

 weapon = "Dragon " + weapon; 

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

 weapon = null; warriorProfession = null; 

จากทั้งหมดนี้เราสามารถสรุปได้ว่าค่าสุดท้ายจากการเปลี่ยนแปลงStringBuilderและไม่เปลี่ยนรูปของเราStringจะเป็น:

 System.out.println("Warrior=" + warriorProfession + " Weapon=" + warriorWeapon); 

ค่าเดียวที่เปลี่ยนแปลงในchangeWarriorClassเมธอดคือwarriorProfessionเนื่องจากเป็นStringBuilderวัตถุที่ไม่แน่นอน โปรดทราบwarriorWeaponว่าไม่ได้เปลี่ยนแปลงเพราะเป็นStringวัตถุที่ไม่เปลี่ยนรูป

ผลลัพธ์ที่ถูกต้องจากรหัสชาเลนเจอร์ของเราคือ:

D : นักรบ = อาวุธอัศวินมังกร = ดาบ

วิดีโอท้า! การดีบักการอ้างอิงอ็อบเจ็กต์ใน Java

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

ข้อผิดพลาดทั่วไปเกี่ยวกับการอ้างอิงวัตถุ

  • พยายามเปลี่ยนค่าที่ไม่เปลี่ยนรูปโดยการอ้างอิง
  • พยายามเปลี่ยนตัวแปรดั้งเดิมโดยการอ้างอิง
  • Expecting the real object won't change when you change a mutable object parameter in a method.

What to remember about object references

  • Java always passes parameter variables by value.
  • Object variables in Java always point to the real object in the memory heap.
  • A mutable object’s value can be changed when it is passed to a method.
  • An immutable object’s value cannot be changed, even if it is passed a new value.
  • “Passing by value” refers to passing a copy of the value.
  • “Passing by reference” refers to passing the real reference of the variable in memory.

Learn more about Java

  • Get more quick code tips: Read all of Rafael's posts in the JavaWorld Java Challengers series.
  • Learn more about mutable and immutable Java objects (such as String and StringBuffer) and how to use them in your code.
  • คุณอาจแปลกใจที่ทราบว่าประเภทดั้งเดิมของ Java นั้นขัดแย้งกัน ในคุณสมบัตินี้จอห์นไอ. มัวร์ทำกรณีนี้เพื่อรักษาพวกเขาและเรียนรู้ที่จะใช้มันให้ดี
  • สร้างทักษะการเขียนโปรแกรม Java ของคุณต่อไปใน Java Dev Gym
  • หากคุณชอบการดีบักการสืบทอด Java โปรดดูวิดีโอเพิ่มเติมในรายการเล่นวิดีโอ Java Challenges ของ Rafael (วิดีโอในชุดนี้ไม่มีส่วนเกี่ยวข้องกับ JavaWorld)
  • ต้องการทำงานในโครงการที่ปราศจากความเครียดและเขียนโค้ดที่ปราศจากข้อบกพร่องหรือไม่? ตรงไป NoBugsProject สำหรับสำเนาของไม่มี Bugs, No Stress - สร้างชีวิตเปลี่ยนซอฟแวร์โดยไม่ทำลายชีวิตของคุณ

เรื่องนี้ "Java ผ่านการอ้างอิงหรือส่งผ่านค่า" เผยแพร่ครั้งแรกโดย JavaWorld