extends와 implements

niz w·2025년 2월 5일

Spring

목록 보기
15/17

수업을 들을 때 배웠던 부분인데...
실제로 사용하는 경우가 많지 않다보니 너무 잊어가고 있어 정리하려고 한다!




✨ extends

클래스상속받을 때 사용한다.
부모 클래스의 속성을 자식 클래스가 물려받게 되는데... 다중 상속은 불가능하다!
물려받는 과정에서 메서드를 재정의(오버라이딩) 할 수 있다.


예시

// 부모 클래스 (SuperClass)
class Animal {
    String name = "동물";

    void sound() {
        System.out.println("동물 소리");
    }
}

// 자식 클래스 (SubClass)
class Dog extends Animal {
    void sound() {
        System.out.println("멍멍!");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        System.out.println(dog.name);
        dog.sound();
    }
}

=> 출력 결과는 "동물" / "멍멍" 이다.

보이는 것처럼 부모에서 설정한 sound()라는 메서드를 Dog 클래스에서 재정의 했으며,
상속받았기 때문에 자식 클래스 객체에서 부모 클래스의 변수를 가져다 사용할 수 있다. (dog.name)




✨ implements

인터페이스를 구현할 때 사용한다.
상속과 달리 다중 구현이 가능해진다!
하지만 인터페이스는 추상 메서드만 가지므로, 반드시 메서드 구현이 필요하고!
인터페이스 자체로는 객체 생성이 불가하다.

여기서 반드시 구현이 필요하단 건 사용하기 위해 필요하다는 의미가 아니라,
implements를 하게 되면 해당 interface에 있는 추상 메서드는 모두!! 필수로 구현해야 한다.


예시

// 인터페이스 정의
interface Animal {
    void sound();
}

// 인터페이스 구현 (implements)
class Cat implements Animal {
    public void sound() {
        System.out.println("야옹!");
    }
}

public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.sound();
    }
}

=> 출력 결과는 "야옹" 이다.

인터페이스에 sound()라는 추상 메서드가 정의되어 있고, Cat에서 구현을 진행했다.


🚨 그럼 인터페이스는 변수를 정의할 수 없나?!?
인터페이스설계도의 역할이기 때문에 이미 구현된 내용을 가질 수는 없다.
대신 final static을 붙여서 상수는 정의할 수 있다.

interface Animal {
    int MAX_AGE = 20;
}

static final int MAX_AGE = 20; 이라고 해도 되고 그냥 타입과 변수명, 값만 써도 static final이 적용된다.

class Dog implements Animal {
    void printMaxAge() {
        System.out.println("최대 나이: " + MAX_AGE);
    }
}

그럼 해당 값을 implements클래스에서 바로 사용할 수 있게 된다~!




✨ 비교

구분 extends implements
적용 대상 클래스(부모-자식 관계) 인터페이스
다중 적용 불가능 (단일 상속만 가능) 가능 (여러 개 인터페이스 구현 가능)
메서드 재정의 선택적 (오버라이딩 가능) 필수 (모든 메서드를 구현해야 함)
사용 목적 코드 재사용 (속성과 기능 상속) 강제 규칙 (구현해야 할 기능 지정)

예시

// 부모 클래스 (SuperClass)
class Animal {
    void eat() {
        System.out.println("먹는다.");
    }
}

// 인터페이스 정의
interface Sound {
    void makeSound();
}

// 자식 클래스 (부모 클래스를 상속 + 인터페이스 구현)
class Dog extends Animal implements Sound {
    public void makeSound() {  // 인터페이스의 메서드 구현
        System.out.println("멍멍!");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();      // 상속 메서드
        dog.makeSound(); // 인터페이스 구현 메서드 호출
    }
}

=> 출력 결과는 "먹는다." / "멍멍!" 이다.

Animal에서 생성한 eat() 메서드는 재정의를 해도 되지만, 하지 않고 그대로 이용!
Sound에서 정의한 makeSound() 메서드는 Dog 자체에서 구현하여 이를 호출했다!




📣 결과적으로, 공통 기능을 각 클래스에서 재정의하고 싶으면 extends!
기능을 강제로 사용하게 하고 싶으면 implements!

extends의 경우 공통 기능을 두되, 필수로 사용해야 하는 강제성은 없다!




✨ 예외(추상클래스)

부모 클래스를 상속받을 때 일부는 구현된 그대로, 일부는 자식 클래스에서 반드시 구현하도록 설정할 수 있다.
바로 부모 클래스를 추상클래스로 두는 것이다!

// 추상 클래스 선언
abstract class Animal {
    // 미리 구현 (공통 사용을 위해)
    void eat() {
        System.out.println("음식을 먹는다.");
    }

    // 추상 메서드(반드시 구현해야 함)
    abstract void sound();
}


class Dog extends Animal {
    @Override
    void sound() { 
        System.out.println("멍멍!");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();   // "음식을 먹는다." 
        dog.sound(); // "멍멍!"
    }
}

이렇게 설정하면, 원래대로 부모의 메서드를 그대로 가져다 쓰기도 하고
자식 클래스에서 필수로 구현해야 할 추상 메서드로 설정할 수 있다!!

물론, 원래대로 eat()도 자식 클래스에서 오버라이드 할 수 있다~

나름대로 extends와 implements를 함께 쓰는 느낌이다.

0개의 댓글