JAVA/공부

클래스 상속과 다형성

GaeGim 2022. 7. 2. 23:45
반응형
  • 상속

부모 클래스의 멤버를 내려받아 자식 클래스 내부에 포함시키는 자바 문법 요소

-> 부모 클래스는 자식 클래스들의 공통적인 특징을 가지고 있는 클래스

 

· 장점

1. 코드의 중복성이 제거된다

2. 클래스의 다형적 표현이 가능하다

 

※ 다형적 표현

1개의 객체를 여러 형태로 표현할 수 있는 특성

 

 

· 문법

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

 

※ 자식 클래스는 부모클래스를 다중 상속 불가

-> 모호성 발생 가능하기 때문에.

(부모 클래스가 여러 자식 클래스를 상속하는 건 무관)

 

 

· 상속 시 메모리 구조

class A {
    int m;
    void abc() {}
}

class B extends A {
    int n;
    void bcd() {}
}
B b = new B();

자식 클래스 B의 객체를 생성 시 클래스 영역에 선언된 자료형의 클래스와 그 부모 클래스가 모두 로딩된다. 참조변수 b는 B 자료형으로 선언되어 힙 메모리에 있는 B 타입의 객체만 가리킬 수 있게 된다.JVM은 자식 클래스의 객체를 생성할 때 가장 먼저 부모 클래스의 객체를 생성하고 그 후에 자식 클래스에서 추가한 필드, 메서드가 객체에 추가시켜 클래스 B의 전체 객체를 완성시킨다. 자식 클래스 객체 내부엔 부모 클래스 객체가 포함되어 있어 자식 클래스 객체로 부모 클래스의 멤버를 사용할 수 있다.

 

 

· 생성자의 상속 여부

상속은 부모클래스의 멤버를 자식 클래스로 내려받는 것이라고 했다. 멤버는 클래스 내부 4요소 중 생성자를 제외한 것이다. 생성자를 제외하고 상속시키는 이유는 무엇일까?

class A {
    A() {
    }
}

class B extends A {
    A(){}
}

자식 클래스 B에 클래스 A의 생성자를 상속받게 했다.

클래스 내부엔 필드, 메서드, 생성자, 이너 클래스 외엔 다른 것이 올 수 없다고 했다.

 

클래스 B 안에 A(){}의 형태를 보자.

우선 소괄호와 중괄호가 있으니 필드와 이너 클래스는 아니다.

생성자는 클래스명과 동일한 이름을 가져야 하는데 그렇지도 않아 생성자도 아니다.

그럼 메서드일까? 그렇다기엔 리턴 타입이 없어 메소드로 볼 수도 없다.

따라서 부모 클래스의 생성자를 상속받는다면 상속과 동시에 오류가 발생할 것이다.

 

 

· 객체의 다형적 표현

A a = new A();	//A 생성자로 만든 A객체(new A())는 A 자료형(A a) -> A는 A이다.
A ab = new B();	//B는 A이다.

 

 

· 객체 타입 변환

업캐스팅 : 범위가 좁은 쪽에서 넓은 쪽으로의 캐스팅 (자식클래스에서 부모클래스로 변환)

다운 캐스팅 : 범위가 넓은 쪽에서 좁은 쪽으로의 캐스팅 (부모클래스에서 자식클래스로 변환)

 

기본 자료형과 달리 객체는 항상 업캐스팅할 수 있어 명시하지 않아도 컴파일러가 대신 넣어준다. 하지만 다운 캐스팅은 개발자가 직접 명시적으로 넣어줘야 한다. 다운캐스팅은 잘못 사용하면 ClassCastException 예외를 발생시킬 수 있다.

 

 

왜 업캐스팅은 항상 되고 다운캐스팅은 안 될 수도 있을까?

범위가 좁은 부모클래스는 자식클래스들의 공통적인 특징을 가지고 있다고 언급했다. 따라서 범위가 좁은 자식클래스에서 서 부모클래스로 변환하는 것은 자식클래스라면 가지고 있는 부모클래스의 공통 특징을 남기면 되기때문에 타입 변환에 문제가 되지 않는다.

하지만 다운캐스팅은 부모클래스에서 자식클래스로의 타입변환이다. 자식클래스는 부모클래스의 공통 특성에 자신만의 필드, 메서드를 추가시킨 것이기 때문에, 부모클래스엔 없는 특성들이 있을 수 있는 것이다. 그래서 변환이 될 수도 있고 안될 수도 있는 것이다.

 

class A {}
class B extends A {}
class C extends B{}

