I 백기선님의 자바 라이브 스터디 6주차 주제 상속에 대해서 포스팅하겠습니다.
상속이란?
상속은 상위 클래스에서 정의한 필드와 메서드를 하위 클래스에서 물려받아 사용할 수 있도록 해주는 기능입니다. 상속을 사용하면 불필요한 중복 코드를 줄일 수 있습니다.
좋은 예시가 될지는 모르겠지만, 카페를 예시로 들어보겠습니다. 커피류들은 커피(샷..?) + 추가 아이템(우유나 캬라멜 시럽등)을 가지겠죠. 그리고 음료를 제조하는 과정(행동 = 메서드)이 필요할겁니다. 커피 종류마다 조금씩은 다르지만 통합할 수 있는 내용들이 있다면 그것들은 부모 클래스에서 상속받아서 사용하는 편이 더 좋더라~ 이 얘기입니다.
class 자식클래스 extends 부모클래스{
}
/*
class 커피{
protected int shot;
public void makeDrink(){
음료 제조
}
}
class 아메리카노 extends 커피{
private String[] items;
public void makeDrink(){
특별 음료 제조
}
}
이야 이거 수도 코드를 짜보니까 예시를 잘못 들었다는 느낌이 팍 오네요.
이야아....
*/
상속의 특징
다중 상속 금지
요즘은 섞어먹는게 유행이라서 커피류 음료랑 티류 음료량 섞고 싶다고 생각해볼게요. 커피랑 티로부터 상속을 받아서 구현하면 되겠지?? 싶어도 자바에서는 이를 허용하지 않습니다.
참고로 모든 클래스들의 부모 클래스(최상위 클래스)는 Object입니다.
또한 접근 제한 지시자들로 인해 부모 클래스에서 상속받지 못하는 항목들도 있으니 유의바랍니다.
ex) private은 같은 패키지에 있어도 물려받지 못하겠죠??
Super 키워드
this 키워드가 클래스 자기 자신을 가리키는 내용이었다면 super는 자신의 부모를 지시합니다. 즉 부모클래스(super 클래스라고도 부릅니다.)의 참조변수라고도 볼 수 있습니다.
super()는 부모 클래스의 생성자를 호출할 수 있습니다. 자바에서 자식 객체를 생성하면 자식 객체만 생성되는 것으로 보이지만 실은 부모 객체도 같이 생성됩니다. 일반 클래스에서 기본 생성자를 다뤘듯이 따로 super() 생성자를 호출하지 않아도 추가로 호출됩니다.
메서드 오버라이딩
부모 클래스가 가지고 있는 메서드를 자식 클래스에서 추가 정의하고 싶을 때 사용됩니다. 같은 이름의 메서드를 자식 버전으로 덮어 씌운다고 해서 오버라이딩이라고 부릅니다.
final 키워드
저번에도 final 키워드를 다룬 느낌이 들지만 수정 될 수 없음을 의미합니다. 필드의 경우 public final String menu = "cake" 라고 해두면 menu의 값은 변경할 수 없습니다.
클래스에서 final의 경우 이 또한 수정이 될 수 없습니다. 클래스 앞에 final을 붙이면 자식 클래스가 상속을 받는 그림을 그릴 수 없습니다.(확장 또한 수정 범위에 포함이 되나 봅니다.)
메서드에 final을 붙이면 메서드 오버라이딩을 할 수 없습니다. 말 그대로 final 최종 버전이라는 느낌이므로 더 이상 손을 댈 수 없도록 막아둔다고 생각하시면 되겠습니다.
추상 클래스
객체를 직접 생성할 수 있는 클래스를 실제 클래스처럼 구현위주가 아닌 공통 분모들을 묶기 위해 만들때 추상 클래스를 사용합니다. 추상 클래스를 위한 키워드는 abstract입니다.
public abstract class Test{}
추상 클래스에서는 똑같이 method 앞에 abstract 키워드를 붙여서 추상 메서드를 만들 수 있습니다. 이 또한 구현부가 없는 형태입니다.
추상 클래스는 직접 인스턴스(객체)를 생성할 수 없다고 합니다. 따라서 new 연산이 불가능합니다.
추상클래스는 is - a "~이다." 자바 책을 보면 한줄 요약으로 이렇게 표현하던데 허헣 그대로 가져와봤습니다. 나중에 is 대신 has - a인 인터페이스가 등장하기도 합니다.
Object 클래스
모든 클래스들의 부모 클래스이며 따로 extends 키워드를 사용하지 않아도 알아서 상속을 받습니다.
따라서 이와 같은 Object 클래스의 메서드들을 상속받아서 사용이 가능하다는 점!!
toString 메서드의 경우 자주 사용되는데, 일반적으로 특정 객체의 모든 내용을 출력하고 싶을때 사용합니다.
public String toString(){
return "shot: " + this.stot + ", item : "+ this.item;
}
이런 느낌으로 toString을 선언해두고 객체 자체를 출력하려고 한다면 지정해둔 출력 방식으로 값을 출력해줍니다. toString을 사용하지 않고 그냥 객체를 출력해보시고 toString을 만들고 출력해서 비교해보세요.
다이나믹 메서드 디스패치
다이나믹 = 런타임, 런타임에 메서드를 매칭시키는 것을 의미합니다.
class Parent{
public void print(){
System.out.println("부모클래스");
}
}
class A extends Parent{
public void print(){
System.out.println("A클래스");
}
}
class B extends Parent{
public void print(){
System.out.println("B클래스");
}
}
public class Main {
public static void main(String[] args) {
Parent p= new Parent();
p.print();
p = new A();
p.print();
p = new B();
p.print();
}
}
p는 맨 처음에 Parent를 가리켰다가 다음에는 A,B를 가리키게 됩니다. 그리고 각각의 print메서드로 매칭되는 이 일련의 과정을 다이나믹 메서드 디스패치라고 부른다고 합니다.
그리고 또 다른데서 살펴본 결과 이 dynamic dispatch에 대한 설명을 전부 인터페이스로만 하시더라구요..?? 그 점에 대해서는 https://leemoono.tistory.com/20 이 분이 실험을 통해 다뤄주셨기 때문에 참고해주세요.
어쨋든 자바는 묵시적으로 항상 호출 객체를 인자로 보내게된다고 합니다. 호출 객체를 인자로 보내기 때문에 this를 이용해 메서드 내부에서 호출 객체를 참조할 수 있고 이것이 dynamic dispatch의 근거가 된다고 합니다.
더블 디스패치
솔직히 이 부분은 여러 글들을 살펴봤지만 온전히 이해할 수 없었습니다. 어떤 분(LichKing님)이 설명을 위해서 작성해주신 코드를 그래도 제 나름대로 이해해보겠습니다.
public class Test {
public static void main(String[] arg) {
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("game play [" +phone.getClass().getSimpleName()+ "]");
}
}
해당 코드에서 Game play가 스마트폰 구현체별로 다르게 구현되어야한다면?? 에서 시작이 됩니다. 다형성을 꼬아서 생각하면 아래와 같은 정답을 얻을 수 있다고 합니다.
interface SmartPhone{
void game(Game game);
}
class Iphone implements SmartPhone{
@Override
public void game(Game game) {
System.out.println("iphone play [" + this.getClass().getSimpleName() + "]");
}
}
class Gallaxy implements SmartPhone{
@Override
public void game(Game game) {
System.out.println("gallaxy play [" + this.getClass().getSimpleName() + "]");
}
}
class Game {
public void play(SmartPhone phone) {
phone.game(this);
}
}
저는 이런게 가능하단걸 생각도 못했네요. 일단 main에서 game의 play()를 찾기 위해 디스패치가 발생할 것이고, phone.game(this)라인에서 동적 디스패치가 또 발생합니다. game을 호출한 객체를 찾으러 가야하기 때문이죠.
그냥 더블 디스패치가 이런 것이구나~ 해도 되지만 위처럼 구현하는 경우에는 다른 구현체가 추가되더라고 Game 클래스 로직 변경은 일어나지 않고 이는 좋은 객체지향 코드를 작성했다고 볼 수 있죠.(OCP 측면에서 확장에는 열려있고 변경에는 닫혀있기 때문입니다.)
이번에는 평상시에 알던 내용에 모르던 내용이 섞여있었네요. 그리고 글 작성시에 예제를 카페로 든 점이 아쉽습니다. 그치만.. 다른 적당한 예시가 떠오르지 않았어요.. 흑..
더블 디스패치 부분 출처 :
https://multifrontgarden.tistory.com/133
참고
'Java' 카테고리의 다른 글
[JAVA] 패키지 (0) | 2021.05.03 |
---|---|
객체지향 프로그래밍이란? (0) | 2021.04.30 |
테스트 코드 작성 범위 (0) | 2021.04.21 |
[JAVA] 클래스 (0) | 2021.04.19 |
[JAVA] LinkedList,Stack.Queue 구현하기 (0) | 2021.04.17 |