다형성 & 추상클래스 & 인터페이스

DALLAE·2025년 4월 20일

개발 지식

목록 보기
3/3

1. 다형성 개념 소개

  • 다형성 정의: "같은 메서드를 호출해도 객체의 타입에 따라 다르게 동작하는 것”
  • 실생활 예시 "한 명의 사람이 학생, 자녀, 친구 등 여러 역할(다형성)"
  • "부모는 자식을 담을 수 있다" 부모 타입으로 자식 객체를 다뤄야 그게 다형성
  • 다형성은 상속을 기반으로 구현됨. 한 타입의 참조변수로 여러 타입의 객체를 참조 가능
  • 조건: 상속 관계가 있어야 함 extends.하지만 상속만 받았다고 해서 다형성인 것은 X
Person[] 김현정 = {
    new Daughter(),
    new Student(),
    new Employee()
};

for (Person 역할 : 김현정) {
    역할.자기소개();  // 각각 다르게 오버라이딩된 메서드 호출
}

Person 김달래 = new 학생();
김달래.자기소개(); // "안녕하세요 학생입니다."

2. 다형성을 사용하는 이유

  • 코드 간결화, 유지보수 편리, 확장성 증가

다형성 없는 버전

Daughter d = new Daughter();
Student s = new Student();
Employee e = new Employee();

d.자기소개();
s.자기소개();
e.자기소개();
  • 완전 비효율적임. 새로운 게 생길 때 마다 객체 생성 계속 새로 해줘야 함
  • 공통된 타입이 없어서 배열로 묶지도, 반복문 사용도 못함

3. 형변환

class Animal {}
class Dog extends Animal {
    void sound() {
        System.out.println("멍멍!");
    }
}

Dog 달래 = new Dog();           // 달래는 강아지
Animal 동물 = 달래;              // ✅ 업캐스팅 (자동 형변환)
Dog 다시달래 = (Dog) 동물;       // ✅ 다운캐스팅 (명시적 형변환)
다시달래.sound();                // 멍멍

Animal 초코 = new Animal();     // 초코는 그냥 동물
Dog 가짜달래 = (Dog) 초코;      // ❌ 컴파일은 되지만 실행하면 에러!
가짜달래.sound();               // ❌ 실제로 Dog가 아니라서 문제 생김

if (초코 instanceof Dog) {  // 형변환 오류 주의사항. 초코는 그냥 동물이지 강아지가 아님 (false)
    Dog 가짜달래 = (Dog) 초코;
    가짜달래.sound();
}

영문 변수명

Dog d = new Dog();  // d는 강아지
Aimal a = d;  // 업캐스팅 (자동 형변환)
Dog d2 = (Dog)a;  // 다운캐스팅 (명시적 형변환)
d2.bark() // 멍멍

4. 매개변수의 다형성

  • 메서드에서 파라미터로 받은 객체에 대해 다형성을 적용하는 방식
  • 하나의 메서드여러 객체를 처리할 수 있음. 즉, 클래스 마다 별도로 오버라이딩 필요 X
  • 메서드가 부모 타입을 매개변수로 받고, 그 안에서 자식 객체가 어떤 메서드를 호출할지 결정됨
  • 교재 예시: void buy(Product p) 메서드로 다양한 상품 구매 처리
    class Product {
        int price;
    }
    
    class Tv extends Product {}
    class Computer extends Product {}
    
    class Buyer {
        void buy(Product p) {
            System.out.println("구매 완료!");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Buyer b = new Buyer();
            b.buy(new Tv());         // Tv도 Product
            b.buy(new Computer());   // Computer도 Product
        }
    }
  • 교재 예시: void moveUnit(Unit u) 메서드로 다양한 유닛 움직임
    public class Main {
        // 매개변수 다형성 적용
        static void moveUnit(Unit u, int x, int y) {
            u.move(x, y);
        }
    
        public static void main(String[] args) {
            Unit marine = new Marine();
            Unit tank = new Tank();
    
            moveUnit(marine, 5, 10);  // Marine is moving.
            moveUnit(tank, 3, 72);    // Tank is moving.
        }
    }

