การรวมไดนามิก Java และการแทนที่เมธอด


90

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

นี่คือปัญหาที่ฉันได้รับ:

/* What is the output of the following program? */

public class Test {

  public boolean equals( Test other ) {
    System.out.println( "Inside of Test.equals" );
    return false;
  }

  public static void main( String [] args ) {
    Object t1 = new Test();
    Object t2 = new Test();
    Test t3 = new Test();
    Object o1 = new Object();

    int count = 0;
    System.out.println( count++ );// prints 0
    t1.equals( t2 ) ;
    System.out.println( count++ );// prints 1
    t1.equals( t3 );
    System.out.println( count++ );// prints 2
    t3.equals( o1 );
    System.out.println( count++ );// prints 3
    t3.equals(t3);
    System.out.println( count++ );// prints 4
    t3.equals(t2);
  }
}

ฉันยืนยันว่าผลลัพธ์ควรเป็นคำสั่งพิมพ์สองชุดแยกกันจากภายในequals()วิธีการแทนที่: ที่t1.equals(t3)และt3.equals(t3). กรณีหลังมีความชัดเจนเพียงพอและในกรณีก่อนหน้านี้แม้ว่าจะt1มีการอ้างอิงประเภท Object แต่ก็มีการสร้างอินสแตนซ์เป็น type Test ดังนั้นการผูกแบบไดนามิกควรเรียกรูปแบบที่ถูกแทนที่ของวิธีการ

ชัดเจนว่าไม่. สัมภาษณ์ของฉันสนับสนุนให้ฉันไปเรียกใช้โปรแกรมตัวเองและแท้จริงและดูเถิดมีเพียงการส่งออกเพียงครั้งเดียวจากวิธีการแทนที่: t3.equals(t3)ที่บรรทัด

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


โปรดค้นหาโพสต์ของฉันสำหรับคำตอบนี้ซึ่งฉันได้พยายามอย่างเต็มที่เพื่ออธิบายพร้อมกรณีเพิ่มเติม ฉันจะขอบคุณข้อมูลของคุณจริงๆ :)
Devendra Lattu

คำตอบ:


82

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

การอภิปรายบางส่วนที่นี่

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

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

ฉันเชื่อว่าหากการเชื่อมโยงเป็นแบบไดนามิกจริง ๆ แล้วกรณีใด ๆ ที่ผู้เรียกและพารามิเตอร์เป็นอินสแตนซ์ของการทดสอบจะส่งผลให้มีการเรียกเมธอดที่ถูกแทนที่ ดังนั้น t3.equals (o1) จะเป็นกรณีเดียวที่ไม่พิมพ์


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

4
ความผิดพลาดของฉันขาดหายไปอย่างสิ้นเชิงกับข้อเท็จจริงที่ว่าวิธีนี้มีการใช้งานมากเกินไปแทนที่จะถูกแทนที่ ฉันเห็น "เท่ากับ ()" และคิดว่าได้รับมรดกและถูกแทนที่ในทันที ดูเหมือนว่าฉันจะได้แนวคิดที่กว้างขึ้นและยากขึ้นอีกครั้ง แต่กลับทำให้รายละเอียดง่าย ๆ : P
Magsol

14
อีกเหตุผลหนึ่งที่มีคำอธิบายประกอบ @Override
Matt

1
พูดซ้ำหลังจากฉัน: "Java ใช้การรวมแบบคงที่สำหรับเมธอดที่โอเวอร์โหลดและการเชื่อมโยงแบบไดนามิกสำหรับวิธีที่ถูกลบล้าง" - +1
Mr_and_Mrs_D

1
ฉันจึงเรียนจบโดยไม่รู้เรื่องนี้ ขอบคุณ!
Atieh

26

equalsวิธีการTestไม่ได้แทนที่วิธีการequals java.lang.Objectดูประเภทพารามิเตอร์! Testชั้นมากไปด้วยวิธีการที่ยอมรับได้equalsTest

หากequalsวิธีการนี้มีจุดมุ่งหมายเพื่อลบล้างควรใช้คำอธิบายประกอบ @Override ซึ่งจะทำให้เกิดข้อผิดพลาดในการคอมไพล์เพื่อชี้ให้เห็นข้อผิดพลาดทั่วไปนี้


ใช่ฉันไม่ค่อยแน่ใจว่าทำไมฉันถึงพลาดรายละเอียดที่เรียบง่าย แต่มีความสำคัญ แต่นั่นคือจุดที่ปัญหาของฉันคือ ขอขอบคุณ!
Magsol

