앞서 다형성을 설명하고 대표적인 오버라이딩과 오버로딩 개념과 예시를 설명했고 마지막으로 다형성을 사용 시 소프트웨어 개발 3원칙(KISS, YAGNI, DRY) 중 YAGNI(필요한 작업만 해라!)를 주의하라는 말과 함께 마무리했다.
이번엔 오버로딩과 오버라이딩이 있는 프로그램을 우리가 작성하고 실행을 눌렀을 때 어떻게 되는지 그리고 실무 적용을 좀 더 깊게 알아보고 답변을 적어볼 생각이다.
프로그램 실행 흐름도

1. 코딩
우리가 Main.java를 작성하고 실행 버튼을 눌렀을 때
Animal myPet = new Dog(); // 다형성
// 상황 A: 정적 바인딩 대상 (Overloading)
calc.add(10, 20);
// 상황 B: 동적 바인딩 대상 (Overriding)
myPet.makeSound();
저장을 한 후 컴파일을 진행한다.
2. 컴파일 타임
컴파일 담당은 javac 자바 컴파일러가 진행하며,. java 파일을 기계가 읽기 쉬운 바이트 코드(.class)로 변환한다.
이때, 정적 바인딩(오버로딩)이 발생합니다.
1) 상황 A : calc.add 발견
javc 자바 컴파일러는 add 메서드이고 매개변수가 (int, int) 두 개를 확인한다.
그 후, 바이트코드에 반드시 add 메서드 주소로 확정 짓는다.
바이트 코드를 열어보면, 정적 바인딩된 코드는 invokestatic이나 invokespecial 같은 명령어로 대상이 명확하게 지정되어 있다.
2) 상황 B : myPet.makeSound 발견
javac 컴파일러는 변수 타입이 Animal를 확인하고 Animal에 makeSound 메서드가 존재하는지 확인한 후 예외를 발생시키지 않는다. 하지만, new Dog()는 실행해 봐야 알 수 있기 때문에, 바이트 코드에 invokevirtual이라는 명령어로 되어 있어 실제 객체의 타입을 보고 호출하라고 적은 후 JVM에게 넘겨버린다.
이렇게 컴파일 완료하면, .class 파일을 생성한다.
3. 클래스 로딩
JVM 안에 클래스 로더가 .class 파일을 메모리에 올린다.
이때 메인 메서드 시작된다.
4. 런타임
JVM은 코드를 한 줄씩 읽으며 실행한다. 이때, 동적 바인딩(오버라이딩)이 발생한다.
실행 흐름이 myPet.makeSound(); 라는 라인에 도착했을 때, JVM은 myPet 변수가 가리키는 실제 객체(Heap 메모리)가 누구인지 확인한다. 실제 객체가 Dog라는 객체가 생성되어 Heap 메모리에 적재되어 있기 때문에, Animal의 makeSound가 아닌 Dog에서 오버라이딩한 makeSound 메서드가 실행된다.

