개발저장소

[Java] 상속 본문

Programming Language/Java

[Java] 상속

개발소 2024. 5. 3. 09:12

1. 클래스의 상속

상속(Inheritance)은 부모 클래스의 필드, 메서드, 이너 클래스를 내려 받아 자식 클래스 내부에 포함시키는 자바의 문법요소이다.

 

대학생 클래스와 직장인 클래스가 있다고 가정하자.

 

대학생 클래스에는 이름, 나이, 학번이라는 필드와 먹기, 잠자기, 등교하기라는 메서드가 존재한다.

직장인 클래스에는 이름, 나이, 사번과 먹기, 잠자기, 회사가기라는 메서드가 존재한다.

두 클래스 모두 이름과 나이, 먹기와 잠자기가 공통으로 포함한다.

 

이름과 나이, 먹기와 잠자기를 가진 사람 클래스를 만들어 대학생과 직장인 클래스가 상속받도록 하면, 추가로 필드와 매서드만 구성하면 된다.

 

즉, 부모 클래스는 자식 클래스의 공통적인 특징을 모아 구성한 클래스라고 볼 수 있다.

 

상속의 장점

1. 상속으로 인해 코드의 중복이 제거되었다.

2. 다형적 표현이 가능하다.

 

다형성(Ploymorphism)

'대학생은 대학생이다.' '대학생은 사람이다.' 와 같이 1개의 객체를 여러 가지 모양으로 표현할 수 있는 특성을 말한다.

반대로 '사람은 대학생이다.' 와 같은 예는 성립하지 않는다.

 

 

문법

class 자식 클래스 extends 부모 클래스 {
	...
}

 

클래스를 상속할 때는 extends 키워드를 사용한다. 자바의 클래스는 부모 클래스가 2개 이상인 다중 상속이 불가능하다.

 

모호성(ambiguous)의 오류

class A와 class B를 상속받는 class C가 있다고 하면, A와 B의 데이터는 C에도 당연히 존재한다. C의 데이터는 A와 B 둘 중 하나를 선택해야 하는데, 이때 모호성이 발생한다. 단, 인터페이스를 사용해 다중 상속을 구현해낼 수는 있다.

 

 

상속 시 메모리 구조는 다음과 같다.

class A {
    int m;
    void method1() {...}
}

class B extends A {
    int n;
    void method2() {...}
}

B b = new B();

 

1. 참조 변수 b는 B 클래스로 선언되었지 때문에 힙 영역의 B 인스턴스를 가리킨다.

2. JVM은 자식 클래스의 인스턴스를 생성할 때 부모 클래스의 인스턴스를 먼저 생성한다. (생성자의 super() 때문이다.)

3. 필드는 힙 영역에, 메서드는 클래스 영역의 인스턴스 매서드가 모인 공간에 저장된다.

 

 

2. 타입 변환

업캐스팅 - 자식 클래스에서 부모 클래스 쪽으로 변환되는 것, 자동으로 가능, 서브 타입의 인스턴스를 수퍼 타입의 변수로 참조하는 것

다운캐스팅 - 부모 클래스에서 자식 클래스 쪽으로 변환되는 것, 명시적으로 타입을 지정해야 한다.

 

업캐스팅은 '학생은 사람이다.'와 같은 지극히 당연하고 항상 성립하는 말이다.

하지만 다운캐스팅은 '사람은 학생이다.'와 같은 항상 성립하지는 않는 말을 뜻한다.

 

A < B < C 가 상속 관계인 경우, A a = new A()인 경우 a를 B 타입으로 다운캐스팅 할 수 없다.

하지만 A a = new B()인 경우 a를 B 타입으로 다운캐스팅 할 수 있다.

중요한 것은 무슨 타입으로 선언되었는지가 아니라 어떤 생성자로 생성됐는지다.

 

메모리 상에서 다운캐스팅을 이해할 수 있다.

A a = new B()는 B() 생성자로 만들어졌다. B 같이 상속 받은 클래스로 인스턴스를 생성할 때는, 부모 클래스인 A 인스턴스가 먼저 힙 영역에 생성되고, 이후에 자식 클래스 B 인스턴스가 생성된다. 하지만 실제 참조 변수 a는 A 클래스 타입이므로 힙 영역의 A 클래스를 가리키게 된다. 선언된 타입은 실제 인스턴스에서 선언된 타입을 가리키게 되는 것이다.

 

반대로 C c = (C) a와 같이 C 클래스 타입으로 캐스팅이 불가능한 이유도 설명이 가능하다. 힙 영역에는 A를 상속한 B 인스턴스만 존재하기 때문이다. C 클래스 타입은 C 인스턴스를 참조해야 하는데, 메모리에는 C 인스턴스가 존재하지 않기 때문에 다운캐스팅이 불가능하다. 다운캐스팅을 할 수 있기 위해서는 힙 영역에 해당 인스턴스가 존재해야 하기 때문이다.

 

A a = new B()인 경우, 참조 변수 a는 A 인스턴스를 가리키고 있기 때문에, B 클래스에서 정의된 필드나 메서드에는 접근이 불가능하다.

 

