กำลังส่ง 'this' ในวิธีการเรียกการปฏิบัติที่ยอมรับใน java


94

วิธีปฏิบัติที่ดี / ไม่ดี / ยอมรับได้หรือไม่ในการส่งผ่านวัตถุปัจจุบันในการเรียกใช้เมธอด ใน:

public class Bar{
    public Bar(){}

    public void foo(Baz baz){
        //  modify some values of baz
    }
}

public class Baz{
    //constructor omitted

    public void method(){
        Bar bar = new Bar();
        bar.foo(this);
    }
}

โดยเฉพาะสายนี้เป็นที่bar.foo(this)ยอมรับหรือไม่?


60
ทำไมถึงไม่ยอมรับ? เป็นเรื่องธรรมดา
Denys Séguret

3
นั่นคือ 8 ใช่แล้ว :) (และใช่ฉันกำลังอัปเดตอยู่)
Alex Gittemeier

2
คลาสที่ไม่ระบุตัวตนที่ไม่คงที่จะส่งผ่านการอ้างอิงของซูเปอร์คลาสนี้โดยอัตโนมัติดังนั้นจึงเป็นที่ยอมรับ ข้อควรระวังประการเดียวคือต้องระวังเกี่ยวกับการอ้างอิงแบบวงกลม
Mehul Rathod

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

3
@dystroy แม้ว่าฉันจะยอมรับว่ามันเป็นที่ยอมรับ แต่ก็หมายความว่ามันเป็นเรื่องที่ยอมรับได้เพราะมันเป็นเรื่องธรรมดาเป็นตรรกะที่ไม่ดีจริงๆ การทำอะไรบางอย่างเพราะเรื่องธรรมดาอาจทำให้คุณมีปัญหามากมาย
Carrie Kendall

คำตอบ:


156

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

ดังนั้นใช้มัน

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

1084        // Argument is a String
1085        if (cs.equals(this))
1086            return true;

