คงเทียบกับ การผูกแบบไดนามิกใน Java


104

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

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

ฉันพบตัวอย่างของการผูกแบบคงที่ทางออนไลน์ที่ให้ตัวอย่างนี้:

public static void callEat(Animal animal) {
    System.out.println("Animal is eating");
}

public static void callEat(Dog dog) {
    System.out.println("Dog is eating");
}

public static void main(String args[])
{
    Animal a = new Dog();
    callEat(a);
}

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

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



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

(มีภาษาที่สามารถเลือกลายเซ็นวิธีเฉพาะได้จนกว่าจะรันไทม์และจะเลือก callEat (Dog))
Hot Licks

คำตอบ:


115

จากบล็อกโพสต์ Javarevisited :

ความแตกต่างที่สำคัญบางประการระหว่างการเชื่อมแบบคงที่และแบบไดนามิกมีดังนี้

  1. การผูกแบบคงที่ใน Java เกิดขึ้นระหว่างเวลาคอมไพล์ในขณะที่การโยงแบบไดนามิกเกิดขึ้นระหว่างรันไทม์
  2. private, finalและstaticวิธีการและตัวแปรใช้คงมีผลผูกพันและจะถูกผูกมัดโดยคอมไพเลอร์ขณะที่วิธีเสมือนจะถูกผูกมัดในช่วง Runtime ขึ้นอยู่กับวัตถุรันไทม์
  3. การผูกแบบคงที่ใช้ข้อมูลType( classใน Java) สำหรับการผูกในขณะที่การโยงแบบไดนามิกใช้อ็อบเจ็กต์เพื่อแก้ไขการโยง
  4. วิธีการโอเวอร์โหลดถูกผูกมัดโดยใช้การผูกแบบคงที่ในขณะที่เมธอดที่ถูกแทนที่ถูกผูกมัดโดยใช้การผูกแบบไดนามิกที่รันไทม์

นี่คือตัวอย่างที่จะช่วยให้คุณเข้าใจทั้งการโยงแบบคงที่และแบบไดนามิกใน Java

Static Binding Example ใน Java

public class StaticBindingTest {  
    public static void main(String args[]) {
        Collection c = new HashSet();
        StaticBindingTest et = new StaticBindingTest();
        et.sort(c);
    }
    //overloaded method takes Collection argument
    public Collection sort(Collection c) {
        System.out.println("Inside Collection sort method");
        return c;
    }
    //another overloaded method which takes HashSet argument which is sub class
    public Collection sort(HashSet hs) {
        System.out.println("Inside HashSet sort method");
        return hs;
    }
}

เอาท์พุท : วิธีการเรียงลำดับ Inside Collection

ตัวอย่าง Dynamic Binding ใน Java

public class DynamicBindingTest {   
    public static void main(String args[]) {
        Vehicle vehicle = new Car(); //here Type is vehicle but object will be Car
        vehicle.start(); //Car's start called because start() is overridden method
    }
}

class Vehicle {
    public void start() {
        System.out.println("Inside start method of Vehicle");
    }
}

class Car extends Vehicle {
    @Override
    public void start() {
        System.out.println("Inside start method of Car");
    }
}

เอาท์พุท:วิธีสตาร์ทภายในรถ



11
ฉันยังไม่เข้าใจความแตกต่าง
technazi

9
@technazi การผูกแบบคงที่เพียงแค่ดูที่ประเภท (สิ่งที่เคยมีมาก่อนเท่ากับเช่น Collection c = new HashSet (); ดังนั้นจะถูกมองว่าเป็นเพียงวัตถุคอลเลกชันเมื่อ infact เป็นแฮชเซ็ต) การรวม Dyanmic คำนึงถึงวัตถุจริง (อะไรที่ตามหลังเท่ากับเพื่อให้รับรู้ HashSet)
มาร์ค

22

การเชื่อมต่อการเรียกเมธอดเข้ากับเนื้อความของเมธอดเรียกว่าการผูก ดังที่ Maulik กล่าวว่า "การผูกแบบคงที่ใช้ข้อมูล Type (Class ใน Java) สำหรับการผูกในขณะที่การโยงแบบไดนามิกใช้ Object เพื่อแก้ไขการผูก" ดังนั้นรหัสนี้:

public class Animal {
    void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {
        Animal a = new Dog();
        a.eat(); // prints >> dog is eating...
    }

    @Override
    void eat() {
        System.out.println("dog is eating...");
    }
}

