정적 대. 자바의 동적 바인딩


103

현재 클래스 중 하나에 대한 할당을 수행하고 있으며 그 안에서 정적동적 바인딩 의 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);
}

그리고 이것은 호출이 callEatstatic binding 을 사용 하기 때문에 "animal is eating"을 인쇄 하지만 이것이 정적 바인딩으로 간주 되는 이유를모르겠습니다 .

지금까지 내가 본 소스 중 어느 것도 내가 따를 수있는 방식으로 이것을 설명하지 못했습니다.



1
"바인딩"이라고하는 몇 가지 다른 개념이 있습니다. 이 특정 경우에,이 유형의 바인딩 (유사하지만 동일하지 않은 "서명"메소드 중에서 선택해야 함)의 경우 컴파일러 (런타임에 변하지 않기 때문에 "정적"결정을 내림)는 매개 변수가 "Animal"은 변수 "a"의 (정적) 유형이기 때문입니다.
Hot Licks 2013 년

(이 특정 방법 서명의 선택이 실행 될 때까지 남아있을 것이다 언어가 있으며, callEat (개)을 선택할 수있다.)
핫 핥는

답변:


115

에서 Javarevisited 블로그 게시물 :

다음은 정적 바인딩과 동적 바인딩의 몇 가지 중요한 차이점입니다.

  1. Java의 정적 바인딩은 컴파일 시간에 발생하고 동적 바인딩은 런타임 중에 발생합니다.
  2. private, finalstatic방법과 결합 정적 변수를 사용하여 가상 메소드 런타임 객체에 기초하여 런타임 접합하면서 컴파일러에 의해 결합된다.
  3. 정적 바인딩은 바인딩을 위해 Type( classJava에서) 정보를 사용하는 반면 동적 바인딩은 바인딩을 해결하기 위해 객체를 사용합니다.
  4. 오버로드 된 메서드는 정적 바인딩을 사용하여 연결되는 반면 재정의 된 메서드는 런타임에 동적 바인딩을 사용하여 연결됩니다.

다음은 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;
    }
}

출력 : 컬렉션 내부 정렬 방법

자바의 동적 바인딩 예

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

출력 : Car 내부 시작 방법



11
난 아직도 그 차이를 이해하지 않는
technazi

9
@technazi 정적 바인딩은 유형을 살펴 봅니다 (예 : Collection c = new HashSet (); 따라서 그것이 해시 세트 일 때 컬렉션 객체로 간주됩니다). 동적 바인딩은 실제 개체를 고려합니다 (등호 뒤에있는 것이 실제로 HashSet를 인식하도록 함).
Mark

22

메서드 호출을 메서드 본문에 연결하는 것을 바인딩이라고합니다. Maulik이 말했듯이 "정적 바인딩은 바인딩을 위해 Type (Class in 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 ... 사용할 방법을 찾기 위해 객체 참조를 사용하기 때문입니다. 위의 코드를 다음과 같이 변경하면 :

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...");
    }
}

다음을 생성합니다. animal is eating ... 정적 메서드이기 때문에 Type (이 경우 Animal)을 사용하여 호출 할 정적 메서드를 확인합니다. 정적 메서드 외에도 private 및 final 메서드는 동일한 접근 방식을 사용합니다.


1
Java a가 실제로 Dog컴파일 타임에 추론 할 수없는 이유는 무엇 입니까?
Minh Nghĩa

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

출력 : Dog의 내부 먹기 방법


"정적 컨텍스트에서 비 정적 클래스 / 메서드를 참조 할 수 없습니다"와 같은 컴파일 오류가 발생하지 않습니까? 나는 main이 정적이라는 것을 염두에두고 항상 혼란스러워합니다. 미리 감사드립니다.
Amnor

4

정적 및 동적 바인딩이 실제로 어떻게 작동 하는지 이해하려면 ? 또는 컴파일러와 JVM에서 어떻게 식별됩니까?

Mammal메서드 speak()Human클래스 extends 가있는 부모 클래스가있는 아래 예제를 살펴 보겠습니다 . 메서드를 Mammal재정의 speak()한 다음 다시 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
    }
}

위 코드를 컴파일하고를 사용하여 바이트 코드를 살펴보면 javap -verbose OverridingInternalExample컴파일러가 프로그램 자체에 추출하여 포함시킨 프로그램의 모든 메서드 호출과 바이트 코드에 정수 코드를 할당하는 상수 테이블을 생성하는 것을 볼 수 있습니다. 모든 메서드 호출 아래의 주석을 참조하십시오)

프로그램 바이트 코드

