ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 상속 , dynamic dispatch
    BackEnd/자바 2020. 12. 26. 10:46

    ※ 상속

     

    상속 : 상위클래스의 변수, 메서드들을 하위 클래스가 물려받는 것 

    이미 존재하는 클래스를 기반으로 새로운 클래스를 작성하며 코드를 재사용할 수 있고, 같은 메서드라도 각 하위 클래스에서 재정의(Overriding) 해 객체 지향에 중요한 부분인 다형성을 구현한다.

     

    extends 키워드를 통해 클래스를 상속할 수 있으며, 복잡성을 줄이고 코드의 간결함을 위해 단일 상속만 가능하다.

     

    상속의 종류

    //single
    class Animal{  
    	void eat(){	System.out.println("eating..."); }  
    }  
    class Dog extends Animal{  
    	void bark(){ System.out.println("barking...");}  
    }  
    
    //Multi Level 
    class Animal{  
    	void eat(){ System.out.println("eating...");	}  
    }  
    class Dog extends Animal{  
    	void bark(){ System.out.println("barking...");	}  
    }  
    class BabyDog extends Dog{  
    	void weep(){ System.out.println("weeping...");  }  
    }  
    //Hierarchical
    class Animal{  
    	void eat(){ System.out.println("eating...");}  
    }  
    class Dog extends Animal{  
    	void bark(){ System.out.println("barking...");}  
    }  
    class Cat extends Animal{  
    	void meow(){ System.out.println("meowing...");}  
    }  

    하지만 implements 를 통해 인터페이스를 상속하면 다중 상속이 가능하고,

    실무적으로는 인터페이스 상속을 더 많이 이용하는 것 같다.

    아래는 인터페이스 상속을 통해 가능한 구조 


     

     super

     

    super()        부모 클래스의 생성자 호출

    super.변수    상위클래스 변수 호출

    super.메서드  상위클래스 메서드 호출

     

     

    부모클래스에서 상속받은 메서드 및 필드는 부모 클래스에 속하기 때문에 부모클래스의 생성자가 호출되어야

    자식 클래스에서 사용가능하다.

    그렇기 때문에 자식클래스로 인스턴트를 생성할 때 부모 클래스의 기본 생성자를 자동으로 호출한다.

    public class Fruit {
    
    	public Fruit() {
    		System.out.println("Fruit default constructor");
    	}
    	public Fruit(String fu) {
    		System.out.println("my child " + fu);
    	}	
    }
    
    public class Banana extends Fruit{
    	
    	public Banana() {
    		System.out.println("subclass banana");
    	}	
    	public static void main(String[] args) {
    		Banana ch = new Banana();
        }
    }
    //Fruit default constructor
    //subclass banana
    

     

    그래서 자식 클래스 생성자가 부모클래스의 생성자와 매개변수가 다르면 컴파일 에러가 난다.

    public class Fruit {
    
    	public Fruit(String fu) {
    		System.out.println("my child " + fu);
    	}
    	
    }
    
    public class Banana extends Fruit{
    	
    	public Banana() {                //컴파일 에러  implicit super Fruit() constructor is undefined
    		System.out.println("subclass banana");
    	}
    	
       public static void main(String[] args) {
    	  Banana ch = new Banana();
        }
    }

    이런 경우에는 부모 생성자를 호출하는 super() 메서드를 사용하면 컴파일 에러가 사라진다.

    public class Fruit {
    
    	public Fruit(String fu) {
    		System.out.println("my child " + fu);
    	}	
    }
    
    public class Banana extends Fruit{
    	
    	public Banana() {
    		super("banana");
    		System.out.println("subclass banana");
    	}	
    	public static void main(String[] args) {
    		Banana ch = new Banana();
        }
    }
    //my child banana
    //subclass banana
    

     

    super.getClass()  

    super.getClass() 메서드를 통해 부모클래스의 이름을 얻을 수 있을 것 같지만, 
    출력해보면 현재(this) 객체가 나온다.

     

    super 키워드는 사실 superclass 의 reference 를 참조하고 있지 않으며, 오직 부모클래스의 메서드만 호출할 수 있다.

    그리고 getClass() 메서드는 Object의 메서드로 런타임에 실행되는 객체를 리턴한다.

    출력해보면 super.getClass() 는 this.getClass() 와 동일 결과를 보여준다.

    부모클래스의 이름을 얻고 싶다면, super.getClass().getSuperClass() 혹은 this.getClass.getSuperClass() 를 통해 얻을 수 있다.

    public class InstrumentedHashSet<E> extends ForwardingSet<E> {
        private int addCount=0;
        
        
          public InstrumentedHashSet(Set<E> s) {        
              super(s);
              System.out.println(super.getClass());  //InstrumentedHashSet
          };
    }    
    public class ForwardingSet<E> implements Set<E>{
    
        private final Set<E> s;
        public ForwardingSet(Set<E> s) {
            this.s=s;
            System.out.println("==========");
            System.out.println(super.getClass()); //InstrumentedHashSet
            System.out.println(super.getClass().getSuperclass()); //ForwardingSet
            System.out.println("==========");
    
         
        }
    }
    Set<String> s = new InstrumentedHashSet<>(new HashSet<>(5));
    
    ==========
    class prac.InstrumentedHashSet
    class prac.ForwardingSet
    ==========
    class prac.InstrumentedHashSet

    www.programmersought.com/article/5514798831/

     


    ※ 추상 클래스

    여러 클래스에서 이용할만한 공통적인 변수나 메서드를 한 곳에 모아 선언해두고,

    구체적인 객체를 구현할 때 이를 규격으로 삼아 사용할 수 있다. 필드와 메서드가 통일되고 시간을 절약할 수 있다.

    - 추상클래스는 실체가 없기 때문에 추상클래스만으로는 객체를 생성할 수 없다.

    - 하위 클래스에서 상속받아 사용하고, 추상클래스에서 abstract 로 선언된 메서드를 Overriding 하는 것이 강제된다.

    • 메소드 오버라이딩 :  상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의해서 사용하는 것. @Override
        public abstract class Bird{
            public abstract void sing();
    
            public void fly(){
                System.out.println("날다.");
            }
        }
        
        public class Duck extends Bird{
            @Override
            public void sing() {
                System.out.println("꽥꽥!!");
            }
        }
        
        public class Test { 
            public static void main(String[] args) {
            	Bird duck1 = new Duck();
                Duck duck2 = new Duck();
                duck1.sing();
                duck1.fly();
                duck2.sing();
                duck2.fly();
            }   
        }

     


    ※ final 키워드

    final 은 변수, 메서드, 클래스를 선언할 때 사용할 수 있다.

    final 변수 선언과 동시에 초기화 하여야 한다.  값을 수정할 수 없다. get만 사용 가능하다.

    final 메서드 오버라이딩이 불가능하며 상속받은 그대로 사용해야 한다. 

    public class Fruit {
    	public String name;
    	
    	public void setName(String name) {
    		this.name = name;
    	}
    	public final String getName() {
    		return name;
    	}
    }
    
    public class Banana extends Fruit{
    	@Override
    	public void setName(String name) {
    		this.name = "Fruit name:: "+name;
    	}
    	public String getName() { //컴파일 에러 cannot override final method - remove final modifier
    		
    	}
    }

     

    final class 는 상속이 불가능하고 subClass 를 만들 수 없다. String 클래스가 있다. 

     

    final 키워드를 붙여 객체를 생성하면, 객체가 가비지 컬렉터에 의해 사라질 때까지 저장소에서 사라지지 않는다.

    final someObj obj = new someObj();

     

    private final 로 선언하면 직접 값을 참조할 수는 없지만 생성자를 통해 값을 참조할 수 있다.

    private final을 선언한 변수를 사용하면 재할당하지 못하며, 해당 필드, 메서드 별로 호출할 때마다 새로이 값이 할당(인스턴스화)한다.

     

     

    ※ Object 클래스

    java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스들의 집합으로, import 문 없이 클래스 이름으로 바로 사용 가능하다. 

    java.lang.Object 클래스는 모든 클래스의 가장 상위에 있는 클래스로 모든 클래스는 Object 를 상속하고 있다고 할 수 있다.  아래와 같은 Object 클래스가 가진 메서드들를 Override 해서 사용할 수 있는데, 

    getClass ,notify,notifyAll ,wait 같이 final  로 선언된 메서드를 override 할 수 없다! 

    출처: TCPschool.com


    ※ Dispatch 

    객체지향 프로그램에서 객체들간 메세지 전송을 기반으로 문제를 해결하는데 이때 메세지 전송은 메서드 호출을 의미하고 이를 dispatch 라고 한다.

     

    Static Dispatch  :  구현클래스를 이용해 compile time 에 어떤 메서드 호출할지 이미 결정된 것 

    Dynamic Dispatch : 인터페이스나 상위 클래스를 참조해 호출되는 메서드가 동적으로 결정되는 것

     

    ※ Static Dispatch 

    자바에서 객체 생성은 런타임시 호출된다. 컴파일시점에 타입에 대한 정보를 알 수 있다.

    아래 예제에서 타입 자체가 Dispatch 라는 구현클래스라 컴파일 시 호출할 메서드도 정해져 있고 컴파일 후 바이트 코드에서도 볼 수 있다.

    public class Test{
    	public static void main(String[] args){
        	Dispatch dis = new Dispath();
            dis.a();
        }
    }
    
    class Dispatch{
    	public void a(){
        	System.out.println("hi");
        }
    }

     

    ※ Dynamic Dispatch

     

    dynamic dispatch 는 런타임에 호출하는 다형성 작업(메서드/함수)의 이행을 선택하는 과정이다.

    이는 객체지향 프로그래밍 언어와 시스템에서 흔히 사용되거나 주요 특성으로 간주된다. 

    *다형성은 어느정도 상호교환 가능한 객체들이 같은 이름을 가지지만 다른 작업을 하는 operation 을 노출하는 현상이다. 

    *예를 들어 File 객체와 Database 객체 각각 개인 정보를 쓰기 위한 'StoreRecord'라는 메서드를각각 구현이 다르게 해가지고 있다. 프로그램은 둘 중 한 객체의 참조를 가지고 있다. 이는 run-time setting 에 의해 결정되었을 수 있고, 이 단계에서 프로그램은 어떤 것을 알거나 신경 쓰지 않을 수 있다.

    프로그램이 객체에 StoreRecord 메서드를 호출할 때, 어떤 메서드가 호출될 지 결정해야한다.

    만약 OOP 가 객체에 메시지를 보낸다고 하면, 이 예제에서 프로그램은 'StoreRecord '메서드에 unknown type의 객체에 메세지를 보내며, run time support system에 올바른 객체에 메세지를 보내도록 의존한다.

     

    dynamic dispatch 는 컴파일 할 때 다형성 오퍼레이션의 이행이 선택되는 static dispatch 와 대조된다.

    동적 디스패치의 목적은 매개 변수 (또는 여러 매개 변수)의 런타임 유형이 알려질 때까지 적절한 구현의 선택을 연기하는 것이다.

     

    *dynamic dispatch  dynamic binding 과 다르고 , 둘은 서로를 포함하지 않는 개념이다.

    dynamic binding 은 컴파일 시점이나 런타임 시점에 만들어 질 수 있고, 

    dynamic dispatch 는 한가지 특정 구현이 런타임에 선택된다는 특징이 있다. 

     

    en.wikipedia.org/wiki/Dynamic_dispatch

     

     

    인터페이스를 타입으로 메서드를 호출한다.

    컴파일러는 타입에 대한 정보를 알고 있어 런타임시에 호출 객체를 확인해 해당 객체의 메서드를 호출한다.

    런타임시에 호출 객체를 알 수 있어 바이트코드에도 어떤 객체의 메서드를 호출해야 하는지 드러나지 않는다.

    public class Test{
    	public static void main(String[] args){
        	Dispatchable dis = new Dispath();
            dis.a();
        }
    }
    
    class Dispatch implements Dispatchable{
    	public void a(){
        	System.out.println("hi");
        }
    }
    
    interface Dispatchable{
    	String a();
    }

    메서드 a() 는 매개변수가 없는 메서드이지만 자바는 묵시적으로 항상 호출 객체를 인자로 보낸다. 

    호출 객체를 인자로 보내기 때문에 this 를 이용해 메서드 내부에서 호출 객체를 참조할 수 있고

    이 점이 dynamic dispatcher 의 근거가 된다.

     

    ※  Double Dispatch 

    public class Phone {
    
    	public static void main(String[] args) {
    		List<SmartPhone> phoneList = Arrays.asList(new Iphone(),new Gallaxy());
    		Game game = new Game();
    		phoneList.forEach(game::play);
    	}
    
    }
    
    interface SmartPhone{
    	
    }
    
    class Iphone implements SmartPhone{
    	
    }
    
    class Gallaxy implements SmartPhone{
    	
    }
    
    class Game{
    	public void play(SmartPhone phone) {
    		System.out.println("play with :: " + phone.getClass().getSimpleName());
            //play with :: Iphone
            //play with :: Gallaxy
    	}
    

    * 아이폰, 갤럭시 외 SmartPhone 을 상속할 새로운 클래스가 생기면 Game 클래스도 변경되야 하는 단점

    public class Phone {
    
    	public static void main(String[] args) {
    		List<SmartPhone> phoneList = Arrays.asList(new Iphone(),new Gallaxy());
    		Game game = new Game();
    		phoneList.forEach(game::play); 
    	}
    }
    
    interface SmartPhone{
    	
    }
    
    class Iphone implements SmartPhone{
    	
    }
    
    class Gallaxy implements SmartPhone{
    	
    }
    
    class Game{
    	
    	public void play(SmartPhone phone) {
    		
    		if(phone instanceof Iphone) {
    			System.out.println("play with IPHONE:: " + phone.getClass().getSimpleName());			
    		}
    		if(phone instanceof Gallaxy) {
    			System.out.println("play with Gallaxy:: " + phone.getClass().getSimpleName());			
    		}
    	}
    	
    }

     * 컴파일 에러 나는 코드

    public class Phone {
    
    	public static void main(String[] args) {
    		List<SmartPhone> phoneList = Arrays.asList(new Iphone(),new Gallaxy());
    		Game game = new Game();
    		phoneList.forEach(game::play); //game class 에 play 호출  //play(SmartPhone) -> 하위타입으로 형변환 자동으로 안됨
    	}
    
    }
    
    interface SmartPhone{
    	
    }
    
    class Iphone implements SmartPhone{
    	
    }
    
    class Gallaxy implements SmartPhone{
    	
    }
    
    class Game{
    	public void play(Iphone phone) {
    		System.out.println("play with Iphone:: " + phone.getClass().getSimpleName());
    	}
    	public void play(Gallaxy phone) {
    		System.out.println("play with Gallaxy:: " + phone.getClass().getSimpleName());
    	}
    }

    *올바른 예시 

    public class Phone {
    	public static void main(String[] args) {
    		List<SmartPhone> phoneList = Arrays.asList(new Iphone(),new Gallaxy());
    		Game game = new Game();
    		phoneList.forEach(game::play); //1.정적 Dispatch 
    	}
    }
    
    interface SmartPhone{
    	void game(Game game);
    }
    
    class Iphone implements SmartPhone{
    	@Override
    	public void game(Game game) {
    		System.out.println("play Iphone game " + game);//study.Game@6ce253f1
    		System.out.println("play Iphone this " + this);//study.Iphone@53d8d10a
    		System.out.println("play Iphone this " + this.getClass());//study.Iphone
    		System.out.println("play with Iphone:: " + this.getClass().getSimpleName()); //Iphone
    	}
    }
    
    class Gallaxy implements SmartPhone{
    	@Override
    	public void game(Game game) {
    		System.out.println("play Gallaxy this " + this);//study.Gallaxy@e9e54c2
    		System.out.println("play with Gallaxy:: " + this.getClass().getSimpleName());
    	}
    }
    
    class Game{
    	public void play(SmartPhone phone) {
    		System.out.println("play this  " + this); //study.Game@6ce253f1
    		phone.game(this); //2. 동적 Dispatch
    	}
    }

     

     

     

     

     

     

     

     

     

     

    반응형
Designed by Tistory.