5. 추상클래스와 추상메서드 (간략히)

  • 추상클래스는 "미완성 설계도"로, 객체를 생성할 수 없고 선언된 추상메서드는 자식 클래스에서 반드시 구현해야 한다.
  • 추상메서드는 구현이 없는 메서드로, 이를 상속받는 자식 클래스가 반드시 구현해야 한다.
abstract class Animal {
    abstract void sound();  // 추상 메서드, 자식 클래스에서 반드시 구현해야 함
}

class Dog extends Animal {
    void sound() {
        System.out.println("멍멍!");
    }
}

6. 추상클래스를 이용한 다형성

  • 공통 메서드 호출: a.sound();
  • 새로운 애니멀(새 등) 추가 시 기존 코드 수정 불필요
abstract class Animal {
    abstract void sound();  // 추상메서드
    void sleep() {        // 일반메서드
        System.out.println("잠자기");
    }
}

Animal[] animals = {new Dog(), new Cat(), new Dallae()};
for(Animal a : animals) {
    a.sound();  // 각 동물마다 다르게 소리냄 (추상메서드 오버라이딩)
    a.sleep(); // 모두 동일하게 "잠자기" 출력 (일반메서드)
}
  • 교재 예시:
    • Unit[] group = { new Marine(), new Tank(), new Dropship() };
    • for(int i=0; i<group.length; i++) group[i].move(100, 200);

7. 인터페이스 (간략히)

  • 인터페이스는 "메서드 목록을 정의한 계약서"로, 메서드는 반드시 구현해야 한다.
  • 인터페이스는 다중 구현이 가능하여 여러 클래스를 동시에 구현할 수 있다.
interface Runnable {
    void run();
}

class Dog implements Runnable {
    public void run() {
        System.out.println("강아지가 달린다!");
    }
}

class Cat implements Runnable {
    public void run() {
        System.out.println("고양이가 달린다!");
    }
}
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();  // "강아지가 달린다!"
    }
}

8. 인터페이스를 이용한 다형성

  • 여러 인터페이스 구현 예시:
    • class Dallae extends Dog implements petable, Trainable { ... }
  • 인터페이스 활용 예시:
    • Trainable 인터페이스를 통해 훈련 가능한 객체만 골라서 처리
    • void conductTraning(Trainable t) { t.train(); }
interface Runnable {
    void run();
}

class Dog implements Runnable {
    public void run() {
        System.out.println("강아지가 달린다!");
    }
}

class Cat implements Runnable {
    public void run() {
        System.out.println("고양이가 달린다!");
    }
}
public class Main {
    public static void main(String[] args) {
        Runnable runnable1 = new Dog();
        Runnable runnable2 = new Cat();

        runnable1.run();  // "강아지가 달린다!"
        runnable2.run();  // "고양이가 달린다!"
    }
}

9. 질문과 답변

Q1. void run()같이 메서드가 하나일 경우 추상클래스? 인터페이스?

강사님한테도 다시 물어봐서 얻은 정확한 답변!

전제조건이 ‘메서드가 하나일 경우’에는 인터페이스를 사용하는 것이 더 일반적이고 유연하다.

기능 중심의 설계가 필요할 때 인터페이스가 적합하며 다중 구현이 가능하기에 확장성도 좋기 때문이다.

발표 때 헛소리를 했습니다…

Q2. double타입(3.5)을 int타입(3)으로 형변환하면 0.5가 손실되는 거 아닌가? 그럼 Animal 타입을 Dog타입으로 형변환하면 뭘 잃는거지?

기본형의 형변환은 진짜 값을 잘라내는 것. 잃는거 맞음

double d = 1.5;
int i = (int) d;        // i는 1 (0.5 사라짐)
double d2 = (double) i; // d2는 1.0 (이미 0.5는 날아감)

참조형의 형변환은 실제 값은 그대로 있지만 사용할 수 있는 기능의 범위만 제한되는 것. 잃는 거 아니고 참조타입을 부모타입, 자식타입 바꿔끼워가며 쓴다고 생각

Dog d = new Dog();
Animal a = (Animal) d; // 업캐스팅 – Dog의 정보는 살아있지만 Animal처럼 취급됨
a.sound(); // 컴파일 에러 – Animal엔 bark() 없음

Dog d2 = (Dog) a; // 다시 다운캐스팅하면 Dog 기능(bark) 사용 가능
d2.sound(); // 잘 동작됨
profile
안녕!!

0개의 댓글