+1 เพื่อเป็นคำตอบที่แท้จริงสำหรับผลลัพธ์ที่อยากรู้อยากเห็นของผู้ถาม
แมตต์ข

โปรดค้นหาโพสต์ของฉันสำหรับคำตอบนี้ซึ่งฉันได้พยายามอย่างเต็มที่เพื่ออธิบายพร้อมกรณีเพิ่มเติม ฉันจะขอบคุณข้อมูลของคุณจริงๆ :)
Devendra Lattu

7

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


1
น่าเสียดายที่ราคาที่ Groovy จ่ายไปนั้นเป็นผลงานที่ยอดเยี่ยมเนื่องจากการเรียกใช้ทุกวิธีใช้การสะท้อนกลับ การคาดหวังว่าภาษาหนึ่งจะทำงานเหมือนกับภาษาอื่นโดยทั่วไปถือว่าเป็นอันตราย หนึ่งต้องตระหนักถึงความแตกต่าง
Joachim Sauer

ควรจะดีและรวดเร็วด้วย invokedynamic ใน JDK7 (หรือแม้กระทั่งใช้เทคนิคการใช้งานที่คล้ายกันในปัจจุบัน)
Tom Hawtin - แทคไลน์

5

Java ไม่สนับสนุนความแปรปรวนร่วมในพารามิเตอร์เฉพาะในประเภทผลตอบแทน

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

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

ขอให้โชคดีกับขั้นตอนการสัมภาษณ์งาน! ฉันชอบที่จะเข้ารับการสัมภาษณ์ใน บริษัท ที่ถามคำถามประเภทนี้แทนที่จะเป็นคำถามเกี่ยวกับโครงสร้าง / โครงสร้างข้อมูลตามปกติที่ฉันสอนนักเรียน


1
คุณหมายถึงพารามิเตอร์ที่แตกต่างกัน
Tom Hawtin - แทคไลน์

ฉันเข้าใจอย่างสมบูรณ์เกี่ยวกับข้อเท็จจริงที่ว่าพารามิเตอร์วิธีการที่แตกต่างกันสร้างเมธอดที่โอเวอร์โหลดโดยภายในไม่ใช่วิธีที่ถูกแทนที่ ไม่ต้องกังวลมีคำถามเกี่ยวกับโครงสร้างข้อมูล / โครงสร้างข้อมูลเช่นกัน : P และขอบคุณสำหรับความโชคดีฉันต้องการมัน! :)
มักซอล

4

ฉันคิดว่ากุญแจสำคัญอยู่ที่ความจริงที่ว่าวิธีการ equals () ไม่เป็นไปตามมาตรฐาน: ใช้ในวัตถุทดสอบอื่นไม่ใช่วัตถุวัตถุดังนั้นจึงไม่ได้แทนที่วิธีการ equals () ซึ่งหมายความว่าคุณมีภาระมากเกินไปเท่านั้นที่จะทำบางสิ่งพิเศษเมื่อได้รับวัตถุทดสอบในขณะที่ให้วัตถุ Object เรียก Object.equals (Object o) การดูรหัสนั้นผ่าน IDE ใด ๆ ควรแสดงวิธีการทดสอบเท่ากับ () สองวิธี


สิ่งนี้และคำตอบส่วนใหญ่ไม่มีประเด็น ปัญหาไม่ได้เกี่ยวกับความจริงที่ว่ามีการใช้การโอเวอร์โหลดแทนการลบล้าง เป็นเหตุให้ไม่ใช้วิธีโอเวอร์โหลดสำหรับ t1.equals (t3) เมื่อ t1 ถูกประกาศเป็น Object แต่เริ่มต้นเป็น Test
Robin

4

วิธีนี้ใช้งานมากเกินไปแทนที่จะใช้การโอเวอร์ริด เท่ากับใช้ Object เป็นพารามิเตอร์เสมอ

btw คุณมีไอเท็มเกี่ยวกับสิ่งนี้ใน java ที่มีประสิทธิภาพของ Bloch (ที่คุณควรเป็นเจ้าของ)


Java ที่มีประสิทธิภาพของ Joshua Bloch?
DJClayworth

ได้ผลใช่กำลังคิดอย่างอื่นในขณะที่พิมพ์: D
Gilles

4

หมายเหตุบางอย่างในDynamic Binding (DD) และStatic Binding̣̣̣ (SB) หลังจากค้นหาสักพัก:

