Study/JAVA

[JAVA] 10장 - 클래스의 상속과 다형성 (1)

오구민 2022. 10. 3. 23:13

1. 클래스의 상속

1.1 상속의 개념

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

 

1.2 상속의 장점

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

객체를 여러 가지 모양으로 표현할 수 있는 특성을 다형성이라 하는데, 상속을 통해 클래스의 다형적 표현이 가능하다.

 

1.3 상속 문법

클래스를 상속할 때는 extends 키워드를 사용하며, 클래스 명 다음에 'extends 부모 클래스'를 표기한다.

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

  1.3.1 특징

  • 자바의 클래스는 다중 상속이 불가하다. 다중 상속을 허용하면 모호성이 발생하기 때문이다.

 

  1.3.2 예제

package study_001;

class Human {
	String name;
	int age;
	void eat() {}
	void sleep() {}
}
class Student extends Human {
	int studentID;
	void goToSchool() {}
}
class Worker extends Human {
	int workerID;
	void goToWork() {}
}
public class Study_01 {
	public static void main(String[] args) {
		// Human 객체 생성
		Human h = new Human();
		h.name = "김현지";
		h.age = 11;
		h.eat();
		h.sleep();
		
		// Student 객체 생성
		Student s = new Student();
		s.name = "김민성";
		s.age = 16;
		s.studentID = 128;
		s.eat();
		s.sleep();
		s.goToSchool();
		
		// Worker 객체 생성
		Worker w = new Worker();
		w.name = "봉윤정";
		w.age = 45;
		w.workerID = 128;
		w.eat();
		w.sleep();
		w.goToWork();
	}
}

 

1.4 상속할 때의 메모리 구조

객체 생성 시, 힙 메모리에 생성된다.

자바 가상 머신은 자식 클래스의 객체를 생성할 때, 가장 먼저 부모 클래스의 객체를 생성한다.

이후 자식 클래스에서 추가한 필드와 메서드가 객체에 추가됨으로써 클래스 B의 전체 객체가 완성되는 것이다.

즉, 자식 클래스의 객체 내부에는 부모 클래스 객체가 포함돼 있으므로, 자식 클래스 객체에서 부코 클래스의 멤버를 사용할 수 있는 것이다.

 

1.5 생성자의 상속 여부

생성자는 자식 클래스로 상속되지 않는다. 절대 상속돼선 안 된다.

클래스 내부에서는 필드, 메서드, 생성자, 이너 클래스 이외에는 단 한 줄도 올 수 없는데,

상속된 생성자는 소괄호와 중괄호가 있으므로 필드와 이너 클래스는 아니고, 자식 클래스와 이름이 달라 생성자로 볼 수도 없다.

남은 것은 메서드인데 리턴 타입이 없는 경우 메서드도 될 수 없어 오류가 발생한다.

이것이 생성자가 상속되지 않는 이유다.

 

1.6 객체의 다형적 표현

예시를 들어보자. 클래스 A를 상속받아 클래스 B를 생성했다.

이때 A는 A와, B는 A다 모두 가능한 표현이다. 이것을 다형적 표현이라고 할 수 있다.

생성한 객체와 동일한 타입으로 선언하는 것은 물론, 자식 클래스의 객체를 부모 클래스 타입으로 선언하는 것은 다형적 표현의 예다.

화살표를 거스르는 방향이 아니면 된다.

 

  1.6.1 실습

package study_001;

//상속 관계 만들기
class A {}
class B extends A {}
class C extends B {}
class D extends B {}

public class Study_01 {
	public static void main(String[] args) {
		//A 타입의 다형적 표현
		A a1 = new A();
		A a2 = new B();
		A a3 = new C();
		A a4 = new D();
		
		//B 타입의 다형적 표현
		//B1 b1 = new A(); //A는 B다(X)
		B b2 = new B();
		B b3 = new C();
		B b4 = new D();
		
		//C 타입의 다형적 표현
		//C c1 = new A();
		//C c2 = new B();
		C c3 = new C();
		//C c4 = new D();
		
		//D 타입의 다형적 표현
		//D d1 = new A();
		//D d2 = new B();
		//D d3 = new C();
		D d4 = new D();
	}
}

 

 

 

2. 객체의 타입 변환

