| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 백준
- 파이썬
- 프로그래밍기초
- 자바개발
- HashMap
- 코딩테스트준비
- 예외처리
- 개발자팁
- 코딩공부
- 자바기초
- 코딩인터뷰
- 자료구조
- JVM
- Java
- 자바공부
- 개발공부
- 메모리관리
- 클린코드
- 알고리즘
- 자바프로그래밍
- 멀티스레드
- 자바
- 프로그래머스
- 코딩테스트
- 객체지향
- 코딩테스트팁
- 알고리즘공부
- 정렬
- 개발자취업
- 가비지컬렉션
- Today
- Total
코드 한 줄의 기록
Java 다형성과 동적 디스패치 완벽 가이드: 개발자가 알아야 할 핵심 개념 본문
Java를 공부하면서 객체지향 프로그래밍의 핵심 개념 중 하나인 다형성(Polymorphism)에 대해 깊이 있게 알아보려고 합니다. 이번 포스팅에서는 다형성의 기본 개념부터 동적 디스패치, 업캐스팅과 다운캐스팅까지 실제 예제 코드와 함께 차근차근 설명해드리겠습니다.
다형성(Polymorphism)이란?
다형성은 그리스어로 '여러 개'를 의미하는 'poly'와 '형태'를 의미하는 'morphism'의 합성어입니다. 프로그래밍에서 다형성은 하나의 객체가 여러 가지 형태를 가질 수 있는 성질을 의미합니다.
Java에서 다형성은 상위 클래스 타입의 참조 변수로 하위 클래스의 객체를 참조할 수 있도록 하는 것입니다. 즉, 부모 클래스 타입으로 선언된 변수가 자식 클래스의 인스턴스를 가리킬 수 있다는 뜻이죠.
class Animal {
public void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("멍멍!");
}
public void bark() {
System.out.println("개가 짖습니다.");
}
}
// 다형성 활용 예시
Animal animal = new Dog(); // 다형성!
animal.sound(); // "멍멍!" 출력 (오버라이딩된 메서드 실행)
다형성의 조건
다형성이 성립하기 위해서는 다음 조건들이 만족되어야 합니다.
- 상속 관계: 상위 클래스와 하위 클래스 간에 상속 관계가 있어야 함
- 메서드 오버라이딩: 하위 클래스에서 상위 클래스의 메서드를 재정의해야 함
- 업캐스팅: 자식 클래스의 객체가 부모 클래스 타입으로 형변환되어야 함
동적 디스패치(Dynamic Dispatch)
동적 디스패치는 다형성을 구현하는 핵심 메커니즘입니다. 컴파일 시점에는 어떤 메서드가 호출될지 알 수 없고, 런타임 시점에서 실제 객체의 타입을 보고 메서드를 결정하는 과정입니다.
정적 디스패치 vs 동적 디스패치
정적 디스패치(Static Dispatch)는 컴파일 시점에 어떤 메서드를 호출할지 명확하게 결정되는 경우입니다.
class Service {
void run(int number) {
System.out.println("run(" + number + ")");
}
void run(String msg) {
System.out.println("run(" + msg + ")");
}
}
public class Main {
public static void main(String[] args) {
Service service = new Service();
service.run(1); // 컴파일 시점에 첫 번째 메서드 호출 결정
service.run("Hello"); // 컴파일 시점에 두 번째 메서드 호출 결정
}
}
동적 디스패치(Dynamic Dispatch)는 런타임에 객체의 실제 타입을 확인하여 메서드를 호출합니다.
abstract class Animal {
abstract void makeSound();
}
class Cat extends Animal {
@Override
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[] animals = {new Cat(), new Dog()};
for (Animal animal : animals) {
animal.makeSound(); // 런타임에 실제 객체 타입에 따라 메서드 결정
}
}
}
가상 메서드 테이블(Virtual Method Table)
Java에서 동적 디스패치는 가상 메서드 테이블(Virtual Method Table, vtable)을 통해 구현됩니다. 각 클래스마다 메서드들의 주소를 저장하는 테이블이 생성되며, 객체가 메서드를 호출할 때 이 테이블을 참조하여 적절한 메서드를 찾아 실행합니다.
업캐스팅(Upcasting)과 다운캐스팅(Downcasting)
업캐스팅(Upcasting)
업캐스팅은 하위 클래스의 객체를 상위 클래스 타입으로 형변환하는 것입니다. 업캐스팅은 자동으로 이루어지며 형변환 연산자를 생략할 수 있습니다.
class Person {
String name;
public void introduce() {
System.out.println("안녕하세요, 저는 " + name + "입니다.");
}
}
class Student extends Person {
String studentId;
public void study() {
System.out.println("공부를 합니다.");
}
@Override
public void introduce() {
System.out.println("안녕하세요, 학생 " + name + "입니다.");
}
}
// 업캐스팅 예시
Student student = new Student();
Person person = student; // 업캐스팅 (자동)
person.introduce(); // Student의 오버라이딩된 메서드 실행
// person.study(); // 컴파일 에러! Person 타입으로는 Student의 고유 메서드 접근 불가
다운캐스팅(Downcasting)
다운캐스팅은 상위 클래스 타입을 하위 클래스 타입으로 형변환하는 것입니다. 다운캐스팅은 명시적으로 형변환 연산자를 사용해야 하며, 안전성을 위해 instanceof 연산자로 확인해야 합니다.
public class Main {
public static void main(String[] args) {
Person person = new Student(); // 업캐스팅
// instanceof로 다운캐스팅 가능 여부 확인
if (person instanceof Student) {
Student student = (Student) person; // 다운캐스팅
student.study(); // 이제 Student의 고유 메서드 호출 가능
}
}
}
instanceof 연산자
instanceof 연산자는 객체가 특정 클래스나 인터페이스의 인스턴스인지 확인하는 연산자입니다. 다운캐스팅을 수행하기 전에 안전성을 보장하기 위해 반드시 사용해야 합니다.
public static void checkAnimal(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.bark(); // Dog만의 고유 메서드 호출
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.meow(); // Cat만의 고유 메서드 호출
}
}
실전 예제: 도형 계산 프로그램
다형성의 활용을 보여주는 실전 예제를 만들어보겠습니다.
abstract class Shape {
protected String name;
public Shape(String name) {
this.name = name;
}
// 추상 메서드 - 하위 클래스에서 반드시 구현해야 함
public abstract double calculateArea();
public void printInfo() {
System.out.println(name + "의 넓이: " + calculateArea());
}
}
class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
super("직사각형");
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
super("원");
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Triangle extends Shape {
private double base, height;
public Triangle(double base, double height) {
super("삼각형");
this.base = base;
this.height = height;
}
@Override
public double calculateArea() {
return (base * height) / 2;
}
}
// 활용 예시
public class ShapeCalculator {
public static void main(String[] args) {
Shape[] shapes = {
new Rectangle(5, 10),
new Circle(7),
new Triangle(6, 8)
};
// 다형성을 활용한 일괄 처리
for (Shape shape : shapes) {
shape.printInfo(); // 각 객체의 실제 타입에 따라 다른 계산 방식 적용
}
// instanceof를 활용한 타입별 처리
for (Shape shape : shapes) {
if (shape instanceof Circle) {
System.out.println("원형 도형을 발견했습니다!");
} else if (shape instanceof Rectangle) {
System.out.println("사각형 도형을 발견했습니다!");
}
}
}
}
다형성의 장점과 활용 이유
장점
- 코드 재사용성 향상: 상위 클래스 타입으로 여러 하위 클래스 객체를 동일하게 처리 가능
- 유지보수성 증대: 새로운 클래스 추가 시 기존 코드 수정 없이 확장 가능
- 느슨한 결합: 클래스 간의 의존성을 줄여 확장성은 높이고 결합도는 낮춤
단점
- 가독성 저하: 실제 인스턴스 타입을 파악하기 어려울 수 있음
- 디버깅의 어려움: 런타임에 메서드가 결정되어 디버깅이 복잡해질 수 있음
인터페이스와 다형성
인터페이스는 순수 추상 클래스의 발전된 형태로, 다형성 구현에 매우 중요한 역할을 합니다.
interface Drawable {
void draw();
}
interface Movable {
void move();
}
class GameCharacter implements Drawable, Movable {
private String name;
public GameCharacter(String name) {
this.name = name;
}
@Override
public void draw() {
System.out.println(name + "을(를) 화면에 그립니다.");
}
@Override
public void move() {
System.out.println(name + "이(가) 이동합니다.");
}
}
// 인터페이스를 활용한 다형성
public class Game {
public static void renderObject(Drawable drawable) {
drawable.draw(); // 구현 객체에 따라 다른 동작
}
public static void moveObject(Movable movable) {
movable.move(); // 구현 객체에 따라 다른 동작
}
}
Java의 다형성은 객체지향 프로그래밍의 핵심 개념으로, 동적 디스패치를 통해 런타임에 적절한 메서드를 선택하여 실행합니다. 업캐스팅과 다운캐스팅을 적절히 활용하고 instanceof 연산자로 안전성을 보장하면서 사용하는 것이 중요합니다.
다형성을 제대로 이해하고 활용하면 유연하고 확장 가능한 코드를 작성할 수 있으며, 이는 실무에서도 매우 중요한 능력입니다. 특히 프레임워크나 라이브러리를 사용할 때 다형성의 개념을 이해하고 있으면 더욱 효과적으로 활용할 수 있습니다.
앞으로도 계속해서 Java의 다양한 개념들을 함께 학습해나가며, 더 나은 개발자가 되어보겠습니다!
Java 추상 클래스 vs 인터페이스 완벽 비교와 다중 구현 활용 가이드
프로그래밍을 공부하면서 자바(Java) 의 핵심 개념 중 하나인 추상 클래스(Abstract Class) 와 인터페이스(Interface), 그리고 이를 활용한 다중 구현(Multiple Implementation) 은 많은 개발자가 헷갈려 하는 부
byteandbit.tistory.com
'JAVA' 카테고리의 다른 글
| 자바 equals/hashCode/toString 설계 원칙 총정리 + Lombok 사용 시 주의점까지 한 번에 정리 (0) | 2025.10.12 |
|---|---|
| Java 내부 클래스 완전 정복: 멤버/로컬/익명 클래스 실무 활용법 (0) | 2025.10.11 |
| Java 추상 클래스 vs 인터페이스 완벽 비교와 다중 구현 활용 가이드 (0) | 2025.10.10 |
| Java 상속과 오버라이딩 완벽 가이드: super 활용법까지 쉽게 이해하기 (0) | 2025.10.09 |
| Java 객체 생성 완벽 가이드: 생성자, 정적 팩토리 메서드, 생성자 체이닝 총정리 (0) | 2025.10.09 |