-
[6주차]상속스터디/[whiteship]JAVA 2021. 2. 15. 14:54
목표
자바의 상속에 대해 학습하세요.
학습할 것 (필수)
- 자바 상속의 특징
- super 키워드
- 메소드 오버라이딩
- 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
- 추상 클래스
- final 키워드
- Object 클래스
자바 상속의 특징
- 자바에서 상속이란 어떤 부모클래스에서 정의된 필드, 메소드 같은 속성들을 그 하위 클래스에서 공통된 속성을 상속해주어 사용하게 해주는 개념이다. 하지만 접근 지정자(public, protected, private, default)에 따라 상속받은 클래스에서도 부모에게 물려받은 필드와 메소드에 접근가능 여부가 정해진다.
- 자바에서 다중 상속은 불가능하다.
접근 지정자 같은 패키지의 클래스 다른 패키지의 클래스 같은 패키지 서브 클래스 다른 패키지 서브 클래스 private X X X X default O X O X protected O X O O public O O O O
super 키워드
super() 키워드는 해당 키워드를 통해서 상속받은 클래스에서 슈퍼 클래스(부모 클래스)의 생성자를 명시적으로 호출 할 수 있다.
또한 super키워드는 생성자 구현체의 맨 윗줄에 와야한다.
super 키워드는 슈퍼 클래스의 멤버(필드나 메소드)를 접근할 때 사용한다.
public class Person { int age; String name; // 기본 생성자(default) public Person(String name, int age){ this.age = age; this.name = name; } // age를 초기화하는 생성자 public Person(int age){ this.age = age; } // name을 초기화하는 생성자 public Person(String name){ this.name=name; } @Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
public class Korean extends Person { public Korean(){ super("kimseongJun",20); } @Override public String toString() { System.out.println("this is Korean.toString()"); return super.toString(); } }
public class Test { public static void main(String[] args) { Korean korean = new Korean(); System.out.println(korean); } }
[결과]
메소드 오버라이딩
OOP개념중 다형성의 개념에 사용하는 것으로, 부모 클래스로부터 상속받은 자식 클래스에서 부모 클래스의 메소드를 재정의해 다른 기능을 수행할 수 있도록 해주는 개념이다. 부모 클래스는 일반 클래스가 될수도 있고 추상클래스, 인터페이스도 될 수 있다. 이렇게 구분한 이유는 다음 주제인 추상 클래스와 인터페이스를 읽어보자.
추상 클래스와 인터페이스
- 추상클래스
- 최소한 한 개 이상의 추상 메소드를 가진 클래스로써, 실제 구현체를 가진 메소드를 멤버로 가질 수 있다.
- 공통적인 기능(실제 구현체를 가지는 메소드)을 가지는 메소드를 상속받는 클래스에게 적용하고 나머지 설계(추상 메소드)는 상속받은 클레스에서 구현하고자 할때 추상메소드를 사용하며 클래스 확장 목적으로 사용되며 이 부분이 인터페이스와 차이점이다.
- 미완성된 설계도라 할 수 있다.
- 인터페이스
- 모든 메소드가 추상메소드란 점. 즉 실제 구현체를 가진 메소드가 존재하지 않는다는 것이다. 즉, 추상클래스 처럼 공통된 기능을 하는 메소드가 없을 경우 사용한다.
- 또한 필드 관점에선 static final키워드로 작성해 정적으로 사용하는 필드만 존재한다.
- 인터페이스는 실제로 상속받아 구현해야하는 클래스에서 각각의 추상 메소드 전부를 실제로 구현해야 한다는 원칙이 따른다.
- 추상클래스가 상속을 통해 클래스를 확장을 목적으로 했다면 인터페이스는 같은 기능을 하지만 동작이 다르게 하기위해 사용된다
- 기본설계도라 할 수 있다.
이렇게 상속받은 추상메소드를 구현하거나 원래의 메소드를 다른 기능으로 구현하고 싶을때 사용하는 개념이 오버라이딩이다.
추상클래스와 인터페이스의 이해를 돕기위해 다음과 같은 코드를 작성했다.
설명하자면 사람이 있고 그 사람이 외국 사람일수도, 한국 사람일 수도 있다고 가정하자. 이러한 조건은 국적에 따른 언어만 다를뿐 먹고, 자고, 걷고 같은 활동은 공통적이다. 따라서 공통된 기능을 다시 구현하지 않고 클래스를 확장하기 위해 추상 클래스를 사용하는 것이 적절하다 생각한다.
abstract public class Person { public void run(){ System.out.println("걷는다"); } public void eat(){ System.out.println("먹는다"); } abstract public void say(); }
public class Korean extends Person { @Override public void say() { System.out.println("한국사람은 한국말"); } }
public class Test { public static void main(String[] args) { Korean korean = new Korean(); korean.eat(); korean.run(); korean.say(); } }
반대로 인터페이스의 경우를 생각해보자. 이번에는 사람을 예제로 하지만 공통된 기능 먹고, 자고, 걷는 공통된 기능 없이 영어를 할 수 있는지 없는지 관점에서 보면 사람은 서로 다를것이다.
interface canEnglish { abstract public void english(); }
public class Korean implements canEnglish { @Override public void english() { System.out.println("한국사람이라 못해요..."); } }
public class Foreigner implements canEnglish{ @Override public void english() { System.out.println("영어할수있어요"); } }
public class Test { public static void main(String[] args) { Korean korean = new Korean(); Foreigner foreigner = new Foreigner(); korean.english(); foreigner.english(); } }
다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
Method Dispatch
어떤 메소드를 호출할지 결정하는 메커니즘이다. Method Dispatch에는 static dispatch와 dynamic dispatch가 존재한다.
- Static method dispatch
- 런타임에 해당 메소드 호출이 이루어지지 않아도 컴파일 시점에서 어느 메소드들이 호출되는지 아는 메커니즘이다.
- 컴파일시 바이트코드에도 메소드 호출과 실행 정보가 남아있다.
- Dynamic method dispatch
- 컴파일 시점에 어느 메소드가 호출되고 실행되는지 모르고 런타임 시점에 호출할 메서드를 정하는 메커니즘으로 overriding된 메소드 들이 이 메커니즘으로 인해 다형성 개념을 실현할 수 있게 된다.
- 런타임 시점에서 메소드 호출하는 과정에서 receiver파라미터를 가지고 실제 할당된 객체를 참조해 실행한다.
- 우리가 흔히 사용하는 this키워드에 해당하는 오브젝트가 receiver 파라미터에도 들어가 있다.
- 실제 객체에 대한 값으로 메소드 호출 과정중에 이 receiver 파라미터 값을 활용해 실제 객체를 호출하게 된다.
public abstract class Service { abstract void run(); }
public class youtube extends Service{ @Override void run() { System.out.println("youtube"); } }
public class facebook extends Service{ @Override void run() { System.out.println("facebook"); } }
public class Test { public static void main(String[] args) { facebook facebook = new facebook(); facebook1.run(); youtube youtube = new youtube(); youtube.run(); Service service = new facebook(); service.run(); } }
main 메소드에 facebook, youtube 레퍼런스를 이용해 메소드를 호출하고 실행한 메커니즘은 static method dispatch라고 할 수 있겠다. -> 추상 클래스를 각각 구현한 facebook, youtube클래스를 객체 타입으로 했고 실제 그 타입의 객체를 할당했기 때문에 컴파일 시간에 어떤 메소드를 호출할지 알게된다
하지만 service 레퍼런스의 경우를 보자. 추상클래스를 객체 타입으로 가지고있고 추상클래스 메소드를 호출하고 있다. 당연히 컴파일 시간에 어떤 메소드가 호출될지 결정이 되어있지 않는다. 하지만 실제 실행을 시키면 실제 객체를 할당한 facebook의 run 메소드를 실행하고 있다. 이러한 경우 Dynamic dispatch라고 한다.
Service service = new facebook(); service.run();
-> 실제 객체를 new facebook을 할당했고 런타임 시점에서 메소드 호출시 receiver parameter값을 확인해 실제 객체인 facebook의 메소드를 호출하게 된다.
- Method Signature
- 자바 컴파일러는 메소드의 이름과 파라미터를 이용해 메소드를 구분하는데, 여기서 우리가 흔히 메소드를 정의할 때 사용하는 메소드의 이름 그리고 해당 메소드의 파라미터의 타입, 파라미터의 리스트들을 시그니처라 한다.
- 주의할점은 메소드의 return타입은 시그니처에 해당하지 않는다.
- 자바 컴파일러는 메소드의 이름과 파라미터를 이용해 메소드를 구분하는데, 여기서 우리가 흔히 메소드를 정의할 때 사용하는 메소드의 이름 그리고 해당 메소드의 파라미터의 타입, 파라미터의 리스트들을 시그니처라 한다.
- double dispatch
- dynamic dispatch를 두 번 하는 경우를 말하는데, 아래 소스코드를 보고 이해를 해보자.
import java.util.Arrays; import java.util.List; public class Test { interface service { public void post(SNS sns); } static class Text implements service { @Override public void post(SNS sns) { if (sns instanceof facebook) { System.out.println("text -> " + "facebook"); } if (sns instanceof youtube) { System.out.println("text -> " + "youtube"); } } } static class Picture implements service { @Override public void post(SNS sns) { System.out.println("picutre -> " + sns.getClass().getName()); } } interface SNS { } static class facebook implements SNS { } static class youtube implements SNS { } public static void main(String[] args) { List<service> serviceList = Arrays.asList(new Text(), new Picture()); List<SNS> socialmedia = Arrays.asList(new facebook(), new youtube()); for (service service : serviceList) { for (SNS sns : socialmedia) { service.post(sns); } } } }
결과로썬 없다. 단 instanceof와 if문을 사용했다는 점에 있어서 안티패턴으로 해당한다. 만약 SNS에 여러가지가 존재한다. 인스타그램, 트위터 등등 (youtube는 아니지만 그냥 넘어가주길 바란다.) 이렇게 다양한 트위터를 클래스로 만들고 각각 처리하는 로직을 만든다면? 전부다 if문으로 instanceof로 조건을 걸어 로직을 처리하게 될것이다. 이러한 과정중에 실수는 물론, 로직에 오류가 있음에도 찾기가 굉장히 어려워 질수 있다.
그렇다면 이렇게 해결할수도 있지 않을까??
interface service { public void post(facebook sns); public void post(youtube sns); } static class Text implements service { @Override public void post(facebook sns) { System.out.println("facebook"); } @Override public void post(youtube sns) { System.out.println("yotube"); } } static class Picture implements service { @Override public void post(facebook sns) { System.out.println("facebook"); } @Override public void post(youtube sns) { System.out.println("yotube"); } }
물론 안된다 왜냐면 자바는 상위타입(SNS 인터페이스)이 묵시적으로 하위타입(facebook, youtube)으로 형변환이 되지 않는이유와 자바 특성상 receiver가 한개뿐인 싱글 디스패치란 이유로 다음과 같은 컴파일 오류가 발생한다.
따라서 Double dynamic method dispatch가 이러한 이유에 사용한다.
import java.util.Arrays; import java.util.List; public class Test { interface Service { void post(SNS sns); } static class Text implements Service { public void post(SNS sns) { sns.text(this); } } static class Picture implements Service { public void post(SNS sns) { sns.Picture(this); } } interface SNS { void text(Text service); void Picture(Picture service); } static class facebook implements SNS { public void text(Text service) { System.out.println("text facebook"); } public void Picture(Picture service) {System.out.println("picture facebook"); } } static class youtube implements SNS { public void text(Text service) { System.out.println("text youtube"); } public void Picture(Picture service) { System.out.println("picture youtube"); } } public static void main(String[] args) { List<Service> serviceList = Arrays.asList(new Text(), new Picture()); List<SNS> socialmedia = Arrays.asList(new facebook(), new youtube()); for (Service service : serviceList) { for (SNS sns : socialmedia) { service.post(sns); } } } }
즉 상위위타입에서 하위타입을 받아 사용한 이전 예제와 달리, 상위타입 있는 그대로 받아 컴파일 에러도 발생하지 않는다. 또한 여기서 다형성의 개념을 한 번 더 사용해(sns.text(this)와 같이 오버라이딩 즉 dynamic dispatch가 한번 더 발생) 좀 더 객체지향 프로그램다워졌다. 즉 double dispatch가 발생했다는 얘기다.
여기서 twitter란 SNS 클래스를 추가한다면, service 인터페이스와 구현한 text, pricture 클래스 수정없이 twitter 클래스만 기능을 구현하게된다.
final 키워드
final 키워드를 붙인 필드는 해당 필드를 상수로 (변하지 않 값)으로 사용하겠다는 의미이다.
또한 final키워드를 붙인 멤버들은 보통 프로그램 전체에서 사용하기 때문에 static 키워드와 붙여 사용한다.
final 멤버 그리고 메소드, 클래스가 존재한다 -> 필드에 말고 메소드, 클래스에 쓰인 경우는 못보았지만 그래도 설명하겠다.
final 메소드
해당 메소드를 상속 받으면 Overriding이 불가능하고 그대로 사용하여야 한다. (다형성 개념이 사라짐)
final 클래스
상속을 받을 수도, 해줄수도 없는 클래스이다. 해당 클래스를 다른 사람에게 상속을 불가능하게 하고 싶은 의도가 있을경우 사용한다.
Object 클래스
Object 클래스는 모든 클래스의 최상위 클래스이다.
모든 클래스는 Object클래스의 멤버를 상속받으면 다음과 같은 멤버들이 존재한다.
'스터디 > [whiteship]JAVA' 카테고리의 다른 글
[9주차]예외처리 (0) 2021.11.23 [13주차]IO (0) 2021.02.20 [5주차]클래스 (0) 2021.02.12 [4주차]제어문 (0) 2021.01.26 [3주차]자바가 제공하는 다양한 연산자 (0) 2021.01.23