업 캐스팅
부모타입에 자식 생성자
Parent P = new Child();
- 자식 클래스 객체를 부모 타입의 참조 변수로 가리키는 것
- 자동 형변환 (명시적 캐스팅 필요 없음)
- 자바에서 다형성을 구현하기 위한 핵심 개념
🔎 자바에서 업캐스팅을 왜 사용할까 ? (핵심 포인트 5가지)
1️⃣ 다형성(Polymorphism) 구현
업캐스팅의 가장 중요한 목적
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.speak(); // 멍멍
a2.speak(); // 야옹
- 부모 타입으로 다양한 자식 객체를 담을 수 있음
- 동일한 메서드 호출이지만, 각 객체에 맞는 동작 실행됨 → 오버라이딩 + 동적 바인딩
2️⃣ 공통된 인터페이스/타입으로 묶어서 처리 가능
컬렉션, 배열 등에서 매우 유용
List<Animal> animals = List.of(new Dog(), new Cat(), new Tiger());
for (Animal a : animals) {
a.speak(); // 각 동물에 맞게 동작
}
- 부모 타입으로 묶으면 하나의 for문, 하나의 메서드로 유연한 처리 가능
3️⃣ 코드의 유연성 / 유지보수성 향상
- 자식 클래스가 추가되어도 기존 코드를 수정할 필요 없음
- 예외 상황만 자식에서 오버라이딩하면 됨
public void printAnimalSound(Animal animal) {
animal.speak(); // 어떤 동물인지 몰라도 동작 가능
}
4️⃣ 인터페이스 기반 설계에 적합
Runnable task = new MyTask();
Thread t = new Thread(task);
자식 클래스가 Runnable 구현 → 업캐스팅하여 인터페이스 기반으로 제어 가능
✅ 이는 전략 패턴, 템플릿 메서드 패턴 등 객체지향 설계에도 활용된다.
5️⃣ 라이브러리/프레임워크 연동시 필수적
- 예: 스프링 프레임워크에서 Controller, Repository 같은 타입 주입
- 다형성 없이는 스프링 DI(의존성 주입) 자체가 어려움
✅ 업캐스팅이 작동하는 이유: "동적 바인딩"
Parent p = new Child();
p.sayHello(); // 실제로는 Child의 sayHello() 실행됨
- 자바는 실행 시점에 실제 객체의 메서드를 찾아 실행 (→ 동적 바인딩)
- 그래서 부모 타입으로 참조해도 자식의 오버라이딩 메서드가 실행됨
✅ 업캐스팅 시 제한 사항
항목 설명
호출 가능 | 부모 클래스에 정의된 멤버만 접근 가능 |
호출 결과 | 자식이 오버라이딩했다면 자식 메서드 실행 |
자식 고유 기능 사용 | ❌ → 다운캐스팅 필요 |
Animal a = new Dog();
a.bark(); // ❌ 컴파일 에러 (Animal에는 없음)
🔎 자바에서 생성자 호출 순서에 대해 알아보자
🖊️
자바는 자식 클래스를 생성하면 부모 클래스 생성자를 먼저 방문하고, 그 다음에 자식 클래스 생성자를 방문한다.
하지만 파이썬의 경우, 자식 클래스를 생성하면 자식 클래스의 생성자만 방문한다 !!!
✅ 자바 상속에서 생성자의 기본 개념
- 자바에서 생성자(Constructor)는 객체가 생성될 때 호출되어 초기화 작업을 수행하는 메서드다.
- 상속이란, 부모 클래스(상위 클래스)의 멤버(필드, 메서드)를 자식 클래스(하위 클래스)가 물려받는 개념
- 하지만! 생성자는 상속되지 않는다. 대신, 자식 클래스 생성자에서 부모 클래스의 생성자를 명시적으로 호출할 수 있다.
✅ 기본 규칙
- 모든 자식 클래스의 생성자에서는 반드시 부모 클래스의 생성자가 먼저 호출되어야 한다.
- 자바는 생성자 내부 첫 줄에서 super()를 자동으로 삽입한다.
- 부모 클래스에 기본 생성자(매개변수 없는 생성자)가 없다면, 자식 클래스에서 super(...)로 명시적으로 호출해야 한다.
- 부모 클래스에 기본 생성자가 없는 경우, 자식 클래스에서 컴파일 오류가 발생한다.
✅ 예제 코드로 이해해보기
// 부모 클래스
class Parent {
Parent() {
System.out.println("부모 클래스 생성자");
}
}
// 자식 클래스
class Child extends Parent {
Child() {
// super(); ← 생략되어 있음
System.out.println("자식 클래스 생성자");
}
}
출력 결과:
부모 클래스 생성자
자식 클래스 생성자
✅ 부모 생성자에 매개변수가 있는 경우
class Parent {
Parent(String msg) {
System.out.println("부모: " + msg);
}
}
class Child extends Parent {
Child() {
super("Hello"); // 반드시 명시적으로 호출
System.out.println("자식 클래스 생성자");
}
}
출력 결과:
부모: Hello
자식 클래스 생성자
핵심 정리
항목 설명
생성자 상속 여부 | 생성자는 상속되지 않음 |
super() 호출 시점 | 자식 생성자의 첫 줄에서만 호출 가능 |
기본 생성자 없을 때 | 자식 클래스에서 반드시 super(...)로 부모 생성자 호출 필요 |
생성자 역할 | 객체 생성 시 필드 초기화 등 수행 |
📌 업 캐스팅의 원칙
- 자식에 이름이 같은 메서드가 있으면 우선 작동한다.
- 자식에 super() 가 있으면 부모의 그것을 호출한다.
- 부모에 자식과 같은 메서드가 있으면 자식을 호출한다.
- (생성자가 있다면 ?) 자식이 태어날 때는 부모가 먼저 태어나고 태어난다.
📌 자식 생성자에서 부모의 메서드 호출
- B가 태어날 때 A의 생성자 먼저 호출
- A가 태어나고 보니 B의 생성ㅈ에서 부모의 paint() 호출
- 부모 paint를 보니 A를 출력 후에 draw() 호출
- draw() 는 자식에게 있으므로 오버라이딩 된 자식 것을 호출
public class MainClass {
public static void main(String[] args) {
A b = new B(); // 업캐스팅
b.paint(); // 호출 1
b.draw(); // 호출 2
}
}
class A {
public A() {
System.out.println("Constructor of A");
}
public void paint() {
System.out.print("A");
}
public void draw() {
System.out.print("B");
draw();
}
}
class B extends A {
public B() {
super.paint();
}
public void paint() {
System.out.print("C");
}
public void draw() {
System.out.print("D");
}
}
정답
Constructor of A
ADCD
📌 자식 생성자에 오버로딩을 걸면 ?
- B가 태어날 때 이번에는 변수를 받음 → 오버로딩 상태
- B 생성자에서 파라미터가 있는 B(int i) 호출
- B는 태어나기 전에 부모의 기본 생성자(매개변수 없는) 호출
- 자식에게 파라미터가 있었음에도 부모는 기본생성자만 호출
public class MainClass {
public static void main(String[] args) {
A b = new B(1);
b.paint();
b.draw();
}
}
class A {
public A() {
System.out.println("생성자 of A");
}
public A(int i) {
System.out.println("생성자 AA + " + i);
}
public void paint() {
System.out.print("A");
draw();
}
public void draw() {
System.out.print("B");
draw(); // 재귀 호출 위험 있음
}
}
class B extends A {
public B() {
super.paint();
}
public B(int i) {
System.out.println("생성자 BB" + i);
}
public void paint() {
System.out.print("C");
}
public void draw() {
Systm.out.print("D");
}
}
정답 :
생성자 of A
생성자 BB1
CD
📌 자식 생성자에 오버로딩을 걸면 ?
- B가 태어날 때 이번에는 변수를 받음 → 오버로딩 상태
- B 생성자에서 파라미터가 있는 B(int i) 호출
- B 생성자에 가봤더니 부모에 숫자를 넣어 명시적으로 생성자 호출
- 부모 파라미터 생성자도 나오고, 자식 파라미터 생성자도 나옴
public class MainClass {
public static void main(String[] args) {
A b = new B(1);
b.paint();
b.draw();
}
}
class A {
public A() {
System.out.println("생성자 of A");
}
public A(int i) {
System.out.println("생성자 of AA" + i);
}
public void paint() {
System.out.print("A");
draw();
}
public void draw() {
System.out.print("B");
draw();
}
}
class B extends A {
public B() {
super.paint();
}
public B(int i) {
super(10);
System.out.println("생성자 BB" + i);
}
public void paint() {
System.out.print("C");
}
public void draw() {
System.out.print("D");
}
}
정답 :
생성자 of AA10
생성자 BB1
CD
https://www.youtube.com/watch?v=EJ35VRwUo40&t=3s
문제 예시의 경우, 다음 유튜브 영상에서 가져왔습니다.
'Language > Java' 카테고리의 다른 글
[Java] 스트림 (0) | 2025.03.17 |
---|---|
[Java] if-else와 try-catch에 대해서 (0) | 2025.03.14 |
[Java] 제네릭(Generic) (0) | 2025.03.06 |
[Java] 기본형과 참조형 (0) | 2025.03.06 |
[Java] 익명 클래스 (Annoymous Class)와 람다 (Lamda) (0) | 2025.03.05 |