-
상속 , dynamic dispatchBackEnd/자바 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 할 수 없다!
※ 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 } }
반응형'BackEnd > 자바' 카테고리의 다른 글
자바 기초 (패키지, import , classpath, 접근제어자) (0) 2021.01.01 중첩클래스의 쓰임 > 이펙티브 자바 아이템 24 (0) 2020.12.26 프록시 패턴 (0) 2020.12.20 JVM, JRE, JDK, 메모리 (0) 2020.12.14 자바 SOLID 원칙 (0) 2020.12.12