기본 자료형에서도 언급했듯이 자바 프로그램은 등호를 중심으로 항상 왼쪽과 오른쪽의 자료형이 일치해야 한다.

만일 자료형이 서로 다를 떄는 컴파일러가 자동으로 타입을 변환해주거나 개발자가 직접 명시적으로 타입을 변환해 줘야 한다.

 

2.1 객체의 업캐스팅과 다운캐스팅

  2.1.1 개념

업캐스팅 기본 자료형 범위가 좁은 쪽에서 넓은 쪽으로 캐스팅하는 것
상속 자식 클래스에서 부모 클래스 쪽으로 변환되는 것
다운캐스팅 기본 자료형 범위가 넓은 쪽에서 좁은 쪽으로 캐스팅하는 것
상속 부모 클래스에서 자식 클래스쪽으로 변환되는 것

 

  2.1.2 업캐스팅 시 알아야 할 점

  • 객체는 항상 업캐스팅이 가능하다. 명시적으로 적어주지 않아도 컴파일러가 대신 넣어준다.

 

  2.1.3 다운캐스팅 시 알아야 할 점

  • 다운캐스팅은 개발자가 명시적으로 직접 넣어줘야 한다.
  • 기본 자료형에서 다운캐스팅할 때는 오차가 발생하긴 하지만, 문법적으로는 항상 가능하다.
  • 하지만 객체는 명시적으로 적어 준다고 해도 다운캐스팅 자체가 안 될 때가 있다.
  • 잘못된 다운캐스팅을 수행하면 ClassCastException이라는 예외가 발생하고 프로그램이 종료된다.
  • 캐스팅의 가능 여부는 무슨 타입으로 선언돼 있는지는 중요하지 않으며, 어떤 생성자로 생성됐는지가 중요하다.

 

2.2 메모리로 이해하는 다운캐스팅

메모리에서의 동작만 잘 이해하면 캐스팅의 가능 여부뿐 아니라 선언된 타입에 따른 차이점까지 한 번에 파악할 수 있다.

다운캐스팅을 할 수 있기 위해서는 힙 메모리 내에 해당 객체가 있어야 한다.

 

  2.2.1 예시

  • A ← B ← C 상속 관계에서 A a = new B()라는 코드를 썼다고 하자.
  • 그럼 실제 객체는 B() 생성자로 만들어졌지만, 메모리에 생성된 순서는 A > B일 것이다.
  • B객체 속에 A객체가 품어져 있는 것이다.
  • 근데 이 객체를 A 타입의 참조 변수로 가리키고 있다. 이때 실제 참조 변수는 힙 메모리의 B객체 안에 있는 A객체를 가리킨다.
  • 선언된 타입이 의미하는 바는 실제 객체에서 자신이 선언된 타입의 객체를 가리키게 되는 것이다.

 

  2.2.2 예제

package study_001;

//상속 관계 만들기
class A {}
class B extends A {}
class C extends B {}
class D extends B {}

public class Study_01 {
	public static void main(String[] args) {
		//업캐스팅(자동 변환): 캐스팅 구문을 생략했을 때 컴파일러가 자동으로 추가
		A ac = (A) new C(); // C>A 업캐스팅 (자동 변환)
		B bc = (B) new C(); // C>B 업캐스팅 (자동 변환)
		
		B bb = new B();
		A a = (A) bb; // B>A 업캐스팅 (자동 변환)
		
		//다운캐스팅(수동 변환): 캐스팅할 수 없을 때(실행할 때 예외 발생)
		A aa = new A();
		//B b = (B) aa; A>B 다운캐스팅(수동 변환): 불가능
		//C c = (C) aa; A>C 다운캐스팅(수동 변환): 불가능
		
		B bd = new D();
		D d = (D) bd; // B>D 다운캐스팅(수동 변환)
		
		A ad = new D();
		B b1 = (B) ad; // A>B 다운캐스팅 (수동 변환)
		D d1 = (D) ad; // A>D 다운캐스팅 (수동 변환)
	}
}

 

2.3 선언 타입에 따른 차이점

힙 메모리에 생성되는 객체의 포함관계를 생각하면 쉬운 선언 타입에 따른 차이점이다.

A ← B일 때, B b = new B()로 선언해보자.

그러면 A 객체를 감싸는 B 객체가 만들어지며, B 객체는 A의 메서드와 필드를 모두 사용할 수 있을 것이다.

