[JAVA] 05. 상속 (Inheritance)

gogori6565·2022년 10월 5일
0

JAVA

목록 보기
9/11

📌 상속 (inheritance)

부모 클래스에 만들어진 필드, 메소드를 자식클래스가 물려받음. 동일 특성을 재정의할 필요 없어 자식 클래스 간결해짐.

  • 상속의 장점
    1) 클래스 간결화
    2) 클래스 관리 용이 - 계층적 분류
    3) 소프트웨어 생산성 향상 - 클래스 재사용-확장 용이, 새 클래스 작성 빠름

자바의 상속 선언 - extends

public class Person{
}

public class Student extends Person { //Person을 상속받는 클래스 Student
}

public class StudentWorker extends Student{ //Student를 상속받는 StudentWorker
}

extends 키워드 사용 ★

자바 상속의 특징

  1. 클래스의 다중 상속 지원 X
    -> 다중 상속? 부모가 2개 이상
  2. 상속 횟수 무제한
  3. 상속 최상위 조상 클래스는 java.lang.Object 클래스

상속과 접근 지정자

  1. 슈퍼 클래스의 public : 모든 클래스 접근 허용
  2. 슈퍼 클래스의 디폴트 : 패키지 내 모든 클래스에 접근 허용
  3. 슈퍼 클래스의 protect : 같은 패키지 내 모든 클래스 접근 허용, 다른 패키지에 있어도 서브 클래스는 슈퍼 클래스의 protected 멤버 접근 가능
  4. 슈퍼 클래스의 private : 다른 모든 클래스에 접근 불허, 클래스 내 멤버들에게만 허용


📌 서브 클래스 / 슈퍼 클래스

생성자 호출 및 실행

Q. 서브 클래스 객체가 생성될 때, 서브와 슈퍼 클래스의 생성자가 모두 실행되는가?
A. Yes. 둘 다 실행됨. 서브 클래스 객체 생성 시 이 객체에 서브 클래스 멤버와 슈퍼 클래스 멤버가 모두 들어있다.

Q. 서브 클래스의 생성자와 슈퍼 클래스의 생성자 중 누가 먼저 실행되는가?
A. 슈퍼 클래스 -> 서브 클래스 순서로 실행된다.
호출 순서 : 서브 클래스의 생성자가 먼저 호출 -> 서브 클래스의 생성자가 실행 전 슈퍼 클래스 생성자 호출 -> 슈퍼 클래스 생성자 실행 -> 서브 클래스 생성자 실행

서브 클래스에서 슈퍼 클래스의 생성자 선택

  • 서브 클래스 생성자 작성 원칙
    서브 클래스 생성자에서 슈퍼 클래스 생성자 하나 선택
  • 서브 클래스에서 슈퍼 클래스의 생성자를 선택하지 않는 경우
    컴파일러가 자동으로 슈퍼 클래스의 기본 생성자 선택 (이 경우, 기본 생성자가 없다면 오류 발생)
  • 서브 클래스에서 슈퍼 클래스의 생성자를 선택하는 방법
    super() 이용

super() - 명시적으로 슈퍼 클래스 생성자 선택

  • 인자를 이용해 슈퍼 클래스의 생성자 호출
  • 반드시 서브 클래스 생성자 코드의 제일 첫 라인에 와야함

업캐스팅 (upcasting)

: 서브 클래스 객체를 슈퍼 클래스 타입으로 타입 변환

  • 업캐스팅을 하고 메소드를 실행할 때, 만일 자식 클래스에서 오버라이딩한 메서드가 있을 경우, 부모 클래스의 메서드가 아닌 오버라이딩 된 메서드가 실행된다!!
class Person {}
class Student extends Person {}

Student s = new Student();
Person p = s; // 업캐스팅, 자동타입변환
  • 업캐스팅된 레퍼런스
    : 객체 내에 슈퍼 클래스의 멤버만 접근 가능
    -> 주의) 업캐스팅한 경우 멤버 갯수가 감소로 인한 멤버 접근 제한이 존재함!

설명))
그냥 슈퍼클래스를 쓴다는 게 아니라

Person p;
Student s = new Student("이재문");
p=s; //업캐스팅

이게 레퍼런스 p가 Student 객체의 멤버 중에서 그 슈퍼클래스인 Person의 멤버만 접근한다는 의미!!
"Student 객체 멤버 중 슈퍼클래스 멤버만 접근"이란 의미가 중요

그래서 업캐스팅 왜 하는데?

다양한 자식 객체들을 부모의 타입으로 묶어서 관리할 수 있다!

Square s = new Square();
Triangle t = new Triangle();
Circle c = new Circle();

//업 캐스팅
Shape[] shapes = {s, t, c};

각각 다른 타입이었던 객체들을 상속 관계를 맺어 부모 클래스(Shape)로 업캐스팅이 가능하다면 하나의 타입으로 묶어 배열을 구성할 수 있다!

