Java 프로그래밍 언어에서 메서드 오버라이딩은 객체지향 프로그래밍의 중요한 개념 중 하나입니다. 이 글에서는 메서드 오버라이딩의 정의, 조건, 메모리 구조, 사용 예제, 오버로딩과의 비교, 접근 제어자에 대한 이해를 통해 이 개념을 깊이 있게 살펴보겠습니다.
메서드 오버라이딩 정의 및 조건
메서드 오버라이딩(Overriding)은 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 다시 정의하는 것을 의미합니다. 이를 통해 자식 클래스는 부모 클래스의 메서드를 자신의 필요에 맞게 수정하여 활용할 수 있습니다.
오버라이딩은 객체지향 프로그래밍의 다형성(polymorphism) 개념과 밀접하게 관련되어 있습니다. 메서드 오버라이딩이 이루어지기 위해서는 몇 가지 조건이 필요합니다.
-
메서드 선언부 일치: 오버라이딩은 부모 클래스의 메서드와 동일한 시그니처(메서드 이름, 매개변수 타입 및 순서)를 가져야 합니다. 반환 타입은 부모 클래스의 반환 타입으로 변환 가능한 타입으로 변경할 수 있습니다.
-
접근 제어자: 자식 클래스에서 정의한 메서드의 접근 제어자는 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없습니다. 즉, 부모 클래스의 메서드가
public
이라면 자식 클래스의 메서드도public
이어야 하며,protected
로는 변경할 수 없습니다. -
예외 처리: 부모 클래스의 메서드보다 더 많은 범위의 예외를 선언할 수 없습니다. 즉, 부모 클래스의 메서드가 어떤 예외를 던지는 경우, 자식 클래스의 오버라이드된 메서드에서는 해당 예외 또는 그 하위 예외만 던질 수 있습니다.
여기서 간단한 표를 통해 오버라이딩의 조건을 정리해 보겠습니다.
조건 | 설명 |
---|---|
메서드 선언부 일치 | 메서드 이름, 매개변수 타입 및 순서가 동일해야 하며, 반환 타입은 변환 가능한 타입으로 변경 가능 |
접근 제어자 | 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없음 |
예외 처리 | 부모 클래스의 메서드보다 더 큰 범위의 예외를 선언할 수 없음 |
이러한 조건을 기반으로 메서드 오버라이딩을 활용하면 자식 클래스는 부모 클래스의 기능을 확장하거나 수정할 수 있어 매우 유용합니다.
메모리 구조
Java에서 메서드 오버라이딩을 이해하기 위해서는 Java의 메모리 구조를 살펴보는 것이 필요합니다. Java는 크게 스택 메모리와 힙 메모리로 나눌 수 있습니다.
스택 메모리는 메서드 호출 시 생성된 프레임이 저장되는 공간이며, 힙 메모리는 객체가 생성되는 공간입니다. 클래스가 메모리에 로드될 때 클래스의 메서드들은 메서드 영역(Method Area)에 저장됩니다.
여기서 부모 클래스와 자식 클래스의 메서드가 둘 다 존재할 수 있으며, 오버라이딩된 메서드는 자식 클래스의 메서드가 호출될 때 실행됩니다. 예를 들어, 다음과 같은 메모리 구조를 상상해 볼 수 있습니다.
Parent
클래스와Child
클래스가 메서드 영역에 로드됩니다.Parent
클래스의display()
메서드와Child
클래스의display()
메서드가 메서드 영역에 저장됩니다.- 객체
pa
가Parent
타입의 인스턴스를 가리키고, 객체ch
가Child
타입의 인스턴스를 가리킵니다. Parent
타입의 참조 변수가Child
타입의 인스턴스를 가리킬 경우, 호출되는 메서드는Child
클래스의 오버라이딩된 메서드입니다.
아래 표는 메모리 구조를 시각적으로 정리한 것입니다.
메모리 영역 | 저장되는 내용 |
---|---|
메서드 영역 | Parent 클래스의 display() 메서드 |
Child 클래스의 display() 메서드 | |
스택 메모리 | Parent pa (Parent 클래스 인스턴스) |
Child ch (Child 클래스 인스턴스) | |
Parent pc (Child 클래스 인스턴스를 가리키는 Parent 타입) |
이러한 메모리 구조를 이해하면, 자바에서 메서드 오버라이딩이 어떻게 작동하는지를 더 명확하게 파악할 수 있습니다.
대표적인 사용 예
메서드 오버라이딩의 대표적인 사용 예를 통해 이 개념을 좀 더 구체적으로 이해해 보겠습니다. 아래는 Parent
클래스와 Child
클래스의 간단한 예제입니다.
“`java
class Parent {
void display() {
System.out.println(“부모 클래스의 display() 메소드입니다.”);
}
}
class Child extends Parent {
void display() {
System.out.println(“자식 클래스의 display() 메소드입니다.”);
}
}
public class InheritanceExample {
public static void main(String[] args) {
Parent pa = new Parent();
pa.display(); // 부모 클래스의 display() 메소드입니다.
Child ch = new Child();
ch.display(); // 자식 클래스의 display() 메소드입니다.
Parent pc = new Child();
pc.display(); // 자식 클래스의 display() 메소드입니다.
}
}
“`
위의 예제에서, Parent
클래스는 display()
메서드를 가지고 있으며, 이를 Child
클래스에서 오버라이딩하여 자신만의 display()
메서드를 정의합니다. 이제 메서드 오버라이딩의 결과를 표로 정리해 보겠습니다.
객체 타입 | 호출된 메서드 |
---|---|
Parent pa | 부모 클래스의 display() 메소드입니다. |
Child ch | 자식 클래스의 display() 메소드입니다. |
Parent pc | 자식 클래스의 display() 메소드입니다. |
이 예제에서 볼 수 있듯이, Parent
타입의 참조 변수가 Child
타입의 인스턴스를 가리킬 때도 자식 클래스에서 오버라이딩된 메서드가 호출됩니다. 이를 통해 다형성을 실현하게 됩니다.
메서드 오버라이딩과 오버로딩 간단 비교
메서드 오버라이딩과 메서드 오버로딩은 유사한 점이 있지만, 개념적으로는 완전히 다릅니다. 오버라이딩은 부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의하는 것이고, 오버로딩은 같은 이름의 메서드를 서로 다른 매개변수 시그니처로 정의하는 것입니다.
메서드 오버로딩의 예를 보면 다음과 같습니다.
“`java
class Parent {
void display() {
System.out.println(“부모 클래스의 display() 메소드입니다.”);
}
}
class Child extends Parent {
void display() {
System.out.println(“자식 클래스의 display() 메소드입니다.”);
}
void display(String str) {
System.out.println(str);
}
}
public class InheritanceExample {
public static void main(String[] args) {
Child ch = new Child();
ch.display(); // 자식 클래스의 display() 메소드입니다.
ch.display(“오버로딩된 display() 메소드입니다.”); // 오버로딩된 메소드 호출
}
}
“`
위 코드에서 Child
클래스는 display()
메서드를 오버라이딩하고, 같은 이름의 display(String str)
메서드를 오버로딩합니다. 이를 통해 사용자는 매개변수의 타입이나 개수에 따라 적절한 메서드를 호출할 수 있습니다.
이 두 가지 개념의 차이를 표로 정리해 보겠습니다.
항목 | 메서드 오버라이딩 | 메서드 오버로딩 |
---|---|---|
정의 | 부모 클래스의 메서드를 자식 클래스에서 재정의 | 같은 이름의 메서드를 서로 다른 매개변수로 정의 |
시그니처 | 동일해야 함 | 서로 달라야 함 |
반환 타입 | 부모 클래스의 반환 타입으로 변환 가능 | 반환 타입은 상관없음 |
이와 같은 비교를 통해 개발자는 두 개념의 차이를 보다 명확하게 이해할 수 있습니다.
접근 제어자 비교
메서드 오버라이딩에서 접근 제어자는 매우 중요한 역할을 합니다. 접근 제어자는 클래스와 메서드의 가시성을 결정하며, 이를 통해 코드의 보안성을 높이고, 잘못된 접근을 방지할 수 있습니다.
부모 클래스에서 정의한 메서드의 접근 제어자가 public
또는 protected
인 경우, 자식 클래스에서 해당 메서드를 오버라이딩할 때 접근 제어자를 더 좁은 범위로 변경할 수 없습니다. 예를 들어, public
메서드를 private
으로 변경하는 것은 허용되지 않습니다.
아래의 예제를 통해 접근 제어자의 영향을 살펴보겠습니다.
“`java
class Parent {
public void display() {
System.out.println(“부모 클래스의 display() 메소드입니다.”);
}
}
class Child extends Parent {
// 오류 발생: 부모 클래스의 메서드를 private로 변경할 수 없음
// private void display() {
// System.out.println(“자식 클래스의 display() 메소드입니다.”);
// }
@Override
public void display() {
System.out.println("자식 클래스의 display() 메소드입니다.");
}
}
“`
위의 코드에서 주석 처리된 부분처럼 부모 클래스의 display()
메서드를 private
로 변경하려고 하면 컴파일 오류가 발생합니다. 이는 접근 제어자가 잘못 설정된 예입니다.
이와 관련된 내용을 표로 정리해 보겠습니다.
접근 제어자 | 가능한 경우 | 불가능한 경우 |
---|---|---|
public | 자식 클래스에서 동일하게 사용 가능 | private로 변경 불가 |
protected | 자식 클래스에서 동일하게 사용 가능 | private로 변경 불가 |
default (package) | 동일 패키지 내의 자식 클래스에서 사용 가능 | private 또는 protected로 변경 불가 |
private | 자식 클래스에서 접근 불가 | 오버라이딩 불가 |
이 표를 통해 접근 제어자의 규칙을 보다 명확하게 이해할 수 있습니다.
결론
Java의 메서드 오버라이딩은 객체지향 프로그래밍의 핵심 개념으로, 상속 관계에 있는 클래스 간의 관계를 더욱 유연하게 만들어 줍니다. 오버라이딩을 통해 자식 클래스는 부모 클래스의 메서드를 필요에 맞게 수정할 수 있으며, 이는 다형성을 활용하는 데 중요한 역할을 합니다.
이 글에서 다룬 내용을 통해 메서드 오버라이딩에 대한 이해를 높이고, 자바 프로그래밍에서 이를 적절히 활용할 수 있는 기회를 제공하기를 바랍니다. 다양한 예제를 통해 기본 개념을 확립하고, 이를 실제 프로젝트나 학습에 적용해 보시길 권장합니다.