จะให้ผลลัพธ์คือ dog is eating ...เพราะกำลังใช้ object reference เพื่อหาว่าจะใช้วิธีไหน. หากเราเปลี่ยนรหัสด้านบนเป็น:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

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


1
เหตุใด Java จึงไม่สามารถอนุมานได้ว่าaเป็นDogเวลาคอมไพล์จริง ๆ?
Minh Ngh Na

4

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

public class DynamicBindingTest {

    public static void main(String args[]) {
        Animal a= new Dog(); //here Type is Animal but object will be Dog
        a.eat();       //Dog's eat called because eat() is overridden method
    }
}

class Animal {

    public void eat() {
        System.out.println("Inside eat method of Animal");
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("Inside eat method of Dog");
    }
}

เอาท์พุท: วิธีกินภายในของสุนัข


สิ่งนี้จะไม่ทำให้เกิดข้อผิดพลาดในการคอมไพล์เช่น "ไม่สามารถอ้างอิงคลาส / วิธีการที่ไม่คงที่จากบริบทคงที่" ได้หรือไม่? ฉันมักจะสับสนกับเรื่องนี้โดยจำไว้ว่า main นั้นคงที่ ขอบคุณล่วงหน้า.
59 เวลา 09:02 น

4

เพื่อที่จะเข้าใจว่าการผูกแบบคงที่และแบบไดนามิกทำงานอย่างไร? หรือวิธีระบุโดยคอมไพเลอร์และ JVM?

ลองมาตัวอย่างด้านล่างที่Mammalเป็นระดับผู้ปกครองซึ่งมีวิธีการspeak()และHumanระดับขยายMammal, แทนที่speak()วิธีการและจากนั้นอีกครั้ง overloads speak(String language)มันด้วย

public class OverridingInternalExample {

    private static class Mammal {
        public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
    }

    private static class Human extends Mammal {

        @Override
        public void speak() { System.out.println("Hello"); }

        // Valid overload of speak
        public void speak(String language) {
            if (language.equals("Hindi")) System.out.println("Namaste");
            else System.out.println("Hello");
        }

        @Override
        public String toString() { return "Human Class"; }

    }

    //  Code below contains the output and bytecode of the method calls
    public static void main(String[] args) {
        Mammal anyMammal = new Mammal();
        anyMammal.speak();  // Output - ohlllalalalalalaoaoaoa
        // 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Mammal humanMammal = new Human();
        humanMammal.speak(); // Output - Hello
        // 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Human human = new Human();
        human.speak(); // Output - Hello
        // 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V

        human.speak("Hindi"); // Output - Namaste
        // 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
    }
}

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

โปรแกรม Bytecode

โดยดูที่โค้ดข้างต้นเราจะเห็นว่า bytecodes ของhumanMammal.speak(), human.speak()และhuman.speak("Hindi")มีความแตกต่างกันโดยสิ้นเชิง ( invokevirtual #4, invokevirtual #7, invokevirtual #9) เพราะคอมไพเลอร์จะสามารถแยกความแตกต่างระหว่างพวกเขาขึ้นอยู่กับรายการอาร์กิวเมนต์และการอ้างอิงระดับ เพราะทั้งหมดนี้ได้รับการแก้ไขที่รวบรวมเวลาแบบคงที่นั่นคือเหตุผลที่วิธีการมากไปเป็นที่รู้จักกันคงความแตกต่างหรือคงผูกพัน

แต่ bytecode สำหรับanyMammal.speak()และhumanMammal.speak()เหมือนกัน ( invokevirtual #4) เนื่องจากตามคอมไพเลอร์ทั้งสองวิธีถูกเรียกโดยMammalอ้างอิง

ดังนั้นคำถามเกิดขึ้นหากการเรียกทั้งสองวิธีมีรหัสไบต์เดียวกันแล้ว JVM จะรู้ได้อย่างไรว่าจะเรียกวิธีใด

คำตอบซ่อนอยู่ใน bytecode เองและเป็นinvokevirtualชุดคำสั่ง JVM ใช้invokevirtualคำสั่งเพื่อเรียกใช้ Java ที่เทียบเท่ากับวิธีการเสมือน C ++ ใน C ++ หากเราต้องการแทนที่เมธอดหนึ่งในคลาสอื่นเราจำเป็นต้องประกาศว่าเป็นเสมือน แต่ใน Java เมธอดทั้งหมดจะเป็นเสมือนตามค่าเริ่มต้นเนื่องจากเราสามารถแทนที่ทุกเมธอดในคลาสลูกได้ (ยกเว้นเมธอดส่วนตัวขั้นสุดท้ายและสแตติก)

ใน Java ตัวแปรอ้างอิงทุกตัวมีตัวชี้ที่ซ่อนอยู่สองตัว

  1. ตัวชี้ไปยังตารางซึ่งเก็บวิธีการของวัตถุไว้อีกครั้งและตัวชี้ไปยังวัตถุคลาส เช่น [speak (), speak (String) Class object]
  2. ตัวชี้ไปยังหน่วยความจำที่จัดสรรบนฮีปสำหรับข้อมูลของวัตถุนั้นเช่นค่าของตัวแปรอินสแตนซ์

ดังนั้นการอ้างอิงอ็อบเจ็กต์ทั้งหมดจะถือการอ้างอิงไปยังตารางโดยอ้อมซึ่งเก็บการอ้างอิงเมธอดทั้งหมดของอ็อบเจ็กต์นั้น Java ได้ยืมแนวคิดนี้มาจาก C ++ และตารางนี้เรียกว่าตารางเสมือน (vtable)

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

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

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

คุณสามารถอ่านได้รายละเอียดเพิ่มเติมในบทความของฉันอย่างไร JVM จับวิธีมากไปและแทนที่ภายใน


2

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

การผูกแบบคงที่ : ในการผูกแบบคงที่จะกล่าวถึงปัญหาต่อไปนี้:

  • ความหมายของขั้นตอน

  • การประกาศชื่อ (ตัวแปร ฯลฯ )

  • ขอบเขตของการประกาศ

การผูกแบบไดนามิก : ปัญหาสามประการที่พบในการเชื่อมโยงแบบไดนามิกมีดังต่อไปนี้:

  • การเปิดใช้งานขั้นตอน

  • การผูกชื่อ

  • อายุการผูกมัด


1

ด้วยวิธีคงที่ในคลาสพาเรนต์และคลาสย่อย: Static Binding

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child(); 
        pc.start(); 
    }
}