그래서 묶어서 뭐할 건데?
만약 각 도형의 넓이를 구하고 싶을 경우, 하나의 도형의 넓이를 구할 때는 큰 문제가 되지 않지만 정사각형, 삼각형, 원 모든 도형의 넓이를 구하고 싶을 경우 넓이를 구하는 공식이 다르기 때문에

s.area();
t.area();
c.area();

이렇게 각 메서드를 호출해줘야 하는 번거로움이 발생한다.
이 경우 코드량도 늘어나고 가독성도 떨어지게 된다.

그러나 부모 클래스에 있는 area() 메서드를 각 자식 클래스에 오버라이딩하여, 업캐스팅된 각 객체가 본인 도형 타입에 맞는 공식을 쓸 수 있게 된다면?
(만일 자식 클래스에서 오버라이딩한 메서드가 있을 경우, 부모 클래스의 메서드가 아닌 오버라이딩 된 메서드가 실행)

for(int i = 0; i < shapes.length; i++){
	shapes[i].area();
}

하나의 자료형으로 관리하므로 코드량도 줄어들고 가독성도 좋아지며 유지보수성도 좋아진다!

이것이 자바에서 업캐스팅과 오버라이딩을 사용하는 이유이자 다형성을 실현하는 방법인 것이다.

다운캐스팅 (downcasting)

업캐스팅을 할 경우 자식 클래스 내 고유한 메서드 실행은 불가능하다
-> 따라서 업캐스팅한 객체를 다시 자식 클래스 타입으로 되돌리는 다운 캐스팅(downcasting)이 필요하다

: 슈퍼 클래스 객체를 서브 클래스 타입으로 변환. 개발자의 명시적 타입 변환 필요.

  • 다운캐스팅은 캐스팅연산자 괄호를 생략할 수 없다
  • 다운캐스팅의 목적은 업캐스팅한 객체를 다시 자식 클래스 타입의 객체로 되돌리는데 목적을 둔다 (복구)
class Person {...}
class Student extends Person {...}
...
Person p = new Student("이재문"); //업캐스팅
...
Student s = (Student)p; //다운캐스팅, (Student)의 타입 변환 표시 필요

instanceof 연산자와 객체의 타입 변환

업캐스팅된 레퍼런스로 객체의 타입을 판단하기가 어려움 -> 슈퍼 클래스는 여러 서브 클래스에 상속되기 때문

instanceof 연산자
레퍼런스가 가리키는 객체의 타입 식별을 위해 사용

객체레퍼런스 instanceof 클래스타입

연산 결과 : true/false의 불린 값


메소드 오버라이딩 (Method Overriding)

슈퍼 클래스의 메소드를 서브 클래스에서 재정의 (슈퍼 클래스의 메소드 이름, 매개변수 타입과 개수, 리턴 타입 등 모든 것을 동일하게 작성)

<동적 바인딩 발생!>
서브 클래스에 오버라이딩된 메소드가 무조건 실행되는 동적바인딩 (슈퍼 클래스 메소드가 무시, 덮어쓰기로 번역되기도 함)

  • 업캐스팅된 변수도 슈퍼가 아닌 서브 클래스의 오버라이딩 메소드를 실행한다

오버라이딩의 목적 - 다형성 실현

오버라이딩 : 슈퍼 클래스에 선언된 메소드를 각 서브 클래스들이 자신만의 내용으로 새로 구현

  • 상속을 통해 '하나의 인터페이스(같은 이름)에 서로 다른 내용 구현'이라는 객체 지향의 다형성 실현

super 키워드

super : 슈퍼 클래스의 멤버를 접근할 때 사용되는 레퍼런스

  • 슈퍼 클래스의 메소드를 호출

  • 컴파일러는 super의 접근을 정적 바인딩으로 처리
    정적바인딩 : 컴파일 시간에 성격이 결정
    동적바인딩 : 실행 시간(run time)에 성격이 결정

  • super() 메서드 : 부모 클래스의 생성자 호출

    • 자식 클래스의 생성자에서 부모 클래스의 생성자를 호출함
    • 생성자의 첫 줄에서 호출해야함 (why? 자식 클래스의 멤버가 부모 클래스의 멤버를 사용할 수도 있기 때문에 반드시 부모 멤버들이 먼저 초기화)
Student s = new Student("해리", 18, "hogwarts");

class Person {
	protected String name;
    protected int age;
    
    public Person(String name, int age){
    	this.name = name;
        this.age = age;
    }
}

class Student extends Person {
	protected String schoolName;
    
    public Student(String name, int age, String schoolName){
    	super(name, age); //부모 클래스의 생성자 호출
        this.schoolName = schoolName;
    }
}

오버로딩 vs 오버라이딩


추상 메소드 (abstract method)

: 선언되어 있으나 구현되지 않은 메소드, abstract로 선언됨

public abstract String getName();
pubilc abstract void setName(String s);