캐스팅이 가능한지 여부를 판별하고 싶다면 instanceof 키워드를 사용하는 것이 좋다.

// A < B < C
C c = new C();
System.out.println(c intstanceof A);	// true
System.out.println(c intstanceof B);	// true
System.out.println(c intstanceof C);	// true

 

c는 C 타입으로 선언되었지만, 다형적 표현 방법에 의해 A와 B 타입으로도 선언될 수 있다는 것을 알 수 있다.

 

 

 

3. 메서드 오버라이딩

메서드 오버라이딩이란 부모 클래스에게 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것이다.

  • 부모 클래스의 메서드와 이름, 입력매개변수의 타입과 개수 및 리턴 타입이 동일
  • 부모 클래스의 메서드보다 접근 제한자의 범위가 같거나 넓어야 한다.
class A {
    void print() {
        System.out.println("A");
    }
}

class B extends A {
    void print() {
        System.out.println("B");
    }
}

 

A  aa = new A(); 의 경우 A() 생성자를 사용해 인스턴스를 생성하고, A 타입 변수로 참조하였다.

A 인스턴스에는 print() 메서드의 정보가 저장되고, 인스턴스 메서드 영역에 저장된다. 생성된 인스턴스는 A뿐이니, aa.print()의 결과는 당연히 "A"이다.

 

B bb = new B(); 의 경우 B() 생성자와 B 타입 참조 변수를 사용하였다. 자식 클래스의 생성자가 사용될 때는 생략된 super()가 동작하여 부모 클래스의 생성자가 먼저 호출된다. 즉, 인스턴스가 생성되는 순서는 A가 먼저인 것이다. A의 print()가 선언된 후, B() print()가 덮어써지기 때문에 bb.print()는 "B"가 된다.

 

A ab = new B(); 의 경우 B() 생성자와 A 타입 참조 변수를 사용한다. 참조 변수가 가리키는 것은 당연히 A 인스턴스일 것이지만, B 인스턴스도 생성되며 print()는 B의 것으로 덮어써졌다. 그렇기 때문에 ab.print()는 "B"가 되는 것이다.

 

※ 메서드 오버라이딩 사용 이유

다형적 표현을 위해 사용한다. 부모 클래스의 메서드를 자식 클래스가 오버라이딩하며 자식 클래스의 스타일에 맞게 재정의할 수 있다.

 

vs 메서드 오버로딩

메서드 오버라이딩과 오버로딩을 혼동할 수 있다. 오버로딩은 이름이 동일하지만 매개변수가 다른 여러 메서드를 같은 공간에 정의하는 것이다. 상속 받은 메서드에 덮어 씌어진다면 오버라이딩, 같이 사용할 수 있다면 오버로딩이다.

 

 

인스턴스 필드와 정적 필드, 메서드

 

인스턴스 필드의 경우

class A {
    int m = 3;
}

class b extends A {
    int m = 4;
}

 

A ab = new B(); 의 경우, ab.m은 3이 된다. 참조 변수 ab가 A 인스턴스를 가리키고 있기 때문이다.

 

정적 필드와 메서드의 경우

class A {
    static int m = 3;
    
    static void print() {
        System.out.println("A");
    }
}

class B extends A {
    static int m = 4;
    
    static void print() {
        System.out.println("B");
    }
}

정적 필드는 인스턴스를 생성하지 않고도 사용할 수 있다. 클래스 내에서 정적 필드는 저장 공간이 분리되기 때문에 오버라이딩이 발생하지 않는다. 정적 메서드 또한 다른 영역에 저장된다. 마찬가지로 오버라이딩이 발생하지 않는다.

 

즉, 오버라이딩은 같은 공간에 저장되는 인스턴스 메서드만 일어나고,

정적 필드, 정적 메서드 등은 각 클래스 내에 저장되기 때문에 오버라이딩이 일어나지 않는다.

 

 

4. Object 클래스

Object 클래스는 모든 클래스의 최상위에 위치한다. 만약 아무런 클래스를 상속하지 않는다면 자동으로 extends Object를 상속한다. 그렇기 때문에 모든 클래스는 Object 클래스가 제공하는 메서드를 사용할 수 있다.

반환 타입 매서드명 주요 내용
String toString() Object 객체의 정보를 출력
일반적으로 오버라이딩 해서 사용
boolean equals(Object obj) 비교 연산자 ==와 비슷한 결과,
메모리 값을 비교한다.
int  hashCode() 객체의 HashCode() 값 리턴
위치를 기반으로 생성되는 고유값이다.
void wait()
wait(long timeout)
wait(long timeout, int nanos)
현재 스레드 일시정지 상태로 전환
동기화 블록에서만 사용 가능
void notify()
notifyAll()
일시정지 상태의 스레드를 해제
동기화 블록에서만 사용 가능

 

 

 

'Programming Language > Java' 카테고리의 다른 글

[Java] 이너 클래스와 이너 인터페이스  (0) 2024.05.09
[Java] 추상 클래스와 인터페이스  (0) 2024.05.08
[Java] 클래스 - 2  (0) 2024.04.30
[Java] 클래스 - 1  (0) 2024.04.26
[Java] 제어문  (0) 2024.04.10