Java의 상속(Inheritance)과 메서드 오버라이딩(Method Overriding)
Java는 객체지향 프로그래밍(Object-Oriented Programming)을 기반으로 하는 언어로, 클래스를 이용하여 데이터를 추상화하고 다양한 객체를 생성하여 사용할 수 있습니다. 이 중 상속(Inheritance) 은 코드의 재사용성과 유지보수성을 높이는 중요한 개념 중 하나입니다. 이번 글에서는 Java의 상속, 메서드 오버라이딩, 그리고 관련된 주요 개념들을 예제와 함께 설명하고자 합니다.
1. 상속(Inheritance) 개념과 특징
상속은 객체지향 프로그래밍에서 이미 정의된 클래스의 특성과 동작을 물려받아 새로운 클래스를 정의하는 것을 의미합니다. 상속을 통해 자식 클래스는 부모 클래스의 멤버 변수와 메서드를 재사용하고, 필요에 따라 추가적인 멤버와 메서드를 정의할 수 있습니다.
1.1 상속의 특징
- 단일 상속(Single Inheritance): Java에서는 하나의 클래스가 오직 하나의 부모 클래스만 상속받을 수 있습니다. 즉, 자식 클래스는 둘 이상의 부모 클래스를 가질 수 없습니다. 이는 다중 상속의 복잡성을 피하고 코드의 모호성을 줄이기 위함입니다.
- 계층적 상속(Hierarchical Inheritance): 여러 자식 클래스가 하나의 부모 클래스를 상속할 수 있습니다. 이를 통해 클래스 간의 계층적 구조를 형성할 수 있습니다.
- super 키워드를 사용하여 부모 클래스의 멤버에 접근할 수 있습니다. 특히 부모 클래스의 생성자나 메서드를 호출할 때 유용합니다.1.2 상속의 장점
- 코드 재사용성: 이미 정의된 클래스를 상속받아 확장함으로써 코드의 중복을 줄일 수 있습니다.
- 유지보수성 향상: 상속을 사용하면 변경 사항이 부모 클래스에서 관리될 수 있어, 자식 클래스의 수정이 최소화됩니다.
- 다형성(Polymorphism): 상속을 통해 동일한 이름의 메서드를 자식 클래스에서 다양한 방식으로 구현할 수 있습니다. 이는 프로그램의 유연성을 높입니다.
2. 클래스 상속(Class Inheritance) 구현하기
자바에서는 extends 키워드를 사용하여 클래스 간의 상속을 구현합니다. 부모 클래스의 멤버(필드와 메서드)는 자식 클래스에서 접근이 가능하며, 자식 클래스는 부모 클래스의 기능을 확장하거나 수정할 수 있습니다.
// 부모 클래스 정의
class Vehicle {
// 부모 클래스의 필드
private int maxSpeed;
// 부모 클래스의 생성자
public Vehicle(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
// 부모 클래스의 메서드
public void displayMaxSpeed() {
System.out.println("Maximum Speed: " + maxSpeed + " km/h");
}
}
// 자식 클래스 정의
class Car extends Vehicle {
private String model;
public Car(int maxSpeed, String model) {
// 부모 클래스의 생성자 호출
super(maxSpeed);
this.model = model;
}
// 자식 클래스에서 추가된 메서드
public void displayModel() {
System.out.println("Car Model: " + model);
}
}
// 메인 클래스
public class Main {
public static void main(String[] args) {
// 부모 클래스의 인스턴스 생성
Vehicle vehicle = new Vehicle(150);
vehicle.displayMaxSpeed();
// 자식 클래스의 인스턴스 생성
Car car = new Car(200, "Sedan");
car.displayMaxSpeed(); // 부모 클래스의 메서드 호출
car.displayModel(); // 자식 클래스의 메서드 호출
}
}
위 예제에서 Car 클래스는 Vehicle 클래스를 상속받고, Vehicle 클래스의 displayMaxSpeed() 메서드를 그대로 사용할 수 있습니다. 또한 Car 클래스에만 존재하는 displayModel() 메서드를 추가하여 고유의 기능을 확장하였습니다.
3. 메서드 오버라이딩(Method Overriding)
메서드 오버라이딩은 자식 클래스가 부모 클래스의 메서드를 재정의하여 자신의 기능을 제공하는 것을 의미합니다. 자식 클래스에서 부모 클래스의 메서드를 재정의할 때는, 메서드의 이름, 매개변수 목록, 반환 타입이 모두 동일해야 합니다.
메서드 오버라이딩의 규칙
- 자식 클래스에서 오버라이딩하는 메서드는 부모 클래스의 메서드와 반환 타입과 매개변수 목록이 동일해야 합니다.
- 자바에서는 @Override 어노테이션을 사용하여 오버라이딩을 명시적으로 표시할 수 있습니다. 컴파일러가 해당 메서드가 실제로 오버라이딩되었는지 여부를 확인하므로, 실수를 방지할 수 있습니다.
- 부모 클래스의 메서드가 final 키워드로 선언된 경우, 해당 메서드는 자식 클래스에서 오버라이딩할 수 없습니다.
// 부모 클래스
class Animal {
void speak() {
System.out.println("동물이 소리를 냅니다.");
}
}
// 자식 클래스
class Dog extends Animal {
@Override
void speak() {
System.out.println("멍멍!");
}
}
// 자식 클래스
class Cat extends Animal {
@Override
void speak() {
System.out.println("야옹!");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.speak();
Dog dog = new Dog();
dog.speak(); // 오버라이딩된 메서드 호출
Cat cat = new Cat();
cat.speak(); // 오버라이딩된 메서드 호출
}
}
위 예제에서 Dog 클래스와 Cat 클래스는 Animal 클래스의 speak() 메서드를 오버라이딩하여 자신만의 동작을 제공합니다.
4. 상속과 접근 제어자
상속 관계에서 자식 클래스가 부모 클래스의 멤버에 접근할 수 있는 범위는 접근 제어자(Access Modifiers) 에 의해 결정됩니다. 자바에서는 private, protected, public, 그리고 default(패키지 접근) 네 가지 접근 제어자가 존재합니다.
접근 제어자의 영향
- private: 부모 클래스의 private 멤버는 자식 클래스에서 직접 접근할 수 없습니다. 만약 자식 클래스에서 접근해야 한다면, 부모 클래스에서 public 또는 protected 접근 제어자를 통해 접근자 메서드를 제공해야 합니다.
- protected: protected 멤버는 자식 클래스에서 직접 접근할 수 있으며, 패키지 외부의 다른 클래스에서는 접근할 수 없습니다.
- default: 같은 패키지 내의 클래스들에서 접근이 가능하며, 패키지 외부에서는 접근이 불가능합니다.
- public: 모든 클래스에서 접근이 가능합니다.
5. 메서드 오버로딩 vs. 오버라이딩
오버로딩과 오버라이딩은 메서드 이름의 재사용을 나타내는 개념으로, 서로 다른 특성을 가집니다.
- 메서드 오버로딩(Method Overloading): 동일한 클래스 내에서 같은 이름의 메서드를 서로 다른 매개변수 목록으로 정의하는 것입니다. 반환 타입은 달라도 상관없습니다.
- 메서드 오버라이딩(Method Overriding): 자식 클래스가 부모 클래스의 메서드를 재정의하는 것을 의미합니다. 반환 타입, 메서드 이름, 매개변수 목록이 모두 동일해야 하며, 동적 바인딩과 다형성의 구현에 사용됩니다.
class MathOperations {
// 메서드 오버로딩
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}
class AdvancedMath extends MathOperations {
@Override
public int add(int a, int b) {
System.out.println("AdvancedMath의 add() 메서드 호출");
return super.add(a, b); // 부모 클래스의 메서드 호출
}
}
위 예제에서 MathOperations 클래스의 add 메서드는 오버로딩을 사용하고, AdvancedMath 클래스의 add 메서드는 부모 클래스의 add 메서드를 오버라이딩하여 재정의합니다.
6. 결론
Java에서 상속(Inheritance) 과 메서드 오버라이딩(Method Overriding)은 객체지향 프로그래밍의 중요한 개념으로, 코드의 재사용성과 유지보수성을 높이고 유연한 설계가 가능하도록 합니다. 상속을 통해 기존 클래스의 특성을 물려받아 새로운 클래스를 손쉽게 확장하고, 메서드 오버라이딩을 통해 부모 클래스의 동작을 자식 클래스에서 재정의하여 다형성을 구현할 수 있습니다.
이러한 개념들은 복잡한 애플리케이션에서 객체 간의 관계를 체계적으로 구성하고, 개발자가 변경사항을 최소화하며 코드의 일관성을 유지할 수 있도록 합니다. 특히, 메서드 오버라이딩을 사용하면 자식 클래스가 부모 클래스의 동작을 자신의 방식으로 재정의하여 다형성을 구현할 수 있으며, 부모 클래스의 참조 변수로 다양한 자식 클래스의 객체를 다룰 수 있어 코드의 유연성이 크게 증가합니다.
상속을 올바르게 사용하기 위해서는 다음과 같은 점들을 항상 고려해야 합니다:
- 단일 상속 원칙: 자바는 다중 상속을 지원하지 않으므로, 필요에 따라 인터페이스나 추상 클래스를 활용하여 다중 상속의 이점을 취할 수 있습니다.
- 접근 제어자의 영향: 상속 관계에서 private, protected, public 등의 접근 제어자가 어떻게 동작하는지 정확히 이해하고 사용해야 합니다.
- 클래스 간의 강한 결합을 피하기: 상속 관계를 사용할 때, 클래스 간의 강한 결합이 발생할 수 있습니다. 이로 인해 한 클래스의 변경이 다른 클래스에 영향을 미칠 수 있으므로, 상속보다는 구성(Composition) 을 고려할 때도 있습니다.
- 메서드 오버라이딩의 적절한 활용: 메서드 오버라이딩을 통해 동작을 확장할 때, 부모 클래스의 메서드와 동일한 시그니처를 가져야 하며, 필요에 따라 super 키워드를 사용해 부모 클래스의 메서드를 호출하는 것을 고려해야 합니다.
Java에서 상속과 메서드 오버라이딩을 활용하는 것은 객체지향의 기본이자 필수적인 개념입니다. 그러나 무분별한 상속 사용은 오히려 코드의 복잡도를 높이고 유지보수성을 떨어뜨릴 수 있으므로, 언제 상속을 사용하고 언제 사용하지 않을지를 신중하게 결정하는 것이 중요합니다.
이번 글을 통해 상속과 오버라이딩의 개념, 장점, 그리고 주의사항을 충분히 이해하고, 실무에서 올바르게 사용할 수 있기를 바랍니다. 객체지향의 핵심인 상속을 잘 이해하고 활용하여, 보다 효율적이고 유연한 Java 애플리케이션을 개발해 보세요!
'JAVA > JAVA 기초' 카테고리의 다른 글
Java의 Generics, Enum, 그리고 Annotation (0) | 2024.10.03 |
---|---|
Java 추상 클래스(Abstract Class)와 인터페이스(Interface) 이해하기 (0) | 2024.10.02 |
Java 메서드 오버로딩 (Method Overloading) (0) | 2024.09.30 |
Java 메서드(Method) (0) | 2024.09.29 |
Java의 클래스(Class)와 객체(Object) (0) | 2024.09.28 |