이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › Java › 클래스와 객체
다형성과 메서드 선택
어느 메서드가 실행될지는 누가, 언제 정하나
변수의 선언 타입과 실제 들어 있는 객체의 타입이 다를 수 있습니다. 오버라이딩한 메서드는 실행 시점에 실제 객체를 보고 고르지만, 오버로딩, static 메서드, 필드는 컴파일 시점에 선언 타입만 보고 정해집니다.
실행으로 대조 · JDK 21에서 모든 조합을 컴파일·실행해 대조
이 실습에 나오는 개념
- 다형성
- 같은 타입의 참조로 같은 메서드를 호출해도 실제 객체의 종류에 따라 다른 동작이 실행되는 성질이다. 상위 타입(인터페이스나 상위 클래스) 변수에 여러 하위 타입 객체를 담을 수 있고, 어떤 구현이 실행될지는 실행 시점에 결정된다(동적 바인딩). 호출하는 코드를 바꾸지 않고 새로운 구현을 추가할 수 있게 해 준다.
- 오버라이딩
- 상위 클래스(또는 인터페이스)의 메서드를 하위 클래스에서 같은 시그니처로 다시 정의하는 것이다. 실행 시점에 실제 객체의 타입에 맞는 메서드가 호출되어 다형성을 구현한다. 접근 범위를 더 좁힐 수 없고, static·private·final 메서드는 오버라이딩할 수 없다.
- 오버로딩
- 같은 이름의 메서드를 매개변수의 개수나 타입을 다르게 해서 여러 개 정의하는 것이다. 어떤 메서드가 호출될지는 컴파일 시점에 인자의 타입을 보고 결정된다. 반환 타입만 다른 것은 오버로딩이 아니며 컴파일 오류다.
- static
- 인스턴스가 아니라 클래스에 속하는 멤버임을 나타내는 키워드다. static 필드는 클래스당 하나만 존재해 모든 인스턴스가 공유하고, static 메서드는 객체를 만들지 않고 호출한다. 값이 바뀌는 static 필드는 사실상 전역 변수이므로 여러 스레드가 동시에 접근할 때 문제가 되기 쉽다.
- 상속
- 기존 클래스(상위 클래스)의 필드와 메서드를 물려받아 새 클래스(하위 클래스)를 정의하는 것이다. 공통 코드를 재사용하고 하위 클래스에서 필요한 부분만 추가하거나 재정의한다. 자바의 클래스는 단일 상속만 가능하며, 상위 클래스 변경이 하위 클래스 전체에 영향을 주므로 "is-a" 관계가 분명할 때만 쓰는 것이 좋다.
이렇게 바꿔 보세요
- 처음 설정으로 — Animal 변수에 Dog 객체. 오버라이딩된 메서드는 Dog 것이 실행됩니다.
- 같은 변수를 오버로딩 메서드에 넘기면 — 실제 객체가 Dog인데도 print(Animal)이 선택됩니다.
- static 메서드는 — 오버라이딩되지 않습니다. 선언 타입의 것이 불립니다.
- 필드는 — 필드도 다형성이 적용되지 않습니다.
- 선언 타입을 Dog로 바꾸면 — 오버로딩에서 print(Dog)가 선택됩니다.
- Dog 변수에 Animal 객체 — 컴파일되지 않습니다.
처음 설정으로 실행하면
- 컴파일 시점: Animal a = new Dog(). 변수 a의 선언 타입은 Animal이고, 실행되면 힙에 Dog 객체가 만들어집니다. 둘이 다릅니다.
- 컴파일 시점: 컴파일러는 선언 타입 Animal에 sound()가 있는지만 확인합니다. 어느 클래스의 sound()를 실행할지는 정하지 않고 "sound()를 호출하라"는 지시만 남깁니다.
- 실행 시점: JVM이 a가 가리키는 실제 객체의 클래스(Dog)를 확인하고, 그 클래스의 sound()를 찾아 실행합니다(동적 바인딩).
실행 결과
- 오버라이딩된 인스턴스 메서드는 변수의 선언 타입이 아니라 실제 객체의 타입으로 정해집니다. 이것이 다형성이며, 스프링이 인터페이스 타입으로 주입받은 구현체를 실행할 수 있는 원리입니다.
코드
class Animal {
String name = "동물";
void sound() { System.out.println("Animal.sound"); }
static void info() { System.out.println("Animal.info"); }
}
class Dog extends Animal {
String name = "개"; // 같은 이름의 필드
@Override
void sound() { System.out.println("Dog.sound"); }
static void info() { System.out.println("Dog.info"); }
}
class Printer {
void print(Animal x) { System.out.println("print(Animal)"); }
void print(Dog x) { System.out.println("print(Dog)"); }
}
Animal a = new Dog();
a.sound();
이 모형의 전제
모든 조합의 출력은 이 PC의 JDK 21에서 같은 코드를 실제로 컴파일·실행한 결과와 대조했습니다. 변수를 통해 static 메서드를 호출하는 a.info()는 문법상 가능하지만 권장되지 않는 방식이며, 선언 타입 기준으로 정해진다는 점을 보이려고 넣었습니다.
앞 실습: 상속과 오버라이딩 · 다음 실습: 인터페이스와 추상 클래스