1. กำหนดเวลาดำเนินการ : (อ้างอิง 1)

  • DB: ในเวลาทำงาน
  • SB: เวลาคอมไพเลอร์

2. ใช้สำหรับ :

  • DB: การลบล้าง
  • SB: การโอเวอร์โหลด (คงที่, ส่วนตัว, ขั้นสุดท้าย) (Ref.2)

อ้างอิง:

  1. ดำเนินการตัวแก้ไขค่าเฉลี่ยที่วิธีการที่ต้องการใช้
  2. เนื่องจากไม่สามารถแทนที่วิธีการด้วยตัวปรับแต่งแบบคงที่ส่วนตัวหรือขั้นสุดท้าย
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

2

หากมีการเพิ่มวิธีการอื่นที่แทนที่แทนการโอเวอร์โหลดจะอธิบายการเรียกการผูกแบบไดนามิกในขณะรันไทม์

/ * ผลลัพธ์ของโปรแกรมต่อไปนี้คืออะไร? * /

public class DynamicBinding {
    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside @override: this is dynamic binding");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++);// prints 0
        t1.equals(t2);
        System.out.println(count++);// prints 1
        t1.equals(t3);
        System.out.println(count++);// prints 2
        t3.equals(o1);
        System.out.println(count++);// prints 3
        t3.equals(t3);
        System.out.println(count++);// prints 4
        t3.equals(t2);
    }
}

1

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

https://sites.google.com/site/jeffhartkopf/covariance


0

คำตอบสำหรับคำถาม "ทำไม" นั่นคือวิธีกำหนดภาษา Java

หากต้องการอ้างอิงบทความ Wikipedia เกี่ยวกับความแปรปรวนร่วมและความแตกต่าง :

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

ภาษาอื่นแตกต่างกัน


ปัญหาของฉันเทียบเท่ากับการเห็น 3 + 3 และการเขียน 9 จากนั้นเห็น 1 + 1 และเขียน 2 ฉันเข้าใจวิธีกำหนดภาษา Java ในกรณีนี้ไม่ว่าจะด้วยเหตุผลใดก็ตามฉันเข้าใจวิธีการนั้นผิดไปโดยสิ้นเชิงแม้ว่าฉันจะหลีกเลี่ยงข้อผิดพลาดนั้นที่อื่นในปัญหาเดียวกันก็ตาม
Magsol

0

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


-1

ฉันจะพยายามอธิบายสิ่งนี้ผ่านสองตัวอย่างซึ่งเป็นเวอร์ชันขยายของตัวอย่างบางส่วนที่ฉันพบทางออนไลน์

public class Test {

    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside of Test.equals ot type Object");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++); // prints 0
        o1.equals(t2);

        System.out.println("\n" + count++); // prints 1
        o1.equals(t3);

        System.out.println("\n" + count++);// prints 2
        t1.equals(t2);

        System.out.println("\n" + count++);// prints 3
        t1.equals(t3);

        System.out.println("\n" + count++);// prints 4
        t3.equals(o1);

        System.out.println("\n" + count++);// prints 5
        t3.equals(t3);

        System.out.println("\n" + count++);// prints 6
        t3.equals(t2);
    }
}

ที่นี่สำหรับบรรทัดที่มีค่านับ 0, 1, 2 และ 3; เรามีการอ้างอิงของObjectสำหรับo1และt1บนequals()วิธีการ ดังนั้นในเวลาคอมไพล์equals()เมธอดจากไฟล์Object.classจะถูก จำกัด ขอบเขต

อย่างไรก็ตามแม้ว่าการอ้างอิงของT1เป็นวัตถุก็มีการเริ่มต้นระบบของระดับการทดสอบ
Object t1 = new Test();.
ดังนั้นในขณะรันไทม์จะเรียกสิ่งpublic boolean equals(Object other)ที่เป็นไฟล์

วิธีการแทนที่

. ป้อนคำอธิบายภาพที่นี่

ตอนนี้สำหรับการนับค่าเป็น 4 และ 6 ตรงไปตรงมาอีกครั้งว่าt3ที่มีการอ้างอิงและการเริ่มต้นของการทดสอบคือการเรียกequals()วิธีการที่มีพารามิเตอร์เป็นการอ้างอิงวัตถุและเป็น

วิธีการมากเกินไป

ตกลง!

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

ป้อนคำอธิบายภาพที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.