ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 동적바인딩과 @Override
    언어/자바 2023. 12. 20. 00:56

     

     

    이번 글의 목차

    • 코드 실행 순서를 맞춰보자
    • 부모 클래스가 버젓이 print를 가지고 있는데도, 부모 클래스에서 자식의 print가 실행된다!?
    • 런타임에는 무슨일이?
    • 왜 이렇게 작동할까?
    • super와 this를 이용한 오버로딩 메소드 호출

     

    코드의 실행 순서를 맞춰보자

     

    아래 코드가 어떻게 작동되는지
    100% 확신이 있으시다면 뒤로 가셔도 좋습니다 😇😇

     

    class Base {
      Base() {
        System.out.println("Base Class Constructor");
        print();
      }
    
      void print() {
        System.out.println("Base");
      }
    }
    
    class Derived extends Base {
      int x;
    
      Derived(int x) {
        System.out.println("Derived Class Constructor");
        this.x = x;
      }
    
      @Override
      void print() {
        System.out.println("Derived " + x);
      }
    }
    
    public class Main {
      public static void main(String[] args) {
        Derived d = new Derived(10);
        d.print();
      }
    }
    
    
    // <정답>
    // Base Class Constructor
    // Derived 0
    // Derived Class Constructor
    // Derived 10

     

     

    함정은 Base의 생성자에 있는 print 메소드에 있습니다.
    실행 흐름은 이렇습니다!

    1. 자식의 생성자 이전에 부모의 생성자가 먼저 실행됩니다.
    2. print 메소드는 오버라이딩 되어있기 때문에, 호출 위치와 상관없이
      자식의 print 메소드함수가 실행됩니다.
    3. 이 때, 자식 생성자는 아직 실행되지 않았기 때문에 x는 0입니다.
    4. 그리고 이후에 자식 생성자가 실행되고
    5. 마지막으로 d.print를 통해서 x 10이 찍히게 됩니다.

    자 그럼, 왜 이렇게 작동하는지 궁금해지지 않나요..ㅎㅎ

     


     

    부모 클래스가 버젓이 print를 가지고 있는데도,
    부모 클래스에서 자식의 print가 실행된다!?

     

    컴파일 타임에는 뭘하나 싶으니 일단 컴파일부터 해본다..

     

    일단 상속을 받은 자식의 생성자에서 부모의 생성자가 먼저 실행되는 것을 확인가능하다.
    또 부모와 자식 모두 같은 시그니처의 void print() 메소드를 가지고 있는 것을 통해서 오버라이딩이 이루어지고 있다는 것을 확인할 수 있다.

     

     

    class Base {
      Base();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           7: ldc           #3                  // String Base Class Constructor
           9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          12: aload_0
          13: invokevirtual #5                  // Method print:()V
          16: return
    
      void print();
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #6                  // String Base
           5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
    
    
    class Derived extends Base {
      int x;
    
      Derived(int);
        Code:
           0: aload_0
           1: invokespecial #1                  // Method Base."<init>":()V
           4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           7: ldc           #3                  // String Derived Class Constructor
           9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          12: aload_0
          13: iload_1
          14: putfield      #5                  // Field x:I
          17: return
    
      void print();
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: aload_0
           4: getfield      #5                  // Field x:I
           7: invokedynamic #6,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
          12: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          15: return
    }
    
    
    public class Main {
      public Main();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class Derived
           3: dup
           4: bipush        10
           6: invokespecial #3                  // Method Derived."<init>":(I)V
           9: astore_1
          10: aload_1
          11: invokevirtual #4                  // Method Derived.print:()V
          14: return
    }

     


     

    런타임에는 무슨일이?

     

    타입은 인터페이스를 보장해줄 뿐, 실행은 실제 메모리에 있는 내용을 따른다.

    dog2는 Animal 클래스가 가지고 있는 인터페이스만 사용할 수 있도록 한정하지만,
    메소드 실행시에는 메모리 주소에 들어있는 객체의 상태를 따라간다.
    (Method Animal.sleep()V 라고 되어있으나 Dog의 Sleep이 실행됨)

     

    class Animal {
        eat() {}
        
        sleep() {
        	System.out.println("animal");
        }
    }
    
    class Dog extends Animal {
        bark() {}
        
        @Override
        sleep() {
        	System.out.println("dog");
        }
    }
    
    public class Main {
      public static void main(String[] args) {
        Dog dog1 = new Dog();
        dog1.bark();
        dog1.eat();
        dog1.sleep();     // "dog"
    
        
        Animal dog2 = new Dog();
        dog2.bark();     // 실행불가능
        dog2.eat();
        dog2.sleep();    // "dog"
      }
    }

     

    /* Main의 바이트 코드 */
    
    public class Main {
      public Main();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class Dog
           3: dup
           4: invokespecial #3                  // Method Dog."<init>":()V
           7: astore_1
           8: aload_1
           9: invokevirtual #4                  // Method Dog.bark:()V
          12: aload_1
          13: invokevirtual #5                  // Method Dog.eat:()V
          16: aload_1
          17: invokevirtual #6                  // Method Dog.sleep:()V
          20: new           #2                  // class Dog
          23: dup
          24: invokespecial #3                  // Method Dog."<init>":()V
          27: astore_2
          28: aload_2
          29: invokevirtual #7                  // Method Animal.eat:()V
          32: aload_2
          33: invokevirtual #8                  // Method Animal.sleep:()V
          36: return
    }

     


     

    왜 이렇게 작동할까?

     

    객체지향은 다형성의 특징을 이용하고 있기 때문에, 부모를 상속받은 다양한 종류의
    자식 객체가 부모 타입의 객체에 할당이 가능하다.
    아래 코드처럼 코드가 실행되기 이전에 어떤 객체가 할당될지 알 수 없는 경우가 있기때문에, 자바는 오버라이딩에 대해서 동적 바인딩을 하도록 만들어졌다고 생각한다.

     

    class Animal {
        void sleep() {
        	System.out.println("animal");
        }
    }
    
    class Dog extends Animal {
        
        @Override
        void sleep() {
        	System.out.println("dog");
        }
    }
    
    class Cat extends Animal {
        
        @Override
        void sleep() {
        	System.out.println("cat");
        }
    }
    
    public class Main {
      public static void main(String[] args) {
        int randomNum = (int) (Math.random() * 10);
    
        Animal animal;
        if (randomNum > 5) {
          animal = new Dog();
        } else {
          animal = new Cat();
        }
    
        animal.sleep();
      }
    }
    
    
    /* 바이트 코드 */
    
    public class Main {
      public Main();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: invokestatic  #2                  // Method java/lang/Math.random:()D
           3: ldc2_w        #3                  // double 10.0d
           6: dmul
           7: d2i
           8: istore_1
           9: iload_1
          10: iconst_5
          11: if_icmple     25
          14: new           #5                  // class Dog
          17: dup
          18: invokespecial #6                  // Method Dog."<init>":()V
          21: astore_2
          22: goto          33
          25: new           #7                  // class Cat
          28: dup
          29: invokespecial #8                  // Method Cat."<init>":()V
          32: astore_2
          33: aload_2
          34: invokevirtual #9                  // Method Animal.sleep:()V
          37: return
    }

     


     

    super와 this를 이용한 오버로딩 메소드 호출

     

    this

    Animal 클래스에서 사용된 this는 호출된 시점의 객체를 지칭한다.
    new Animal()을 통해서 호출되었다면 this는 animal 객체를,
    new Dog()를 통해서 호출되었다면 this는 dog 객체를 지칭한다.

    Dog클래스는 이미 컴파일타임에 sleep이 오버라이딩 되어서 dog객체의 sleep은
    오버라이딩 된 sleep이다.
    (개인적으로는 Dog를 생각할 때 Animal은 생각하지 않고, Animal의 초기화 과정이 Dog의 생성자의 가장 처음에 실행된다고 생각하려한다.)

     

    super

    super는 this와는 다르게 해당 객체의 부모를 명시적으로 가르킨다.
    따라서 오버라이딩 여부와 관계없이 실제 부모 클래스가 가지고 있는 메소드를
    가리킨다.

     

    class Animal {
      Animal() {
        this.sleep();
      }
      void sleep() {
        System.out.println("animal");
      }
    }
    
    class Dog extends Animal {
      Dog() {
        super();
        super.sleep();
      }
    
      @Override
      void sleep() {
        System.out.println("dog");
      }
    }
    
    public class Main {
      public static void main(String[] args) {
        Dog dog1 = new Dog();
      }
    }
    
    
    /* 바이트 코드 */
    
    class Animal {
      Animal();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: aload_0
           5: invokevirtual #2                  // Method sleep:()V
           8: return
    
      void sleep();
        Code:
           0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #4                  // String animal
           5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
    
    class Dog extends Animal {
      Dog();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method Animal."<init>":()V
           4: aload_0
           5: invokespecial #2                  // Method Animal.sleep:()V
           8: return
    
      void sleep();
        Code:
           0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #4                  // String dog
           5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
Designed by Tistory.