다형성(Polymorphism)은 객체 지향 프로그래밍(OOP)의 핵심 개념 중 하나로, "하나의 인터페이스로 여러 형태의 동작을 수행할 수 있는 능력"을 의미해. Java를 기준으로 정리해볼게.
✅ 다형성의 개념
정의
- 같은 메서드나 인터페이스를 통해 여러 객체가 서로 다른 동작을 수행할 수 있음.
- 부모 클래스의 참조 변수가 자식 클래스의 객체를 가리킬 수 있음.
이점
- 코드의 재사용성 증가
- 유지보수 용이
- 확장성이 좋아짐
✅ 다형성의 종류
컴파일 타임 다형성 (Compile-time Polymorphism) → 메서드 오버로딩(Method Overloading)
- 같은 클래스 내에서 같은 이름의 메서드를 여러 개 정의할 수 있음.
- 매개변수의 개수, 타입, 순서가 다르면 메서드를 구분할 수 있음.
- 컴파일 시점에 어떤 메서드를 호출할지 결정됨.
class MathUtil { int add(int a, int b) { return a + b; } double add(double a, double b) { // 오버로딩 return a + b; } }
런타임 다형성 (Runtime Polymorphism) → 메서드 오버라이딩(Method Overriding)
- 부모 클래스의 메서드를 자식 클래스에서 재정의(Override) 할 수 있음.
- 실행 시점에서 어떤 메서드를 호출할지 결정됨.
@Override
애노테이션을 사용하여 재정의된 메서드를 명확하게 표시하는 것이 일반적임.
class Animal { void makeSound() { System.out.println("동물이 소리를 냅니다."); } } class Dog extends Animal { @Override void makeSound() { System.out.println("멍멍!"); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); // 부모 타입(Animal)으로 자식 객체(Dog) 참조 myDog.makeSound(); // "멍멍!" 출력 } }
✅ 다형성의 핵심 개념
업캐스팅(Upcasting) 가능
- 부모 타입으로 자식 객체를 참조할 수 있음.
Animal myDog = new Dog();
(✅ 가능)- 하지만 부모 클래스에 정의된 메서드만 호출 가능함. (재정의된 메서드는 예외)
다운캐스팅(Downcasting) 필요
- 부모 타입을 다시 자식 타입으로 변환할 때는 다운캐스팅이 필요함.
(Dog) myDog
처럼 변환해야 하지만, 안전한 다운캐스팅을 위해instanceof
를 활용하는 것이 좋음.
if (myDog instanceof Dog) { Dog realDog = (Dog) myDog; realDog.makeSound(); // "멍멍!" }
✅ 팩트 체크
✔ 다형성은 "하나의 인터페이스로 여러 형태를 수행하는 것"
✔ 오버로딩(컴파일 타임)과 오버라이딩(런타임) 두 가지 형태가 있음
✔ 부모 클래스 타입으로 자식 객체를 참조하는 것이 가능 (업캐스팅)
✔ 실행 시점에서 메서드가 결정되기 때문에 유연한 코드 작성이 가능함
✔ 다운캐스팅 시 instanceof
를 활용하여 안전하게 변환해야 함
📌 부모와 자식 클래스의 멤버(변수, 메서드)는 언제 사용될까?
✅ 멤버(필드, 메서드)의 호출은 "참조 변수 타입"과 "실제 객체 타입"에 따라 달라짐.
✅ 특히, 메서드 오버라이딩 시 다형성이 적용되며, 업캐스팅(승급)과 다운캐스팅(강등)에 따라 접근 가능 여부가 달라짐.
1️⃣ 필드(멤버 변수)와 메서드(함수)의 사용 규칙
상황 | 필드(변수) 사용 | 메서드(함수) 사용 |
---|---|---|
기본적인 호출 | 참조 변수 타입을 기준으로 결정됨 | 실제 객체 타입을 기준으로 결정됨 |
오버라이딩된 메서드 | 영향을 받지 않음 | 항상 실제 객체의 메서드가 호출됨 |
업캐스팅(승급, Upcasting) | 부모의 필드만 접근 가능 | 자식이 오버라이딩한 메서드가 호출됨 |
다운캐스팅(강등, Downcasting) | 강제 형변환 후 자식의 필드 접근 가능 | 강제 형변환 후 자식 메서드 호출 가능 |
📌 즉, 필드는 "참조 변수의 타입"에 따라 결정되고, 메서드는 "실제 객체 타입"을 따라간다.
2️⃣ 기본적인 멤버 변수 및 메서드 접근
class Parent {
int x = 10;
void show() {
System.out.println("부모 클래스 show() 메서드");
}
}
class Child extends Parent {
int x = 20; // 부모의 x를 숨김 (변수 오버라이딩 아님, Shadowing)
@Override
void show() {
System.out.println("자식 클래스 show() 메서드");
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
System.out.println(c.x); // 20 (참조 변수 타입 기준 → Child의 x)
c.show(); // "자식 클래스 show() 메서드" (메서드는 객체 타입 기준)
}
}
📌 실행 결과:
20
자식 클래스 show() 메서드
✔ 변수는 참조 변수 타입 기준 → c.x
는 Child
의 x
값을 출력 (20).
✔ 메서드는 실제 객체 타입 기준 → c.show();
는 Child
의 show()
가 실행됨.
3️⃣ 업캐스팅(Upcasting) - 부모 타입으로 참조
public class Main {
public static void main(String[] args) {
Parent p = new Child(); // 업캐스팅 (승급)
System.out.println(p.x); // 10 (참조 변수 타입 기준 → Parent의 x)
p.show(); // "자식 클래스 show() 메서드" (메서드는 실제 객체 기준)
}
}
📌 실행 결과:
10
자식 클래스 show() 메서드
✔ 변수 p.x
는 Parent의 x
(10) → 참조 변수의 타입을 따라감.
✔ 메서드 p.show();
는 Child의 show()
실행 → 실제 객체 타입을 따라감.
📌 업캐스팅 시 부모의 변수만 보이고, 오버라이딩된 메서드는 자식 것이 호출됨.
4️⃣ 다운캐스팅(Downcasting) - 자식 타입으로 변환
public class Main {
public static void main(String[] args) {
Parent p = new Child(); // 업캐스팅
// System.out.println(p.y); // 컴파일 오류 (Parent 타입에서는 Child의 멤버 접근 불가)
Child c = (Child) p; // 다운캐스팅 (강등)
System.out.println(c.x); // 20 (Child의 x)
c.show(); // "자식 클래스 show() 메서드"
}
}
📌 실행 결과:
20
자식 클래스 show() 메서드
✔ 다운캐스팅 후 c.x
는 Child의 x
(20) → 이제 접근 가능.
✔ 다운캐스팅 후 c.show();
는 Child의 show()
실행 → 원래 가능했던 것.
📌 다운캐스팅을 하면 부모 클래스에서 접근할 수 없었던 자식의 멤버에도 접근할 수 있음.
5️⃣ 오버라이딩과 super
를 활용한 부모 메서드 호출
class Parent {
void show() {
System.out.println("부모 클래스 show() 메서드");
}
}
class Child extends Parent {
@Override
void show() {
super.show(); // 부모의 메서드 호출
System.out.println("자식 클래스 show() 메서드");
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
c.show();
}
}
📌 실행 결과:
부모 클래스 show() 메서드
자식 클래스 show() 메서드
✔ super.show();
를 사용하면 오버라이딩된 부모의 메서드도 실행 가능!
6️⃣ 정리 - 언제 부모/자식의 멤버가 사용될까?
상황 | 변수(필드) | 메서드(함수) |
---|---|---|
기본 호출 | 참조 변수 타입을 따름 | 실제 객체 타입을 따름 |
업캐스팅(부모 타입으로 참조) | 부모 클래스의 변수만 접근 가능 | 자식이 오버라이딩한 메서드가 호출됨 |
다운캐스팅(자식 타입으로 변환) | 강제 형변환 후 자식 변수 접근 가능 | 강제 형변환 후 자식 메서드 호출 가능 |
오버라이딩된 메서드 | X (변수는 오버라이딩되지 않음) | 항상 실제 객체의 메서드가 실행됨 |
✅ 변수는 참조 변수의 타입을 따른다.
✅ 메서드는 실제 객체 타입을 따른다.
✅ 업캐스팅하면 부모의 변수만 보이지만, 오버라이딩된 메서드는 자식 것이 호출된다.
✅ 다운캐스팅하면 자식 클래스의 변수와 메서드 모두 접근 가능하다.
👉 "필드는 참조 타입을 따라가고, 메서드는 실제 객체를 따라간다!" 🚀
좋은 질문이야! 객체(Object)와 타입(Type)의 차이를 확실히 이해하면 다형성을 더 잘 활용할 수 있어.
✅ 객체와 타입의 차이
객체(Object)란?
- 클래스의 인스턴스(실제 메모리에 생성된 것).
- 생성된 객체는 클래스에서 정의한 필드(변수)와 메서드(동작)를 가짐.
- 예)
new Dog()
→ Dog 클래스의 객체 생성.
타입(Type)이란?
- 변수가 참조할 객체의 데이터 유형을 결정.
- 변수가 접근할 수 있는 필드와 메서드를 제한함.
- 예)
Animal myDog = new Dog();
→myDog
변수의 타입은Animal
이므로,Animal
클래스에서 정의된 필드와 메서드만 사용 가능.
✅ 객체 vs. 타입의 동작 차이
객체는 실제로 Dog
인스턴스이지만, 참조 타입(Animal)에 따라 사용할 수 있는 기능이 제한됨.
class Animal {
String type = "동물"; // 필드
void makeSound() { // 메서드
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
String breed = "불독"; // 자식 클래스에만 있는 필드
@Override
void makeSound() {
System.out.println("멍멍!");
}
void wagTail() { // 자식 클래스에만 있는 메서드
System.out.println("꼬리를 흔듭니다.");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog(); // 업캐스팅
System.out.println(myDog.type); // ✅ "동물" (Animal에 정의된 필드 사용 가능)
myDog.makeSound(); // ✅ "멍멍!" (오버라이딩된 메서드는 호출 가능)
// System.out.println(myDog.breed); // ❌ 오류! (Animal에는 breed 필드 없음)
// myDog.wagTail(); // ❌ 오류! (Animal에는 wagTail() 없음)
Dog realDog = (Dog) myDog; // 다운캐스팅
System.out.println(realDog.breed); // ✅ "불독"
realDog.wagTail(); // ✅ "꼬리를 흔듭니다."
}
}
✅ 정리
✔ 객체(Object) = 실제 생성된 인스턴스
✔ 타입(Type) = 변수가 참조할 수 있는 필드와 메서드를 결정
✔ 업캐스팅(Animal myDog = new Dog();)
- 부모 타입 변수는 부모 클래스에 정의된 필드와 메서드만 접근 가능
- 오버라이딩된 메서드는 호출 가능 (동적 바인딩)
✔ 다운캐스팅(Dog realDog = (Dog) myDog;) - 자식 클래스의 필드와 메서드를 사용하려면 다운캐스팅 필요
이제 업캐스팅과 타입의 개념이 더 명확해졌을 거야! 😊
업캐스팅(Upcasting)의 핵심 이점은 코드의 유연성과 확장성이야. 부모 타입(Animal
)을 사용하면, 자식 클래스(Dog
, Cat
, Bird
등)가 추가되어도 기존 코드를 변경하지 않고 사용할 수 있기 때문이야.
✅ 업캐스팅의 주요 이점
1️⃣ 유연성과 확장성 증가
- 여러 자식 클래스가 추가되어도 공통된 인터페이스(부모 클래스)를 통해 다룰 수 있음.
- 새로 추가되는 동물(
Cat
,Bird
등)이 있어도Animal
타입으로 관리하면 기존 코드를 수정할 필요가 없음.
class Animal {
void makeSound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("멍멍!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("야옹!");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog(); // 업캐스팅
myAnimal.makeSound(); // "멍멍!" (동적 바인딩)
myAnimal = new Cat(); // 업캐스팅 (유연성)
myAnimal.makeSound(); // "야옹!" (동적 바인딩)
}
}
✔ myAnimal
변수의 타입이 Animal
이므로, Dog → Cat으로 변경할 때 코드 수정이 필요 없음.
2️⃣ 코드 재사용성 증가 (다형성 활용)
- 부모 클래스 타입을 사용하면, 같은 코드로 다양한 객체를 처리할 수 있음.
public class Main {
public static void printAnimalSound(Animal animal) {
animal.makeSound(); // 어떤 동물이든 같은 코드로 처리 가능
}
public static void main(String[] args) {
printAnimalSound(new Dog()); // "멍멍!"
printAnimalSound(new Cat()); // "야옹!"
}
}
✔ printAnimalSound()
메서드는 Animal
타입만 받기 때문에, Dog, Cat 등 어떤 객체든 동일한 코드로 처리 가능.
3️⃣ 컬렉션(List, 배열)에서의 활용
- 업캐스팅을 사용하면 다양한 객체를 같은 리스트(List)나 배열(Array)에서 관리 가능.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
animals.add(new Dog()); // 업캐스팅
animals.add(new Cat()); // 업캐스팅
for (Animal animal : animals) {
animal.makeSound(); // 각각 "멍멍!", "야옹!" 출력
}
}
}
✔ ArrayList<Animal>
을 사용하면 모든 동물 객체를 하나의 리스트에서 관리 가능.
4️⃣ 인터페이스와 조합하면 강력한 설계 가능
- 업캐스팅을 활용하면 객체 간 결합도를 줄이고 인터페이스를 통한 유연한 설계 가능.
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("야옹!");
}
}
public class Main {
public static void main(String[] args) {
Animal myPet = new Dog(); // 인터페이스 타입으로 업캐스팅
myPet.makeSound(); // "멍멍!"
}
}
✔ 인터페이스를 사용하면 여러 개의 클래스를 유연하게 확장 가능.
✅ 결론
✔ 업캐스팅의 주요 이점
1️⃣ 유연성과 확장성 → 새 클래스 추가 시 기존 코드 수정 불필요
2️⃣ 코드 재사용성 → 하나의 메서드로 여러 객체 처리 가능
3️⃣ 컬렉션(List, 배열)에서 활용 → 여러 객체를 한 리스트에 저장 가능
4️⃣ 인터페이스와 조합 가능 → 유지보수와 확장성 향상
📌 정리하면, 업캐스팅을 활용하면 코드가 더 유연하고 확장 가능해지며, 유지보수가 쉬워진다! 🚀
'자바' 카테고리의 다른 글
객체(Object)와 타입(Type)의 차이 (0) | 2025.02.09 |
---|---|
상속(Inheritance)이란? (0) | 2025.02.09 |
Object 클래스란? (0) | 2025.02.09 |
super() 정리2 (0) | 2025.02.09 |
매개변수가 없는 메서드와 매개변수가 없는 생성자 비교를 통한 생성자 이해 (0) | 2025.02.08 |