위의 코드를 살펴보면 우리의 바이트 코드 것을 볼 수 있습니다 humanMammal.speak(), human.speak()그리고 human.speak("Hindi")완전히 다른 ( invokevirtual #4, invokevirtual #7, invokevirtual #9) 컴파일러는 인수 목록 및 클래스 참조를 기반으로 그들을 구별 할 수 있기 때문이다. 이 모든 것이 컴파일 타임에 정적으로 해결되기 때문에 Method OverloadingStatic Polymorphism 또는 Static Binding이라고 합니다.

그러나 컴파일러에 따라 두 메서드가 모두 참조 로 호출되기 때문에 anyMammal.speak()humanMammal.speak()에 대한 바이트 코드 는 동일합니다 ( invokevirtual #4) Mammal.

이제 두 메서드 호출에 동일한 바이트 코드가 있으면 JVM이 호출 할 메서드를 어떻게 알 수 있습니까?

글쎄, 대답은 바이트 코드 자체에 숨겨져 있으며 invokevirtual명령어 세트입니다. JVM은 invokevirtual명령을 사용하여 C ++ 가상 메소드에 해당하는 Java를 호출합니다. C ++에서 다른 클래스의 한 메서드를 재정의하려면 가상으로 선언해야하지만 Java에서는 자식 클래스의 모든 메서드를 재정의 할 수 있기 때문에 기본적으로 모든 메서드가 가상입니다 (개인, 최종 및 정적 메서드 제외).

Java에서 모든 참조 변수에는 두 개의 숨겨진 포인터가 있습니다.

  1. 객체의 메서드를 다시 보유하는 테이블에 대한 포인터와 Class 객체에 대한 포인터입니다. 예 : [speak (), speak (String) Class 객체]
  2. 해당 개체의 데이터 (예 : 인스턴스 변수 값)에 대해 힙에 할당 된 메모리에 대한 포인터입니다.

따라서 모든 개체 참조는 해당 개체의 모든 메서드 참조를 보유하는 테이블에 대한 참조를 간접적으로 보유합니다. Java는이 개념을 C ++에서 차용했으며이 테이블을 가상 테이블 (vtable)이라고합니다.

vtable은 배열 인덱스에 대한 가상 메서드 이름과 참조를 보유하는 배열과 같은 배열입니다. JVM은 클래스를 메모리에로드 할 때 클래스 당 하나의 vtable 만 생성합니다.

따라서 JVM이 invokevirtual명령어 세트를 만날 때마다 해당 클래스의 vtable에서 메서드 참조를 확인하고 우리의 경우 참조가 아닌 객체의 메서드 인 특정 메서드를 호출합니다.

이 모든 것이 런타임에만 해결되고 런타임에 JVM이 호출 할 메서드를 알기 때문에 메서드 재정의Dynamic Polymorphism 또는 단순히 Polymorphism 또는 Dynamic Binding이라고 합니다.

내 기사 에서 JVM이 메서드 오버로딩 및 재정의를 내부적으로 처리하는 방법 에 대한 자세한 내용을 읽을 수 있습니다 .


2

컴파일러를 설계하는 동안 정적 바인딩과 동적 바인딩 사이에는 세 가지 주요 차이점과 변수프로 시저런타임 환경으로 전송되는 방식이 있습니다. 이러한 차이점은 다음과 같습니다.

정적 바인딩 : 정적 바인딩 에서는 다음 세 가지 문제에 대해 설명합니다.

  • 절차의 정의

  • 이름 선언 (변수 등)

  • 선언의 범위

동적 바인딩 : 동적 바인딩에서 발생하는 세 가지 문제는 다음과 같습니다.

  • 절차 활성화

  • 이름 바인딩

  • 바인딩의 수명


1

부모 및 자식 클래스의 정적 메서드 사용 : 정적 바인딩

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 () 앞의 static 키워드를 제거하고 비 정적 메서드로 만들면 메서드 숨김이 아닌 동적 다형성이 표시됩니다.

내 대답을 지원하기 위해 아래 링크를 찾았습니다 : 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 2013 년

@HotLicks 정확히 무엇과 같은 결정? 인터페이스에서 foo (String str) 메서드를 호출하기 위해 클래스를 컴파일하는 경우 컴파일러는 foo (String str) 메서드가 호출되어야하는 클래스를 컴파일 타임에 알 수 없습니다. 런타임시에만 메서드 호출을 특정 클래스 구현에 바인딩 할 수 있습니다.
아론

그러나 특정 메서드 서명에 대한 정적 바인딩은 여전히 ​​발생합니다. 컴파일러는 여전히 callEat (Dog)보다 callEat (Animal)을 선택합니다.
Hot Licks 2013 년

@HotLicks 물론입니다.하지만 제가 대답 한 질문이 아닙니다. 아마도 그것은 나에게 오해의 소지가 있었을 것입니다 : DI는 컴파일 타임에 컴파일러가 실제로 다른 하위 클래스 / 구현을 인스턴스화했는지 여부를 알 수 없다는 것을 강조하기 위해 인터페이스에서 호출하는 것과 비교했습니다.
아론

사실, 컴파일 타임에 컴파일러는 (이 경우) "a"가 Dog라는 것을 매우 쉽게 알 수 있습니다. 사실, 그것을 "잊으려면"어느 정도 시간을 투자해야합니다.
Hot Licks 2013 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.