추상 메소드는 서브 클래스에서 오버라이딩하여 구현해야함

추상 클래스(abstract class)의 2종류

  1. 추상 메소드를 하나라도 가진 클래스 (클래스 앞에 반드시 abstract라고 선언해야함)
  2. 추상 메소드가 하나도 없지만 abstract로 선언된 클래스
//1. 추상 메소드 하나 이상 가진 클래스

abstract class Shape { // 추상 클래스 선언
	public Shape() { }
	public void paint() { draw(); }
	abstract public void draw(); // 추상 메소드
}
//2. 추상 메소드가 없는 클래스

abstract class MyComponent { // 추상 클래스 선언
	String name;
	public void load(String name) {
		this.name = name;
	}
}

★ 추상 클래스는 객체를 생성할 수 없다!★

추상 클래스의 상속

추상 클래스 상속의 2가지 경우

  1. 추상 클래스의 단순 상속 - 서브 클래스도 추상 클래스
    -> 추상 클래스 상속받고 추상 메소드는 구현하지 않은 경우, 서브 클래스도 abstract로 선언해야함

  2. 추상 클래스 구현 상속 - 서브 클래스는 추상 클래스 X
    -> 서브 클래스에서 슈퍼 클래스의 추상 메소드 구현 (오버라이딩), 서브 클래스는 추상 클래스가 아님

추상 클래스의 용도

  1. 설계와 구현 분리
    -> 슈퍼 클래스에서는 개념을 정의하고, 서브 클래스마다 다른 구현이 필요한 메소드는 추상 메소드로 선언
    -> 각 서브 클래스에서 구체적인 행위 구현, 서브 클래스마다 목적에 맞게 서로 다른 추상 메소드를 구현

자바의 인터페이스

클래스가 구현해야 할 메소드들이 선언되는 추상형
-특정 역할에 대한 대략적인 틀만 정의

interface 키워드로 선언함

public interface SerialDriver {...}
  • 인터페이스의 객체 생성 불가 X
  • 인터페이스 타입의 레퍼런스 변수는 선언 가능 O
  • 인터페이스 구현
    인터페이스를 상속받는 클래스는 인터페이스의 모든 추상 메소드를 반드시 구현해야함
  • 인터페이스 상속 및 다중 상속 가능

인터페이스 구현

인터페이스의 추상 메소드를 모두 구현한 클래스 작성

  • implements 키워드 사용
class SamsungPhone implements PhoneInterface { // 인터페이스 구현
	// PhoneInterface의 모든 메소드 구현
	public void sendCall() { System.out.println("띠리리리링"); }
	public void receiveCall() { System.out.println("전화가 왔습니다."); }
    
	// 메소드 추가 작성
	public void flash() { System.out.println("전화기에 불이 켜졌습니다."); }
}

인터페이스 목적

=> 인터페이스는 스펙을 주어 클래스들이 그 기능을 서로 다르게 구현할 수 있도록 하는 클래스의 규격 선언이며, 클래스의 다형성을 실현하는 도구이다.

인터페이스와 업캐스팅

인터페이스를 구현하는 객체는 인터페이스 타입으로 업캐스팅 될 수 있음!

interface AAA(){
	public void aaa();
}

interface BBB(){
	public void bbb();
}

interface CCC(){
	public void ccc();
}

//Alphabet 클래스는 총 3가지 역할(AAA, BBB, CCC)을 수행
class Alphabet implements AAA, BBB, CCC {
	...
}
//Alphabet 클래스의 객체 생성
Alphabet alpha = new Alphabet();

//인터페이스 타입으로 업캐스팅 (즉, alpha 객체를 a라는 인터페이스 타입으로 해석할 수 있다는 의미)
AAA a = alpha;
BBB b = alpha;
CCC c = alpha;

인터페이스 타입으로 해석된 객체는 해당 인터페이스의 메소드만 수행 가능

a.aaa(); //O
b.bbb(); //O
c.ccc(); //O

a.bbb()  //X
c.aaa(); //X

업캐스팅을 사용하는 장점 => "그룹화" 가능!

  • 전혀 다른 객체일지라도 같은 인터페이스를 구현하였다면, 업캐스팅을 통해 그룹화해서 관리 가능
interface Flyable {
	public void fly();
}

class Bird implements Flyable {
	public void fly() {
    	...
    }
}

class Airplane implements Flyable {
	public void fly() {
    	...
    }
}
//다양한 객체 생성
Bird bird = new Bird();
Airplane airplane = new Airplane();

//인터페이스 타입으로 그룹화!
Flyable[] flyanbleThings = { bird, airplane };

+) ArrayList를 통해서도 업캐스팅하여 인터페이스 타입으로 저장 가능

//ArrayList를 통해서도 객체들을 업캐스팅하여 저장 가능
ArrayList<Flyable> flyList = new ArrayList<Flyable>();

list.add(bird);
list.add(airplane);
profile
p(´∇`)q

0개의 댓글