มองหาโครงการ(thisใหญ่ ๆ ที่ "ได้รับการยอมรับ" คุณจะไม่พบมัน


6
คำตอบที่สมบูรณ์แบบ
maxf130

15
-1 เนื่องจากไม่มีการกล่าวถึงข้อเท็จจริงที่ว่าความสัมพันธ์แบบสองทิศทางมีความซับซ้อนมากกว่าความสัมพันธ์แบบทิศทางเดียว เป็นสิ่งสำคัญเพื่อให้แน่ใจว่าซอฟต์แวร์มีความชัดเจนที่สุด ในตัวอย่างเฉพาะด้านบนการย้าย Method foo ไปยังคลาส Baz จะเหมาะสมกว่าเพื่อหลีกเลี่ยงการอ้างอิงแบบสองทิศทางระหว่างสองคลาสและเพื่อนำพฤติกรรมและข้อมูลมารวมกัน
.

36
-1 สำหรับคำตอบเนื่องจากคุณพบรายละเอียดเล็กน้อยเพิ่มเติมในคำถามที่คุณสามารถแสดงความคิดเห็นได้? เหรอ?
Denys Séguret

13
@dystroy ใช่ฉันไม่เห็นด้วยกับวลีเปิดของคุณ: "ไม่มีเหตุผลที่จะไม่ใช้"
.

4
ในทางปฏิบัติในโค้ดจริง (ตรงกันข้ามกับตัวอย่างที่เรียบง่ายของ OP) การส่งผ่านthisไม่ได้หมายความว่าคุณเพิ่มลิงก์แบบสองทิศทางเนื่องจากการสืบทอดและอินเทอร์เฟซเป็นต้น
Denys Séguret

165

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

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


18
+1: เป็นการดีที่จะชี้ให้เห็นถึงอันตรายในการอ้างถึง 'สิ่งนี้' ในตัวสร้าง
Bathsheba

ไม่จำเป็นต้องเป็นการปฏิบัติที่ไม่ดีเสมอไป ตัวอย่างเช่นตัวCarสร้างอาจสร้างWheelอินสแตนซ์Carโดยที่ไม่มีWheelจะเริ่มต้นได้ไม่สมบูรณ์ในขณะที่ตัวสร้างที่Wheelไม่มีสิ่งที่เกี่ยวข้องCarจะเริ่มต้นได้ไม่สมบูรณ์เช่นกัน ในกรณีนี้อาจเป็นที่ยอมรับได้ในตัวสร้างของรถเพื่อส่งthisต่อไปยังตัวสร้างของล้อ อีกทางเลือกหนึ่งคือการทำให้ทั้งรถยนต์และล้อมีตัวสร้างส่วนตัวและใช้ฟังก์ชันโรงงานที่สร้างรถยนต์ล้อและติดตั้งล้อบนรถ แต่นั่นควรเป็นวิธีการคงที่ในรถยนต์หรือวิธีการคงที่บนล้อ?
Lie Ryan

แน่นอนคุณควรสร้างCarFactoryWheelInstallerProxyที่ติดตั้งล้อให้คุณ
Kevin

6
@LieRyan Wheelเป็นผู้อยู่ใต้บังคับบัญชาอย่างสมบูรณ์Carและ IMO ไม่ควรรู้Carเลย
Izkata

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

42

ใช่แต่คุณควรระวังสองสิ่ง

  1. ผ่านสิ่งนี้เมื่อยังไม่ได้สร้างวัตถุ (เช่นในตัวสร้าง)
  2. ส่งผ่านสิ่งนี้ไปยังวัตถุที่มีอายุยืนยาวซึ่งจะทำให้การอ้างอิงมีชีวิตอยู่และจะป้องกันไม่ให้วัตถุนี้ถูกรวบรวม

1
โปรดทราบว่าตัวสร้างใน Java ไม่ใช่ตัวสร้างจริงๆมันน่าจะเหมาะสมกว่าที่จะเรียกตัวสร้างของ Java ว่า "initializer" ภายในคอนสตรัคเตอร์ Java อ็อบเจ็กต์ได้รับการจัดสรรหน่วยความจำจริงซึ่งอ็อบเจ็กต์นี้มีอยู่แล้ว / สร้างขึ้นภายในคอนสตรัคเตอร์
Lie Ryan

1
ไม่จริงออบเจ็กต์อาจมีตัวแปรอินสแตนซ์บางตัวที่ยังไม่ได้เริ่มต้นเพื่อให้อ็อบเจ็กต์ยังทำงานไม่เต็มที่ ดังนั้นในตัวสร้างคุณสามารถส่งต่อสิ่งนี้ไปยังอ็อบเจ็กต์ที่สองซึ่งสามารถเรียกใช้เมธอดไปยังอ็อบเจ็กต์ที่ยังไม่ได้เริ่มต้นตัวแปรอินสแตนซ์ทั้งหมด
Stefanos T.

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


5

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


2
ในโค้ดตัวอย่าง Baz.method () เป็นวิธีอินสแตนซ์ที่เรียก Bar.foo () โดยมีอินสแตนซ์ของ Baz เป็นพารามิเตอร์ ดังนั้น OP จึงไม่เรียกเมธอดในคลาสเดียวกัน
ผู้ใช้

@ MichaelKjörlingฉันคิดว่า Juned กำลังพูดอย่างนั้นโดยการย้าย foo () วิธีการไปยังคลาส Baz จะไม่จำเป็นต้องผ่านthisระหว่างสองคลาส ดังนั้นไม่จำเป็นต้องเพิ่มความซับซ้อนเพิ่มเติม
.

4

เป็นแนวทางปฏิบัติที่ไม่ดีในการส่งผ่านวัตถุปัจจุบันในการเรียกใช้เมธอดหากมีทางเลือกที่ซับซ้อนน้อยกว่าเพื่อให้บรรลุพฤติกรรมเดียวกัน

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

หากต้องการอ้างอิง Refactoring โดย Martin Fowler:

เปลี่ยน Bidirectional Association เป็น Unidirectional (200)

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

...

คุณควรใช้การเชื่อมโยงแบบสองทิศทางเมื่อคุณต้องการ แต่ไม่ใช่เมื่อคุณไม่ทำ ทันทีที่คุณเห็นการเชื่อมโยงแบบสองทิศทางไม่ได้ดึงน้ำหนักอีกต่อไปให้ทิ้งส่วนท้ายที่ไม่จำเป็น

ดังนั้นในทางทฤษฎีเราควรจะได้ยินเสียงระฆังปลุกเมื่อเราพบว่าเราจำเป็นต้องผ่านไปthisและพยายามอย่างมากที่จะคิดหาวิธีอื่นในการแก้ปัญหาในมือ แน่นอนว่ามีบางครั้งที่ในที่สุดก็มีเหตุผลที่จะทำเช่นนั้น

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

ในทางปฏิบัติฉันพบว่าโค้ดของฉันได้รับการปรับปรุงอย่างมากโดยหลีกเลี่ยงการเชื่อมโยงแบบสองทิศทางเช่นโรคระบาด


คุณสับสนกับตัวอย่างที่เรียบง่ายกับความจำเป็นในการสร้างลิงก์แบบสองทิศทาง การส่งค่านี้เป็นพารามิเตอร์ตามที่ควรจะชัดเจนด้วยตัวอย่างซอร์สโค้ด java.lang จำนวนมาก (เช่นที่คุณเห็นในคำตอบของฉัน) ไม่ได้หมายความว่าคุณเพิ่มการอ้างอิงแบบสองทิศทาง คำตอบนี้ควรเป็นความคิดเห็นในความคิดของฉัน
Denys Séguret

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

1
"โดยนิยามความสัมพันธ์แบบสองทิศทางที่ถูกสร้างขึ้นโดยเร็วเช่นนี้ถูกส่งผ่านไป" สิ่งนี้ทำให้ชัดเจนว่าคุณไม่เข้าใจตรงไหน ดูตัวอย่างที่ฉันให้ ไม่มีลิงก์แบบสองทิศทางเนื่องจากประเภทของอาร์กิวเมนต์ในequalsคือ Object นี่เป็นเรื่องธรรมดามาก: วิธีการรับกำหนดอาร์กิวเมนต์เป็นคลาสทั่วไปหรือเป็นอินเทอร์เฟซ สาเหตุหนึ่งที่ใช้รูปแบบดังกล่าวใน java คือเพื่อหลีกเลี่ยงการอ้างอิงที่ไม่ต้องการ ก่อนที่จะดำเนินการต่อไปฉันขอแนะนำให้คุณดูเหตุการณ์ที่เกิดขึ้นมากมายในการส่งผ่านthisเป็นอาร์กิวเมนต์ในไลบรารี java ที่น่านับถือ
Denys Séguret

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

2
@JW: การให้เหตุผลในคำตอบนี้ไม่เกี่ยวข้อง เป็นความคิดที่ดีเสมอที่จะทำ X ถ้ามีอย่างอื่นที่ง่ายกว่าสำหรับค่า X ใด ๆ
Lie Ryan

4

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


มีผลข้างเคียงมากมาย มันเพิ่มความซับซ้อน
.

หากมีผลข้างเคียงมากมายเราไม่พบคำแนะนำเดียวเช่นนั้นในซอร์สโค้ด java ของเราดูตัวอย่าง @destroys จากซอร์สโค้ด
Suresh Atta

2

เพียงเพื่อเพิ่มอีกหนึ่งตัวอย่างที่ผ่านthisมานั้นถูกต้องและต่อไปนี้การออกแบบที่ดี: รูปแบบของผู้เข้าชม ในรูปแบบการออกแบบผู้เยี่ยมชมaccept(Visitor v)มักใช้วิธีการในลักษณะที่เรียกv.visit(this)ว่า


1

ยอมรับได้

ตัวอย่างจากเอกสาร Oracle JAVA:

ภายในเมธอดอินสแตนซ์หรือคอนสตรัคเตอร์นี่คือการอ้างอิงถึงอ็อบเจ็กต์ปัจจุบัน - อ็อบเจ็กต์ที่มีการเรียกเมธอดหรือคอนสตรัคเตอร์ คุณสามารถอ้างถึงสมาชิกใด ๆ ของอ็อบเจ็กต์ปัจจุบันจากภายในวิธีการอินสแตนซ์หรือตัวสร้างโดยใช้สิ่งนี้

การใช้สิ่งนี้กับฟิลด์

สาเหตุที่พบบ่อยที่สุดในการใช้คีย์เวิร์ดนี้เนื่องจากฟิลด์ถูกเงาโดยพารามิเตอร์เมธอดหรือคอนสตรัคเตอร์


2
"คุณสามารถอ้างถึงสมาชิกใด ๆ ของออบเจ็กต์ปัจจุบัน " ซึ่งดูเหมือนจะไม่ตอบคำถาม "สามารถส่งผ่านthisเป็นพารามิเตอร์ได้หรือไม่ "
ผู้ใช้

2
นี่เป็นการบอกว่าคุณสามารถthis.some_variableอ้างถึงตัวแปรคลาสแทนตัวแปรโลคัลได้อย่างไร ไม่มีอะไรเกี่ยวข้องกับการส่งผ่านthisเป็นพารามิเตอร์
Jose Salvatierra

0

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


9
ลักษณะของหัวข้อนี้
Denys Séguret

4
"ทุกอย่างใน java จะถูกส่งผ่านไปตามค่า" - นั่นเป็นความคิดเห็นเริ่มต้นที่ทำให้เข้าใจผิดมาก จริงๆแล้ววัตถุทั้งหมดถูกส่งผ่านโดยการอ้างอิงและประเภทดั้งเดิมทั้งหมดจะถูกส่งผ่านด้วยค่า คุณไม่เคยมีการthisอ้างอิงถึงประเภทดั้งเดิมดังนั้นฉันคิดว่า "ข้อมูลเพิ่มเติม" ของคุณกำลังทำให้เกิดความสับสน
Stewart

1
@Stewart: เขาทำให้ชัดเจนทันทีว่าเขาไม่ได้หมายความว่าจะคัดลอกวัตถุทั้งหมด
LarsH

10
@Stewart no วัตถุจะไม่ถูกส่งผ่านโดยการอ้างอิง แต่การอ้างอิงวัตถุจะถูกส่งผ่านด้วยค่า มันเป็นความแตกต่างที่สำคัญ - การอ้างอิงโดยผ่านหมายความว่าถ้าฉันมีตัวแปรท้องถิ่นที่อ้างถึงวัตถุและส่งผ่านตัวแปรนั้นไปยังวิธีอื่นเมธอดจะสามารถเปลี่ยนวัตถุที่ตัวแปรของฉันอ้างถึงได้และนี่ไม่ใช่สิ่งที่แน่นอน คุณสามารถทำได้ใน Java วิธีนี้สามารถเปลี่ยนสถานะของวัตถุผ่านสำเนาการอ้างอิงของตัวเอง แต่ไม่สามารถเปลี่ยนสำเนาการอ้างอิงของฉันให้ชี้ไปที่อย่างอื่นได้
Ian Roberts

2
@Stewart yoda.arachsys.com/csharp/parameters.htmlเป็นบทความที่ดีที่อธิบายความแตกต่างระหว่างการส่งผ่านการอ้างอิงและการส่งต่อการอ้างอิงตามค่าในบริบทของ C # ซึ่งรองรับทั้งสองอย่าง
Ian Roberts
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.