조금 더 자세히 알아보면, 첫 번째 이미지에서 JVM은 자바 인터프리터, JIT 컴파일러, 컴파일 시스템이 존재하는데, 동적 바인딩은 자바 인터프리터와 JIT 컴파일러 둘 다 처리한다.
- 초반에 자바 인터프리터가 클래스 로더가 불러온 바이트 코드를 한 줄씩 읽는다.
이때, 동적 바인딩인 오버라이딩을 만나면, 실제 객체가 무엇인지 확인하기 위해 힙 메모리(런타임 시스템 쪽)를 확인한다.
확인을 마친 인터프리터는 메서드를 실행하도록 연결한다. - 만약 오버라이딩 메서드가 자주 호출되었다면, JIT 컴파일러가 움직인다.
Hot Spot으로 인지하고 JIT 컴파일러가 매번 타입을 확인하는 것은 불필요하므로, 이 동적 바인딩 과정을 기계어로 최적화해서 빠르게 실행할 수 있도록 바꿔버린다.
5. 프로그램 종료
모든 코드를 순회한 후 메모리 해제 및 프로그램을 종료한다.
이렇게 프로그램 하나의 생명주기에 컴파일 타임(Static)과 런타임 시점(Dynamic)이 발생한다.
질문의 의도
오버로딩과 오버라이딩의 개념이 아닌 지원자가 어느 정도의 지식 깊이와 코딩 스타일을 한 번에 파악할 수 있는 질문이라고 생각한다.
1. 기본적인 문법을 헷갈리지 않는가? ( Syntax Check )
오버라이딩은 상속 관계이고 오버로딩은 같은 클래스 내의 차이를 명확히 구분하는 가? 라는 질문일 수 있다고 생각한다.
그렇기에 답변은 오버로딩은 적재, 오버라이딩은 덮어쓰기라는 뉘앙스로 명확하게 구분해 말하면 될 것이다.
2. 작동 원리를 알고 있는가? ( Deep Dive )
오버로딩은 단순히 메서드의 매개변수의 타임과 개수가 다르다. 라고만 대답하면 안 된다.
앞에서 시점을 추가로 덧붙여 대답해야 한다.
javac 컴파일러가 .java 파일을 바이트 코드(.class 파일)로 변경하는 과정 중 정적 바인딩인 오버로딩은 매개변수 개수와 타입을 보고 예외 발생을 할지, invokestatic 또는 invokespecial를 적을지 판단한다.
이번엔 동적 바인딩인 오버라이딩을 만나면, 참조변수의 상속 관계를 확인하고 어떤 객체가 올지 실행해 봐야 알 수 있기 때문에 invokevirtual이라고 표시해 두고 JVM에 넘긴다.
3. 객체지향 설계를 이해하고 있는가? ( Why Use? )
"오버로딩과 오버라이딩을 왜 사용하는가?"라는 질문의 의도는 "클린코드(가독성)와 유지보수성(확장성)을 고민해보았는가?"이다.
오버로딩의 목적은 메서드 이름을 통일하여 코드의 가독성을 높이기 위함이고 오버라이딩의 목적은 다형성을 통해 유연한 설계를 하고, 기존 코드를 수정하지 않고도 기능을 확장하기 위함이다.
여기서 유연한 설계는 요구사항이 바뀌거나 새로운 기능이 추가될 때, 기존 코드를 거의 건들지 않고도 대처할 수 있는 구조를 말한다.
오버로딩과 오버라이딩을 물어볼 때 나올 만한 질문
1. 매개변수는 같은 상황에서 리턴 타입만 다르게 해서 오버로딩이 가능한가요?
불가능합니다. 호출하는 시점에서 int a = 메서드()처럼 받지 않고, 그냥 메서드()라고만 호출 했을 때, 컴파일러는 리턴타입이 무엇인지 알 방법이 없어서 모호성 에러가 발생한다.
2. 자식 클래스에서 오버라이딩할 때 접근 제어자(public, private 등)를 부모보다 좁게 설정할 수 있나요?(LSP: 리스코프 치환 원칙에 대한 질문)
불가능 합니다. Parnet p = new Child()라고 할 때, 부모가 public이라서 p.method() 호출할 경우 실제 객체인 자식인 private으로 막혔다면, 실행 중 접근 불가 에러가 발생하기 때문에 부모-자식 간의 계약을 위반하는 것입니다.
3. 오버라이딩할 때 @Override를 꼭 붙여야 하나요? 안 붙여도 돌아가잖아요?
필수는 아니지만, 반드시 붙이는 것이 좋은 습관입니다.
개발자가 실수로 메서드명 오타를 낼 경우, 어노테이션이 없으면, 컴파일러는 새로운 메서드 추가로 인식해 버려 오버라이딩이 안됩니다. 반대로 @Orverride를 붙이면, 컴파일러가 "부모 클래스에 이런 메서드가 존재하지 않는다" 라는것을 인지하고 컴파일 에러로 잡아주기 때문입니다.
'Daily Dev Q&A 정리 템플릿' 카테고리의 다른 글
| 25.12.08 자바의 Collections Framework에 대해 설명해보기 (0) | 2025.12.08 |
|---|---|
| 25.12.07 SOLID원칙에 대해 (1) | 2025.12.07 |
| 25.12.03 다형성을 좀 더 구체적으로 설명해주세요! 라는 질문에 대비하기 (0) | 2025.12.03 |
| 25.12.02 상속과 컴포지션(Composition)의 차이와, 언제 컴포지션을 사용할까? (0) | 2025.12.02 |
| 25.12.01 객체지향 언어 개념과 자바는 어떤 언어인가 (0) | 2025.12.01 |