//자동 타입 변환
B b1 = new B();
A a1 = (A) b1;	//(A)를 컴파일러가 자동 추가 -> B()생성자로 A타입으로 업캐스팅
C c1 = new C();
B b2 = (B) c1;	//(B)를 컴파일러가 자동 추가 -> C()생성자로 B타입으로 업캐스팅
A a2 = (A) c1;	//(C)를 컴파일러가 자동 추가 -> C()생성자로 A타입으로 업캐스팅

//수동 타입 변환
A a1 = new A();
B b1 = (B) a1;	//예외 발생 가능성o -> A()생성자로 만든 A타입을 B타입으로 다운캐스팅
A a2 = (B) a2;	//정상 작동 -> B()생성자로 만든 A타입을 B타입으로 다운캐스팅 -> 객체가 B타입
C c2 = (C) a2;	//예외 발생 가능성o -> B()생성자로 만든 A타입을 C타입으로 다운캐스팅

캐스팅의 가능 여부는 어떤 타입으로 선언된 지가 중요한 게 아니라 어떤 생성자로 생성됐는 지가 관건이다.

 

 

 

· 다운 캐스팅

부모 클래스 A가 있고 그의 자식 클래스 B가 있다고 생각해보자.

A a = new B();

실제 객체는 B() 생성자로 만들었다는 것을 알 수 있다. 

자식클래스의 생성자를 호출하면 부모클래스 객체 먼저 생성된다고 했다. A객체가 먼저 메모리에 만들어지고 이후 B객체가 만들어질 것이다. B객체가 A객체를 품고 있는 것처럼 생각하면 된다.

 

선언된 타입이 의미하는 것은 실제 객체에서 자신이 선언된 타입의 객체를 가리키게 되는 것이다.

B b = (B) a;	//O -> 1행
C c = (C) a;	//X -> 2행

1행은 A타입의 a를 B타입으로 캐스팅해서 B타입으로 저장하려고 하는 것이다. (B) a는 B객체를 가리켜야 하고 힙 메모리에는 이미 B객체가 존재하므로 B타입을 가리키는 것은 마땅하다.

2행은 C타입으로 캐스팅해서 C타입으로 저장하려 하는 것인데, 힙 메모리에는 C객체가 생성된 적이 없어 다운캐스팅이 불가하다.

-> 이와 같은 이유때문에 캐스팅의 여부는 생성자의 종류가 중요하다는 것이다.

 

 

 

· 캐스팅 가능 여부를 확인해주는 instanceof 키워드

캐스팅 가능 여부를 파악하기 위해선 실제 객체를 어떤 생성자로 만들었는지와 클래스 간 상속 관계를 알아야 하지만 이것들을 다 따지는 건 번거로운 일이다.. 그래서 자바는 캐스팅 가능 여부를 불리언 타입으로 확인할 수 있는 문법 요소를 제공하는데, 바로 instanceof 키워드다. (true면 가능, false면 불가능)

 

문법)

참조변수 instanceof 타입

여기서의 타입은 참조변수가 표현될 수 있는 모든 다형적 타입을 의미한다.

 

 

 

  • 메서드 오버라이딩

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

 

조건)

1. 부모 클래스의 메서드와 시그너처 및 리턴 타입이 동일해야 한다.

2. 부모 클래스의 메서드보다 접근 지정자의 범위가 같거나 넓어야 한다.

 

자식 클래스의 생성자가 호출되면 부모 클래스의 객체가 힙 메모리에 먼저 생성될 텐데 이 과정에서 부모 클래스의 객체 내 메서드가 메서드 영역에 생성될 것이다. 이후 자식 클래스 객체가 생성되는데 동일한 이름, 리턴타입, 시그니처를 가진 메서드가 있는 상태다. 하지만 이미 같은 이름의 부모클래스의 메서드가 존재하고 있는데 이때 자식 클래스의 메서드가 이걸 덮어쓰기 즉, 오버라이딩하는 것이다. 자식 클래스 객체로 메서드를 실행하면 오버라이딩된 자식클래스의 메서드가 실행될 것이다.

 

※ 오버라이딩한다고 부모클래스의 메서드가 사라지는 것은 아니다. 존재하지만 자식클래스의 메서드 밑에 깔려 있는 것이므로 원한다면 부모클래스의 메서드를 호출할 수도 있다.

 

※ 동일한 필드나 메서드가 있을 땐 참조변수가 가리키는 객체의 바깥쪽부터 안쪽으로 들아가며 만나는 첫 멤버가 실행된다.

 

class A {
    void print() {
    	System.out.println("A클래스");
    }
}

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


A ab = new B();
ab.print();	//오버라이딩돼서 B클래스 출력됨

 

 

 

· 오버라이딩과 오버로딩

오버로딩은 이름은 동일하지만 시그니처가 다른 여러 개의 메서드를 같은 공간에 정의하는 것