class parent {
    static public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

    static public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of parent

การผูกแบบไดนามิก:

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child();
        pc.start(); 
    }
}

class parent {
   public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

   public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of child

0

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

ดูตัวอย่างด้านล่าง:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

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

ฉันพบลิงค์ด้านล่างเพื่อสนับสนุนคำตอบของฉัน: https://youtu.be/tNgZpn7AeP0


0

ในกรณีของประเภทการผูกแบบคงที่ของอ็อบเจ็กต์ที่กำหนดในเวลาคอมไพล์ในขณะที่ประเภทการรวมไดนามิกของอ็อบเจ็กต์จะถูกกำหนดที่รันไทม์



class Dainamic{

    void run2(){
        System.out.println("dainamic_binding");
    }

}


public class StaticDainamicBinding extends Dainamic {

    void run(){
        System.out.println("static_binding");
    }

    @Override
    void run2() {
        super.run2();
    }

    public static void main(String[] args) {
        StaticDainamicBinding st_vs_dai = new StaticDainamicBinding();
        st_vs_dai.run();
        st_vs_dai.run2();
    }

}

-3

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

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


1
ไม่จริง. คอมไพเลอร์จะทำการตัดสินใจแบบเดียวกันหากคุณกำลังโทรบนอินเทอร์เฟซ
Hot Licks

@HotLicks การตัดสินใจเช่นเดียวกับอะไร? หากคุณคอมไพล์คลาสเพื่อเรียกใช้เมธอด foo (String str) บนอินเทอร์เฟซคอมไพเลอร์จะไม่สามารถทราบได้ในเวลาคอมไพล์ว่าคลาสใดที่ควรเรียกใช้เมธอด foo (String str) เฉพาะที่รันไทม์เท่านั้นที่การเรียกใช้เมธอดสามารถถูกผูกไว้กับการใช้งานคลาสเฉพาะ
แอรอน

แต่การผูกแบบคงที่กับลายเซ็นวิธีการเฉพาะยังคงเกิดขึ้น คอมไพเลอร์ยังคงเลือก callEat (Animal) มากกว่า callEat (Dog)
Hot Licks

@HotLicks แน่นอน แต่นั่นไม่ใช่คำถามที่ฉันตอบ บางทีมันอาจทำให้ฉันเข้าใจผิด: DI เปรียบเทียบกับการเรียกใช้อินเทอร์เฟซเพื่อเน้นว่าในเวลาคอมไพล์คอมไพเลอร์ไม่สามารถรู้ได้ว่าคุณสร้างอินสแตนซ์คลาสย่อย / การนำไปใช้งานที่แตกต่างกันจริงหรือไม่
แอรอน

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