jsleee 2024. 11. 9. 15:27

개발을 할 때 기능을 단위로, class 단위로 개발을 하기 때문에 각각의 class 에는 공통 요소가 있을 수 밖에 없다. 

이럴때, 중요한것이 상속이다. 

상속을 통하여 불필요한 중복된 기능을 이중으로 만드는 일을 피할 수 있다.

 

상속을 이용한 간단한 예시를 한번 보자.

// 부모 클래스 정의
class Animal {
    // 필드
    String name;

    // 메서드
    void eat() {
        System.out.println(name + "이(가) 먹고 있습니다.");
    }
}

// 자식 클래스 Dog 정의
class Dog extends Animal {
    // Dog 클래스만의 메서드
    void bark() {
        System.out.println(name + "이(가) 짖고 있습니다.");
    }
}

// 자식 클래스 Cat 정의
class Cat extends Animal {
    // Cat 클래스만의 메서드
    void meow() {
        System.out.println(name + "이(가) 야옹하고 있습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Dog 클래스의 인스턴스 생성
        Dog myDog = new Dog();
        myDog.name = "바둑이";
        myDog.eat(); // 출력: 바둑이이(가) 먹고 있습니다.
        myDog.bark(); // 출력: 바둑이이(가) 짖고 있습니다.

        // Cat 클래스의 인스턴스 생성
        Cat myCat = new Cat();
        myCat.name = "나비";
        myCat.eat(); // 출력: 나비이(가) 먹고 있습니다.
        myCat.meow(); // 출력: 나비이(가) 야옹하고 있습니다.
    }
}

다음과 같이 animal이라는 부모 클래스에 name 필드, eat() 메서드를 공유하므로서 코드를 효율적이고 직관적으로 짜는 게 가능하다. 

 

 

단일 상속

상속을 사용하기 위해선 위 코드의 자식 클래스에서 했던것과 같이 extends 키워드를 사용해야 한다.

또한 자바에서는 단일 상속만 허용하므로 extends 대상은 하나만 선택할 수 있다.

 

상속과 메모리 구조

new Dog()를 호출하게 되면 Dog 뿐만 아니라 부모 클래스인 Animal도 자동으로 호출된다. 

즉, 자식클래스를 호출하면 부모와 지식이 모두 생성되고 공간도 분리 된다. 

 

상속과 메서드 오버라이딩

가끔 부모에 있는 메서드를 그대로 사용하지 않고 다르게 재정의 하고 싶을 수 있다. 

이때 메서드 오버라이딩을 사용한다. 

 

아래 코드를 보자.

// 부모 클래스 Animal 정의
class Animal {
    // 메서드 eat 정의
    void eat() {
        System.out.println("동물이 음식을 먹고 있습니다.");
    }
}

// 자식 클래스 Dog 정의
class Dog extends Animal {
    // 메서드 eat 오버라이딩
    @Override
    void eat() {
        System.out.println("강아지가 사료를 먹고 있습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 부모 클래스 타입으로 인스턴스 생성
        Animal myAnimal = new Animal();
        myAnimal.eat(); // 출력: 동물이 음식을 먹고 있습니다.

        // 자식 클래스 타입으로 인스턴스 생성
        Dog myDog = new Dog();
        myDog.eat(); // 출력: 강아지가 사료를 먹고 있습니다.

        // 부모 클래스 참조로 자식 클래스의 인스턴스 사용
        Animal anotherDog = new Dog();
        anotherDog.eat(); // 출력: 강아지가 사료를 먹고 있습니다.
    }
}

위 코드는 Dog class 가 eat() 이라는 메서드를 재정의(오버라이딩) 하였다. 

 

코드에서 확인할 수 있듯이 메서드가 오버라이딩 되면 우선 호출한 인스턴스의 메서드를 확인한다. 만약 있으면 그 메서드를 호출하고, 없다면 부모 클래스로 가서 메서드를 호출한다. 

 

@Override

위 코드에서 볼수 있듯이 다음 애노테이션을 사용한것을 알 수 있는데, 이는 상위 클래스의 매서드를 오버라이드하는 것임을 나타낸다. 

이는 필수는 아니지만 오버라이딩을 잘못한 경우 컴파일 오류를 발생시켜주므로 붙여주는것이 좋다. 

 

super

부모와 자식의 필드명이 같거나 매서드가 오버라이딩 되어 있으면, 자식에서 부모의 필드나 메서드를 호출할 수 없다.

이때 이를 가능하게 하는게 super 키워드이다. 

 

// 부모 클래스 Animal 정의
class Animal {
    // 메서드
    void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 Dog 정의
class Dog extends Animal {
    // 메서드 sound 오버라이딩
    @Override
    void sound() {
        // 부모 클래스의 sound 메서드 호출
        super.sound();
        System.out.println("강아지가 멍멍 짖고 있습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Dog 클래스의 인스턴스 생성
        Dog myDog = new Dog();
        myDog.sound(); 
        // 출력:
        // 동물이 소리를 냅니다.
        // 강아지가 멍멍 짖고 있습니다.
    }
}

다음 코드에서 볼수 있듯이 super.sound() 를 통하여 부모 클래스의 sound 메서드를 호출한걸 확인할 수 있다.

 

super - 생성자

! 상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다. !
상속관계에서 부모의 생성자를 호출할 때는 super()를 사용하면 된다.

// 부모 클래스 Animal 정의
class Animal {
    // 필드
    String name;

    // 부모 클래스의 생성자
    Animal(String name) {
        this.name = name;
        System.out.println("Animal 생성자 호출: " + name + "이(가) 생성되었습니다.");
    }
}

// 자식 클래스 Dog 정의
class Dog extends Animal {
    // 추가 필드
    String breed;

    // 자식 클래스의 생성자
    Dog(String name, String breed) {
        // 부모 클래스의 생성자 호출
        super(name);
        this.breed = breed;
        System.out.println("Dog 생성자 호출: " + breed + " 종류의 " + name + "이(가) 생성되었습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Dog 클래스의 인스턴스 생성
        Dog myDog = new Dog("바둑이", "시바견");
        // 출력:
        // Animal 생성자 호출: 바둑이이(가) 생성되었습니다.
        // Dog 생성자 호출: 시바견 종류의 바둑이이(가) 생성되었습니다.
    }
}

다음과 같이 super() 을 이용하여 부모 생성자를 호출 할수 있다.

단, 부모클래스가 기본생성자인경우, super()을 생략할 수 있다.