오버라이딩은 파일명과 확장명이 완벽히 동일한 파일을 같은 공간에 복사하는 것

 

자식클래스는 오버라이딩 대상이 된 부모 클래스의 메서드를 제외한 메서드를 사용 가능하다.

 

 

 

  • 인스턴스 필드와 정적 멤버의 중복

인스턴스 필드나 정적 멤버(필드, 메서드)는 자식클래스에서 동일명으로 정의돼도 오버라이딩되지 않는다.

 

인스턴스 필드의 경우, 이름이 중복되더라도 객체 내 각각의 공간 속에 완변히 분리되어 저장되기 때문에 오버라이딩이 발생하지 않는다. 

정적 필드의 경우, 동일한 이름이여도 각각의 클래스 내에 포함된 정적 필드 저장 공간이 완벽 분리되어 오버라이딩이 발생하지 않는다.

정적 메서드의 경우, 정적 필드와 마찬가지로 동일한 이름이여도 각자의 클래스 내부에 분리되어 존재하기 때문에 오버라이딩 발생하지 않는다.

 

※ 메서드의 경우는 객체 내 메서드 위치를 저장하는 공간은 분리되어 있지만 실제 메서드가 저장되는 공간은 인스턴스 메서드 영역의 한 곳이므로 오버라이딩이 발생하게 된다.

 

 

 

* 값을 읽을 때의 기준

타입 생성자
인스턴스 필드
정적 필드
정적 메서드
인스턴스 메서드

 

 

 

  • super 키워드와 super() 메서드

클래스 자신의 내부 구성 요소를 호출하는 문법 요소. 상속 관계에서만 사용 가능.

 

this : 자기 객체를 가리키는 참조변수명으로 인스턴스 메서드 내부에서 필드를 사용하거나 메서드 호출 시 참조 변수 명으로 사용하고 생략 시 컴파일러가 자동 추가

this() : 자신의 또다른 생성자를 호출하고 생성자 내에서만 사용할 수 있으며 항상 첫 줄에 위치

 

super : 부모클래스의 객체를 가리켜 필드명의 중복, 메서드 오버라이딩으로 가려진 부모 필드, 메서드를 호출하기 위해 사용

super() : 부모클래스의 생성자를 호출, 생성자 내부에서만 사용 가능하며 항상 첫줄에 위치.

->첫줄에 와서 자식클래스보다 부모클래스 객체 먼저 만들도록 해줌

 

※ 모든 생성자의 첫 줄엔 this()나 super()가 와야 한다. 아무것도 명시하지 않으면 컴파일러가 super() 삽입해준다. 이는 생성자를 호출하면 항상 부모클래스의 생성자가 한번 호출된다는 의미가 되고 최종적으로는 자식 클래스의 생성자로 객체 생성 시 부모 클래스의 객체가 만들어질 수 있는 이유가 된다.

 

 

 

 

  • 최상위 클래스 Object

컴파일러는 아무런 클래스로 상속하지 않으면 자동으로 extends Object를 삽입해 Object 클래스를 상속한다. 자바의 모든 클래스는 어떤 객체를 만들든 Object 타입으로 선언할 수 있다.

 

 

· 대표적인 Object 클래스 메서드

- toString() : 객체 정보를 문자열로 출력 (객체 정보 : 패키지명.클래스명@해시코드)

*해시코드 : 객체가 저장된 위치와 관련된 고유한 값

 

- equals(Object obj) : 스택 메모리의 값 비교

입력매개변수로 넘어온 객체와 자기 객체의 스택 메모리 변수값을 비교해 맞으면 true, 틀리면 false를 반환하는 메서드. 스택 메모리 값을 비교한다는 의미는 등가 비교 연산(==)과 동일한 기능을 하여 기본 자료형일 때 값을 비교하고, 참조 자료형일 때 객체의 위치값을 비교한다는 것이다. 참조 자료형의 실제 데이터 값을 비교하고 싶다면 equals() 메서드를 오버라이딩하면 된다.

 

- hashCode() : 객체의 위치와 관련된 값. 실 위치 값이 아니다

일반적으로 두 객체의 내용을 비교할 땐 오버라이딩한 equals() 메서드만으로도 충분하지만 HashTable, HashMap 등에서 동등비교할 땐 hashCode()까지 오버라이딩해야 한다.

 

 

반응형

'JAVA > 공부' 카테고리의 다른 글

이너 클래스와 이너 인터페이스  (0) 2022.07.11
자바 제어자-2  (0) 2022.07.02
자바 제어자-1  (0) 2022.07.02
클래스 외부 구성 요소  (0) 2022.07.02
클래스 내부 구성 요소  (0) 2022.07.02