멀티캠퍼스

[2026.04.24] TIL - 20일차 Java 다형성

buckwheat 2026. 4. 26. 17:12

 

1. 다형성

다형성은 객체지향 프로그래밍의 3대 특징 중 하나로, “여러 형태를 가진다”는 뜻이다.
쉽게 말하면 하나의 부모 타입으로 여러 종류의 자식 객체를 다룰 수 있는 개념이다.

예를 들어 Car라는 부모 클래스가 있고, Sonata, Avante, Grandure 같은 자식 클래스가 있다면, 자식 객체들을 각각 따로 다루는 것이 아니라 Car 타입 하나로 묶어서 다룰 수 있다. 즉, 같은 부모 타입이지만 실제로는 서로 다른 자식 객체를 담을 수 있다는 점이 다형성의 핵심이다.


2. 클래스 형변환

다형성을 이해하려면 클래스 형변환을 먼저 알아야 한다. 클래스 형변환은 상속 관계에 있는 클래스 사이에서만 가능하다.

 

1) 업 캐스팅

업 캐스팅은 자식 객체를 부모 타입의 참조변수에 담는 것이다. 즉, 부모 타입이 자식 객체의 주소를 받을 수 있는 것이다.

예를 들어 SonataCar의 자식 클래스라면, Car c = new Sonata();처럼 사용할 수 있다.

이 경우 실제 객체는 Sonata이지만, 참조변수 타입은 Car이다.

업 캐스팅은 자동으로 처리되지만 부모 타입의 참조변수로는 부모 클래스에 정의된 멤버만 접근할 수 있다. 즉, 실제 객체가 자식 객체라고 해도 부모가 원래 가지고 있지 않은 자식만의 멤버는 바로 사용할 수 없다.

 

2) 다운 캐스팅

다운 캐스팅은 부모 타입의 참조변수를 다시 자식 타입으로 형변환하는 것이다. 즉, 부모 타입으로 담아 두었던 자식 객체를 다시 자식 타입으로 바꾸는 것이다.

예를 들어 Car c = new Sonata();라고 한 뒤, Sonata 클래스에만 있는 메소드를 사용하고 싶다면 (Sonata)c처럼 형변환해야 한다.

다운 캐스팅은 자동으로 되지 않기 때문에 반드시 자식 클래스 타입을 직접 명시해야 한다.

 


 

3. instanceof 연산자

다운 캐스팅을 할 때는 현재 참조변수가 실제로 어떤 객체를 가리키고 있는지 확인하는 것이 중요하다. 이때 사용하는 것이 instanceof 연산자이다.

instanceof는 참조변수가 특정 클래스 타입의 객체를 참조하고 있으면 true, 아니면 false를 반환한다.

즉, 다운 캐스팅 전에 if(c instanceof Sonata)처럼 확인하면, 안전하게 형변환할 수 있다.
상속 관계에 있는 여러 자식 객체를 부모 타입 하나로 다루다가, 실제 객체 종류에 따라 다르게 처리해야 할 때 자주 사용한다.

 


4. 객체배열과 다형성

다형성은 객체배열에서도 유용하게 사용된다.
부모 클래스 타입의 배열을 만들면, 그 안에 여러 종류의 자식 객체를 저장할 수 있다.

예를 들어 Car[] 배열을 만들고, 그 안에 Sonata, Avante, Grandure, Spark, Morning 객체를 각각 저장할 수 있다.
즉, 배열의 자료형은 하나인데 실제 들어가는 객체는 여러 종류가 될 수 있다.

이처럼 다형성을 사용하면 관련 있는 여러 객체를 하나의 배열로 묶어서 관리할 수 있다.

 

5. 매개변수와 다형성

다형성은 메소드의 매개변수에서도 활용된다.
부모 타입의 매개변수 하나만 만들어 두면, 여러 자식 객체를 전달받을 수 있다.

예를 들어 driveCar(Car c)처럼 부모 타입을 매개변수로 두면, 메소드 호출 시 new Sonata(), new Avante(), new Grandure() 같은 자식 객체들을 모두 전달할 수 있다.

