상속의 기본 원리
객체지향 프로그래밍에서 상속은 클래스 간의 관계를 정의하는 핵심 메커니즘입니다. 자바에서는 extends 키워드를 사용하여 한 클래스가 다른 클래스의 속성과 동작을 물려받도록 할 수 있습니다.
class Vehicle {
protected String brand;
public void startEngine() {
System.out.println("엔진을 시동합니다.");
}
}
class Car extends Vehicle {
private int doors;
public void openTrunk() {
System.out.println("트렁크를 엽니다.");
}
}
생성자 호출 순서
자식 클래스 인스턴스 생성 시 부모 클래스의 생성자가 먼저 실행됩니다. 명시적으로 호출하지 않으면 기본 생성자가 자동으로 호출됩니다.
class Animal {
public Animal() {
System.out.println("동물이 생성되었습니다.");
}
public Animal(String name) {
System.out.println(name + " 동물이 생성되었습니다.");
}
}
class Dog extends Animal {
public Dog() {
super("포메라니안");
System.out.println("강아지 객체 완료");
}
}
// 실행 결과:
// 포메라니안 동물이 생성되었습니다.
// 강아지 객체 완료
메서드 오버라이딩
하위 클래스는 상위 클래스의 메서드를 재정의할 수 있으며, 이는 다형성을 구현하는 기반이 됩니다.
class Shape {
public double calculateArea() {
return 0;
}
}
class Rectangle extends Shape {
private double width, height;
public Rectangle(double w, double h) {
this.width = w;
this.height = h;
}
@Override
public double calculateArea() {
return width * height;
}
}
class Circle extends Shape {
private double radius;
public Circle(double r) {
this.radius = r;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
super 키워드 활용
super 키워드는 부모 클래스의 멤버에 접근하거나 생성자를 호출할 때 사용됩니다.
class Electronics {
protected String model;
public Electronics(String model) {
this.model = model;
}
public void powerOn() {
System.out.println("기본 전원 켜기");
}
}
class Smartphone extends Electronics {
private int batteryLevel;
public Smartphone(String model, int battery) {
super(model);
this.batteryLevel = battery;
}
@Override
public void powerOn() {
super.powerOn();
System.out.println(model + " 스마트폰 전원 켜짐 (배터리: " + batteryLevel + "%)");
}
}
인터페이스를 통한 다중 상속 효과
자바는 클래스 단일 상속만 지원하지만, 인터페이스를 통해 유사한 효과를 얻을 수 있습니다.
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck extends Animal implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("오리가 날아갑니다.");
}
@Override
public void swim() {
System.out.println("오리가 수영합니다.");
}
}
final 키워드와 상속 제한
final로 선언된 클래스는 상속이 불가능하며, 메서드는 오버라이딩이 금지됩니다.
final class ImmutableConfig {
private final String version;
public ImmutableConfig(String ver) {
this.version = ver;
}
public final String getVersion() {
return version;
}
}
// class CustomConfig extends ImmutableConfig {} // 컴파일 에러
추상 클래스 상속
추상 클래스는 인스턴스화할 수 없지만, 공통적인 구조를 하위 클래스에게 제공합니다.
abstract class GameCharacter {
protected int health;
protected String name;
public GameCharacter(String characterName) {
this.name = characterName;
this.health = 100;
}
public abstract void specialAttack();
public void displayStatus() {
System.out.println(name + " 체력: " + health);
}
}
class Warrior extends GameCharacter {
public Warrior(String name) {
super(name);
}
@Override
public void specialAttack() {
System.out.println(name + "이(가) 강력한 베기 공격을 합니다!");
}
}
접근 제어와 상속
상속 관계에서 접근 제어자의 영향은 다음과 같습니다:
private: 하위 클래스에서 접근 불가protected: 같은 패키지 또는 하위 클래스에서 접근 가능public: 모든 위치에서 접근 가능- 패키지-private(default): 같은 패키지 내에서만 접근 가능
class BaseComponent {
private String secretKey = "private-data";
protected String internalId = "protected-data";
public String publicName = "public-data";
}
class ExtendedComponent extends BaseComponent {
public void accessTest() {
// System.out.println(secretKey); // 컴파일 에러
System.out.println(internalId); // 접근 가능
System.out.println(publicName); // 접근 가능
}
}
다형성과 타입 변환
상속을 통해 업캐스팅과 다운캐스팅이 가능해지며, 이는 다형성을 실현하는 핵심 요소입니다.
Shape[] shapes = {
new Rectangle(5.0, 3.0),
new Circle(2.5),
new Rectangle(4.0, 6.0)
};
for (Shape shape : shapes) {
System.out.println("면적: " + shape.calculateArea());
}