Java 객체지향 심화 - 상속, 다형성, 추상화

1. 상속 (Inheritance)

상속은 부모 클래스의 필드와 메서드를 자식 클래스가 물려받는 것이다. extends 키워드를 사용한다.

// 부모 클래스 (슈퍼클래스)
class Animal {
    String name;
    int age;

    void eat() {
        System.out.println(name + "이(가) 먹이를 먹습니다.");
    }

    void sleep() {
        System.out.println(name + "이(가) 잠을 잡니다.");
    }
}

// 자식 클래스 (서브클래스)
class Dog extends Animal {
    String breed;

    void bark() {
        System.out.println(name + "이(가) 멍멍 짖습니다!");
    }
}

class Cat extends Animal {
    void meow() {
        System.out.println(name + "이(가) 야옹~ 합니다.");
    }
}
public class InheritanceExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "바둑이";  // 부모로부터 상속
        dog.age = 3;          // 부모로부터 상속
        dog.breed = "진돗개"; // 자신의 필드

        dog.eat();    // 부모 메서드
        dog.sleep();  // 부모 메서드
        dog.bark();   // 자신의 메서드
    }
}

상속의 특징

  • 자식 클래스는 부모 클래스의 메서드를 선언 없이 사용할 수 있다
  • 자식 클래스의 메서드는 부모 클래스가 사용할 수 없다
  • 자식 클래스끼리는 메서드 공유가 불가능하다

상속의 장점

  • 코드 재사용성 향상
  • 유지보수 용이
  • 계층적 구조 표현

2. 메서드 오버라이딩 (Overriding)

부모 클래스의 메서드를 자식 클래스에서 재정의하는 것이다.

class Animal {
    String name;

    void sound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

class Dog extends Animal {
    @Override  // 오버라이딩 어노테이션 (선택이지만 권장이라고 함..)
    void sound() {
        System.out.println(name + ": 멍멍!");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println(name + ": 야옹~");
    }
}
public class OverridingExample {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.name = "바둑이";
        cat.name = "나비";

        animal.sound();  // 동물이 소리를 냅니다.
        dog.sound();     // 바둑이: 멍멍!
        cat.sound();     // 나비: 야옹~
    }
}

오버로딩 vs 오버라이딩

구분 오버로딩 (Overloading) 오버라이딩 (Overriding)
위치 같은 클래스 상속 관계
메서드명 동일 동일
매개변수 다름 동일
반환타입 상관없음 동일

3. super 키워드

super는 자식 클래스에서 부모 클래스를 참조할 때 사용하는 키워드다.

super로 부모 필드/메서드 접근

class Parent {
    String name = "부모";

    void show() {
        System.out.println("Parent의 show()");
    }
}

class Child extends Parent {
    String name = "자식";  // 부모와 같은 이름의 필드

    @Override
    void show() {
        System.out.println("Child의 show()");
    }

    void display() {
        System.out.println("this.name: " + this.name);   // 자식
        System.out.println("super.name: " + super.name); // 부모

        this.show();   // Child의 show()
        super.show();  // Parent의 show()
    }
}

super() 생성자

부모 클래스의 생성자를 호출할 때 사용한다. 왜 가능한지는 모르지만 private이 아닌 경우에는 자식클래스에서 부모 클래스의 필드에 대해 조회/변경이 모두 가능하다.

class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    String breed;

    Dog(String name, String breed) {
        super(name);  // 부모 생성자 호출
        this.breed = breed;
    }
}

중요: super()는 반드시 생성자의 첫 줄에 위치해야 한다!


4. 다형성 (Polymorphism)

부모 타입으로 자식 객체를 참조할 수 있다. 이를 통해 같은 타입으로 다양한 객체를 다룰 수 있다.

public class PolymorphismExample {
    public static void main(String[] args) {
        // 다형성: 부모 타입으로 자식 객체 참조
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.sound();  // 멍멍! (Dog의 오버라이딩된 메서드)
        animal2.sound();  // 야옹~ (Cat의 오버라이딩된 메서드)

        // 배열로 여러 타입 관리
        Animal[] animals = {new Dog(), new Cat(), new Cow()};
        for (Animal a : animals) {
            a.sound();  // 각 객체의 오버라이딩된 메서드 호출
        }
    }
}

5. 추상 클래스와 인터페이스

추상 클래스 (Abstract Class)

미완성 메서드를 포함한 클래스로, 인스턴스를 생성할 수 없다.

abstract class Shape {
    String color;

    // 추상 메서드 (구현부 없음)
    abstract double getArea();

    // 일반 메서드 (구현부 있음)
    void setColor(String color) {
        this.color = color;
    }
}

class Circle extends Shape {
    double radius;

    @Override
    double getArea() {
        return Math.PI * radius * radius;
    }
}

인터페이스 (Interface)

추상 메서드의 집합으로, 클래스가 반드시 구현해야 할 기능을 정의한다.

interface Flyable {
    void fly();  // 추상 메서드 (public abstract 생략)

    default void land() {  // default 메서드
        System.out.println("착륙합니다.");
    }

    static void info() {  // static 메서드
        System.out.println("Flyable 인터페이스");
    }
}

class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("새가 날아갑니다.");
    }
}

추상 클래스 vs 인터페이스

구분 추상 클래스 인터페이스
키워드 abstract class interface
다중 상속 불가능 가능 (implements)
변수 모든 종류 가능 public static final
메서드 추상/일반 모두 가능 추상 메서드 (default, static 가능)
목적 “~이다” (is-a) “~할 수 있다” (can-do)

왜 두개를 나눠놨을까? 아직은 알 수 없다. . ..ㅋㅋ

default와 static의 차이

인터페이스에서:

  • default: 객체가 있어야 호출 가능, 오버라이딩 가능
  • static: 객체 없이 호출 가능, 오버라이딩 불가능
interface MyInterface {
    default void defaultMethod() {
        System.out.println("default 메서드");
    }

    static void staticMethod() {
        System.out.println("static 메서드");
    }
}

class MyClass implements MyInterface {
    @Override
    public void defaultMethod() {  // 오버라이딩 가능
        System.out.println("오버라이딩된 default 메서드");
    }

    // static 메서드는 오버라이딩 불가!
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.defaultMethod();  // 객체 필요

        MyInterface.staticMethod();  // 객체 없이 호출
    }
}