즉, 자식 클래스마다 메소드를 따로 만들 필요 없이 부모 타입 하나로 여러 객체를 처리할 수 있다는 점이 장점이다.


6. 바인딩과 동적 바인딩

바인딩은 호출하는 코드와 실제 실행할 메소드를 연결하는 것을 말한다. 기본적으로 프로그램은 컴파일될 때 메소드 호출이 정적으로 연결된다.
하지만 상속 관계에서 다형성이 적용되고, 자식 클래스가 부모 메소드를 오버라이딩한 경우에는 실행 시점에 실제 객체 타입에 맞는 메소드가 연결된다. 이를 동적 바인딩이라고 한다.

 

즉, 참조변수 타입이 부모 클래스라고 해도 실제 객체가 자식 클래스라면, 오버라이딩된 자식 메소드가 우선 실행된다.
다형성이 제대로 동작하는 이유가 바로 이 동적 바인딩 때문이다.


7. 추상 클래스

추상 클래스는 미완성 클래스라고 볼 수 있다.
몸체가 없는 추상 메소드를 하나 이상 포함할 수 있으며, 클래스 선언부에 abstract 키워드를 사용한다.

추상 클래스는 자체적으로 객체를 만들 수 없고, 반드시 상속을 통해 자식 클래스에서 사용해야 한다.
또한 추상 메소드가 있으면 자식 클래스에서 반드시 오버라이딩하여 구현해야 한다.

추상 클래스의 특징은 다음과 같다.

  • abstract 키워드를 사용한다.
  • 객체 생성이 불가능하다.
  • 추상 메소드가 하나라도 있으면 반드시 추상 클래스여야 한다.
  • 일반 변수와 일반 메소드도 함께 가질 수 있다.
  • 참조형 변수 타입으로는 사용할 수 있다.

추상 클래스의 장점은 공통된 기능을 정의하면서도, 자식 클래스에서 꼭 구현해야 할 기능을 강제할 수 있다는 점이다.


 

8. 인터페이스

인터페이스는 상수 필드와 추상 메소드만 작성할 수 있는 특수한 형태이다.
즉, 여러 클래스가 공통적으로 가져야 할 기능의 규칙만 모아 놓은 것이라고 이해하면 된다.

인터페이스 안의 메소드는 묵시적으로 public abstract이고, 변수는 묵시적으로 public static final이다.
따라서 인터페이스를 구현하는 클래스는 인터페이스 안에 선언된 메소드를 반드시 구현해야 한다.

인터페이스의 특징은 다음과 같다.

  • 모든 메소드는 기본적으로 public abstract
  • 모든 변수는 기본적으로 public static final
  • 객체 생성은 불가능하다
  • 참조형 변수로는 사용할 수 있다

인터페이스의 장점은 공통 기능에 대한 일관성을 제공한다는 점이다. 또한 다형성을 지원해 여러 객체를 같은 방식으로 연결하고 사용할 수 있게 해 준다.


9. 추상 클래스와 인터페이스의 차이

추상 클래스와 인터페이스는 모두 객체를 직접 생성할 수 없다는 공통점이 있다.
하지만 차이도 있다.

추상 클래스는 단일 상속만 가능하고, extends를 사용한다.
일반 필드와 일반 메소드도 가질 수 있으며, 추상 메소드는 0개 이상 포함할 수 있다.

인터페이스는 다중 구현이 가능하고, implements를 사용한다.
모든 메소드가 추상 메소드이며, 모든 필드는 상수 형태로만 존재한다.

즉, 추상 클래스는 “공통 기능 + 강제할 기능”을 함께 담는 데 적합하고, 인터페이스는 “반드시 지켜야 할 기능 규칙”을 정의하는 데 더 적합하다고 볼 수 있다.

 

구분 추상클래스 인터페이스
상속 단일 상속 implements 사용
구현 extends 사용 implements 사용
추상 메소드 abstract 메소드 0 이상 모든 메소드는 abstract
abstract 명시적 사용 묵시적으로 abstract
객체 객체 생성 불가 객체 생성 불가
용도 참조 타입 참조 타입