메서드 Overriding / Overloading
메서드 오버로딩(컴파일 다형성)
특징
- 하나의 클래스 내에 있는 같은 이름의 다른 파라미터 종류, 순서, 수량을 지닌 메서드들을 구현함을 의미한다.
- 메서드 시그니처에 속하지 않는 요소들은 상관하지 않는다. (예시: 반환값, 접근 제어자, 메서드 바디 등)
- 다양한 입력값(자료형 또는 수량)에 대해 비슷한 작업을 하려는 목적이다.
컴파일 타임 다형성의 의미
- 정적 바인딩(binding), 얼리 바인딩으로 불리기도 한다.
- 메서드의 시그니처, 메서드의 이름과 파라미터가 컴파일 타임에 확인(컴파일러가 확인함)되므로 컴파일 다형성이라고 불린다.
➕다른 이름의 같은 파라미터의 메서드도 메서드 오버로딩일까?
- 메서드 시그니처는 메서드 이름과 파라미터(순서, 수량, 타입)로 구성된다. 그럼 “같은 이름의 다른 파라미터” 말고 “다른 이름의 같은 파라미터”는 메서드 오버로딩이 될 수 없을까?
- 메서드 오버로딩은 개념적으로 동일한 작업을 여러가지 방식으로 수행하는 목적이 있다.
- 개념적으로 동일한 작업을 나타내는 수단이 메서드의 이름이다. 따라서 다른 이름의 같은 파라미터를 지닌 메서드들은 오버로딩 되었다고 할 수 없다.
-
코드 예시
// 올바른 오버로딩 class ValidOverloader { // Different parameter types void process(int x) { } void process(String s) { } // Different number of parameters void process(int a, int b) { } // Different parameter order void process(String s, int i) { } void process(int i, String s) { } } // 메서드 오버로딩이 아님 class NotOverloader { void process(String data) { } void handle(String data) { } // Different name = different method void transform(String data) { } // Different name = different method }
➕연관되는 클래스가 하나인데 왜 컴파일 “다형성”이지?
- OOP의 다형성은 “여러 자식 클래스들이 하나의 부모 클래스로 취급될 수 있다”는 원칙이다. → “메서드 오버로딩은 컴파일 다형성이라는데, 그럼 메서드 오버로딩도 여러 클래스들이 수반되는 개념일까?”
- ❗메서드 오버로딩은 “컴파일 다형성”이라 불리지만 하나의 클래스만 수반된다.
- ❗메서드 오버로딩의 다형성은 OOP의 다형성이 아니라 애드혹 다형성(ad-hoc polymorphism)에 포함되기 때문이다.
- 애드혹 다형성은 “컴파일 타임에 인자에 따라 호출될 메서드를 결정함”을 의미한다. OOP의 다형성과 다르다.
- 덧붙임. 다형성(Polymorphism)은 “다양한 형태”를 의미한다. 프로그래밍에서는 함수 호출, 다양한 데이터 타입을 다루는 인터페이스 등이 “형태”에 해당된다.
- 덧붙임. Java가 OOP 언어이기는 하지만 Java에 속한 모든 개념이 OOP임은 아니라는 점을 시사한다. (애드혹 다형성은 OOP의 개념이 아니다)
➕기술적이기보단 인간적인 개념이다
- 컴파일러는 메서드를 시그니처(이름 + 파라미터)로 식별한다. 즉 오버로딩 된 메서드들은 컴파일러에게는 각기 다른 메서드들일 뿐이며, 특별하게 오버로딩 된 메서드라고 인식하지 않는다.
- 메서드 오버로딩은 같은 개념의 작업을 다양한 방식으로 처리한다는 개발자들의 편의로 만들어진 개념이다.
코드 예시
-
수학적 작업
class MathOperations { // Overloaded method to add two integers int add(int a, int b) { return a + b; } // Overloaded method to add three integers (different number of parameters) int add(int a, int b, int c) { return a + b + c; } // Overloaded method to add two doubles (different parameter types) double add(double a, double b) { return a + b; } }
메서드 오버라이딩(런타임 다형성)
특징
- 위계 관계가 있는, 적어도 2개의 클래스들이 관련된다.
- 자식 클래스의 구현체는 부모 클래스의 메서드와 시그니처가 동일해야 한다. (같은 이름, 같은 파라미터 구조)
- 부모 클래스에 정의된 바를 따르면서도 자식 클래스에게 맞춤형인 작업을 수행하려는 목적이다.
런타임 다형성의 의미
- 동적 바인딩, 느린 바인딩으로도 불린다.
- 호출되는 실제 객체의 타입은 런타임 때 확인(JVM이 확인)되므로 런타임 다형성이라고 불린다.
-
코드 예시
// 참조값은 Animal, 실제 객체는 Dog // 컴파일러는 참조값(Animal)만 봄 // JVM에 런타임에 Dog 객체를 확인하고 // Dog의 메서드를 호출함 Animal myPet = new Dog(); myPet.makeSound();
➕메서드 오버라이딩의 규칙
- 자식 클래스의 구현체의 접근 제어자는 부모 클래스가 정의한 메서드의 접근 제어자와 동일하거나 범주가 더 넓어야 한다.
- ❌ 부모는 public → 자식은 private
- 자식 클래스의 구현체는 부모 클래스가 정의한 메서드에서 던지는 예외보다 더 넓은 범주의 확인된 예외를 던질 수 없다.
- ❌(부모 클래스가 IOException을 던진다면 자식은 Exception을 던질 수 없음)
- 확인되지 않은 예외의 경우 부모 클래스가 던지는 예외와 무관하게 아무 예외나 던질 수 있다.
- 자식 클래스가 static 메서드는 구현할 수 없다. (Method Hiding)
- static 메서드는 클래스 자체게 속하여 컴파일 때 인지되기 때문이다.
- 실제 객체의 타입을 확인하는 다형성은 런타임에 적용되는 원리이다.
- 자식 클래스가 부모 클래스의 메서드의 시그니처와 동일한 static 메서드를 만들면 Method Hiding이 발생한다.
// static 메서드는 컴파일 때 인식된다. // 컴파일러가 정적 메서드를 호출하는 대상은 // 참조되는 타입이다. 즉 Parent다. // 따라서 Parent의 static 메서드가 실행된다. // 자식의 static 메서드가 "숨겨진다". Parent p = new Child(); p.staticMethod(); - static 메서드는 클래스 자체게 속하여 컴파일 때 인지되기 때문이다.
- private 메서드, final 메서드도 구현할 수 없다.
- private 메서드는 자식의 시점에서 인지될 수 없다.
- final로 선언된 대상은 불변하기 때문에 구현할 수 없다.
- 자식 클래스의 구현체는 부모 클래스의 메서드가 반환하는 타입 또는 그 반환 타입의 서브 타입을 반환할 수 있다.
- @Override 어노테이션은 거의 필수다. 오타 확인, 의도치 않은 오버라이딩, 가독성 증진 등의 이점이 있다.
➕뭐가 메서드 시그니처가 아닌 것들에 대한 제약 사항을 강제하는가?
- 메서드 오버라이딩을 할 때 동일한 메서드 시그니처를 따라야 하는 것은 이해할 수 있다. 그래야 컴파일러가 동일한 메서드라고 인식할 테니. 그럼 “뭐가 메서드 시그니처가 아닌 것들(반환값, 접근 제어자 등)에 대해 제약을 거는 걸까?”
- 메서드 오버로딩에 대한 규칙은 Java Language Specification, 즉 JLS가 정의한다.
- JSL에 메서드 시그니처(이름 + 파라미터), 반환값, 접근 제어자, 예외 타입, final 메서드에 대해 명시되어 있다.
- 단! static 메서드를 구현할 수 없다는 것은 명시되어 있지 않다. 본질적으로 “static 메서드는 컴파일 때 참조되는 클래스에 따라 호출되기 때문에” 구현이 불가능 한 것이다.
-
코드 예시
class Parent { public static void staticMethod() { System.out.println("Parent static"); } public void instanceMethod() { System.out.println("Parent instance"); } } class Child extends Parent { // This is METHOD HIDING, not overriding public static void staticMethod() { System.out.println("Child static"); } // This is METHOD OVERRIDING @Override public void instanceMethod() { System.out.println("Child instance"); } } // Test the behavior Parent obj = new Child(); obj.staticMethod(); // Output: "Parent static" (reference type) obj.instanceMethod(); // Output: "Child instance" (object type)
코드 예시
-
동물 부모 클래스
class Animal { // Method to be overridden public void makeSound() { System.out.println("The animal makes a sound."); } } class Dog extends Animal { // Overriding the parent class method @Override public void makeSound() { System.out.println("The dog barks: Woof Woof!"); } } class Cat extends Animal { // Overriding the parent class method @Override public void makeSound() { System.out.println("The cat meows: Meow Meow!"); } }