반대로 A a = new B()를 선언했다고 해보자.

B() 생성자로 객체를 생성했지만, 참조 변수가 A 타입으로 선언되어 B() 내의 필드와 메서드를 사용할 수 없을 것이다.

 

  2.3.1 위의 내용을 코드로 작성했다.

package study_001;

//상속 관계 만들기
class A {
	int m = 3;
	void abc() {
		System.out.println("A 클래스");
	}
}
class B extends A {
	int n = 4;
	void dbc() {
		System.out.println("B 클래스");
	}
}

public class Study_01 {
	public static void main(String[] args) {
		// A타입/A생성자
		A aa = new A();
		System.out.println(aa.m);
		aa.abc();
		System.out.println();
		
		// B타입/B생성자
		B bb = new B();
		System.out.println(bb.m);
		System.out.println(bb.n);
		bb.abc();
		bb.dbc();
		System.out.println();
		
		// A타입/B생성자 : 다형적 표현
		A ab = new B();
		System.out.println(ab.m);
		ab.abc();
	}
}

 

2.4 캐스팅 가능 여부를 확인하는 instanceof 키워드

캐스팅할 수 있는지 확인하려면 실제 객체를 어떤 생성자로 만들었는지와 클래스 사이의 상속 관계를 알아야 한다.

하지만 이를 일일이 확인하는 것은 쉬운 일이 아니고...

자바는 이를 위해 캐스팅 가능 여부를 불리언 타입으로 확인할 수 있는 문법 요소를 제공하고 있다.

그게 바로 instanceof다.

참조 변수 instanceof 타입
// true : 캐스팅 가능 / false : 불가능

이를 사용하면 상속 관계나 객체를 만든 생성자를 직접 확인하지 않고도 캐스팅 가능 여부를 확인할 수 있다.

따라서 잘못된 캐스팅에 따른 실행 예외로 프로그램이 종료되는 것을 방지하기 위해 일반적으로 다운캐스팅을 수행할 때 instanceof를 이용해 캐스팅 가능 여부를 확인하고, 가능한 떄만 캐스팅을 수행한다.

 

  2.4.1 예제

package study_001;

//상속 관계 만들기
class A {}
class B extends A {}

public class Study_01 {
	public static void main(String[] args) {
		//instanceof
		A aa = new A();
		A ab = new B();
		
		System.out.println(aa instanceof A);
		System.out.println(ab instanceof A);
		System.out.println();
		
		System.out.println(aa instanceof B);
		System.out.println(ab instanceof B);
		System.out.println();
		
		if(aa instanceof B) {
			B b = (B) aa;
			System.out.println("aa를 B로 캐스팅했습니다.");
		} else {
			System.out.println("aa는 B타입으로 캐스팅이 불가능합니다.");
		}
		if(ab instanceof B) {
			B b = (B) ab;
			System.out.println("ab를 B로 캐스팅했습니다.");
		} else {
			System.out.println("ab는 B타입으로 캐스팅이 불가능합니다.");
		}
		if("안녕" instanceof String) {
			System.out.println("\"안녕\"은 String 클래스입니다.");
		}
	}
}
//실행 결과
true
true

false
true

aa는 B타입으로 캐스팅이 불가능합니다.
ab를 B로 캐스팅했습니다.
"안녕"은 String 클래스입니다.

 

 

 

 

가끔... 공부가 죽어도 하기 싫은 날이 있다.

오늘이 그랬다... 그냥 놀고 싶었다...

다운캐스팅 업캐스팅 개념이 너무 어려웠고 이해가 되지 않아서 머리를 싸맸다...

 

몇 번이고 오늘은 공부 때려치겠다고 누워놓고는 다시 일어났다...

누웠다 일어났다 좀비가 따로 없었다...

 

책상 앞에 앉으면 그래도 이왕에 한 김에 조금만 더 해야지, 더 해야지 했다.

머리를 비우고 억지로 책을 읽었다.

그랬더니 모르겠던 다운캐스팅 업캐스팅이 이해가 됐다.

 

많이 하진 않았지만 그래도 한 거에 의의를 두기로 했다.

내일은 10장을 끝내자. 내일은 할 수 있을 거야.

오늘은 이래도 내일은 더 잘 하면 되지.