241011 TIL - Java 스터디 정리 2 추상클래스와 인터페이스, 다형성

J_log·2024년 10월 11일
0

Java 스터디 정리

목록 보기
2/5
post-custom-banner

추상클래스

하나 이상의 추상메서드를 포함한 클래스. 추상 메서드는 선언만 되어 있고 구체적인 동작은 이를 상속받은 하위 클래스에서 구현해야 한다. 추상 클래스 자체는 인스턴스를 생성할 수 없고 반드시 상속을 통해서만 사용할 수 있다.

abstract class Animal {
    abstract void sound();  // 추상 메서드 (구현 X)
    
    void sleep() {          // 일반 메서드 (구현 O)
        System.out.println("잠을 잡니다.");
    }
}

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

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("야옹");
    }
}

추상 클래스는 왜 사용하는가?

  • 공통된 기능을 정의하기 위해 : 여러 하위 클래스가 공통적으로 가져야 할 속성이나 동작을 정의하는데 사용된다.
  • 템플릿 역할 : 템플릿의 역할을 하여 상속받는 클래스들이 어떤 메서드를 반드시 구현해야 하는지 규칙을 정한다. 이를 통해 코드의 일관성을 유지할 수 있다.
  • 재사용성 증가 : 추상 클래스 내에서 구현된 메서드는 모든 하위 클래스에서 공통으로 사용할 수 있기 때문에 코드의 재사용성을 높일 수 있다.
  • 상속을 통해 코드 확장성 확보 : 상속을 통해 코드의 확장성을 높인다. 새로운 클래스가 생길 때 기존 코드를 수정할 필요 없이 새로운 클래스에서 필요한 부분만 구현하면 된다.
  • 객체 생성 제한 : 추상 클래스는 직접 인스턴스를 생성할 수 없기 때문에 불완전한 객체가 생성되는 것을 방지한다. 상속받은 하위 클래스가 구체적인 동작을 정의 해야만 객체를 생성할 수 있다.

언제 사용하면 좋은가?

  • 여러 클래스들이 공통된 기능을 가지면서도 구체적인 동작은 다르게 구현해야 할 때
  • 추상 메서드뿐 아니라 공통된 필드나 메서드의 구현을 포함하고 싶을 때
  • 상속 관계가 명확한 구조에서 사용하고자 할 때

overriding과 super

오버라이드

부모 클래스의 메서드의 내용을 재정의 하는 것

  • 부모 클래스의 메서드를 그대로 사용 가능하지만 자식 클래스에서 필요에 따라 변경해서 사용한다.

오버라이딩을 하기 위해서 만족해야 하는 것들

  • 선언부가 부모 클래스의 메서드와 일치해야 한다.
  • 접근 제어자를 부모 클래스의 메서드 보다 좁은 범위로 변경할 수 없다.
  • 예외는 부모 클래스의 메서드 보다 많이 선언할 수 없다.

@Override
public void testMethod() {}

super

부모 클래스의 멤버를 참조할 수 있는 키워드

  • 객체 내부 생성자 및 메서드에서 부모 클래스의 멤버에 접근하기 위해 사용
  • 자식 클래스 내부에서 선언한 멤버와 부모 클래스에서 상속받은 멤버와 이름이 같을 경우 이를 구분하기 위해 사용

다형성

여러가지 형태를 가질 수 있는 능력

Tire tire = new HankookTire("HANKOOK");
 Tire tire = new KiaTire("KIA");

예제코드 처럼 부모 타이어 변수 = 자식 타이어 객체; 를 선언하여 자동 타입 변환된 변수를 사용하여 각각의 자식 타이어 객체에 재정의 된 메서드를 통해 다양한 타이어를 생성할 수 있다.

  • 매개변수에도 다형성이 적용될 수 있다.
public Car(Tire tire) {
    this.tire = tire;
}

...

Car car1 = new Car(new KiaTire("KIA"));
Car car2 = new Car(new HankookTire("HANKOOK"));

Car 생성자에서 매개변수의 타입이 부모 타이어이기 때문에 자식 타이어 객체들을 매개값으로 전달 할 수 있다.

  • 변환 타입에도 다형성이 적용될 수 있다.
Tire getHankookTire() {
    return new HankookTire("HANKOOK");
}

Tire getKiaTire() {
    return new KiaTire("KIA");
}

...

Tire hankookTire = car1.getHankookTire();
KiaTire kiaTire = (KiaTire) car2.getKiaTire();

변환 타입이 부모 타이어이기 때문에 자식 타이어 객체들을 반환값으로 지정할 수 있다.
또한 자동 타입 변환이 된 반환값인 자식 타이어 객체를 강제 타입 변환할 수도 있다.

다형성을 이용해 얻을 수 있는 주요 이점

  • 유연성 향상 : 다형성을 사용하면 상위 클래스 타입으로 객체를 선언한 후 하위 클래스의 인스턴스를 동적으로 할당할 수 있다. 이는 코드의 유연성을 크게 향상시켜, 코드 수정 없이 새로운 하위 클래스를 쉽게 추가할 수 있다.
  • 코드의 재사용성 : 동일한 상위 클래스나 인터페이스를 사용하여 다양한 객체들을 처리할 수 있어 중복 코드를 줄이고 재사용할 수 있다.
  • 유지보수성 향상 : 하위 클래스에서 상위 클래스의 메서드나 속성을 오버라이드하여 각각의 기능을 구현할 수 있어, 코드를 수정할 필요 없이 새로운 기능을 추가 하거나 변경할 수 있다.
  • 인터페이스 기반 프로그래밍 : 인터페이스나 상위 클래스를 기준으로 프로그래밍할 수 있어, 여러 구현체가 있는 경우에도 일관된 방식으로 객체를 다룰 수 있다.

인터페이스

인터페이스는 클래스가 구현해야 하는 메서드들의 집합을 정의하는 하나의 청사진이다. 인터페이스는 메서드를 선언만 하며 메서드의 실제 구현은 인터페이스를 구현하는 클래스에서 담당한다.

public interface Animal {
void makeSound();
}

  • 다중 구현 가능 : Java는 다중 상속을 지원하지 않지만 인터페이스는 여러 개를 구현할 수 있다. 이를 통해 다중 상속의 이점을 누리면서 상속의 복잡성을 피할 수 있다.
public class Dog implements Animal, Pet {
    public void makeSound() {
        System.out.println("Bark");
    }
}
  • 상수 필드 : 인터페이스는 일반적으로 상수(static final) 필드만을 가질 수 있다.(생략 가능)
public interface MathConstants {
    public static final char A = 'A'
   	final int NUMBER = 300;
    static char B = 'B'
    double PI = 3.14159;
}
  • 디폴트 메서드 및 정적 메서드 : 디폴트 메서드는 인터페이스 내에서 구현을 제공할 수 있어 기존 구현체에 영향을 주지 않고 인터페이스에 새로운 기능을 추가할 수 있다.
public class Main implements A {

    @Override
    public void a() {
        System.out.println("A");
    }


    public static void main(String[] args) {
        Main main = new Main();
        main.a();

        // 디폴트 메서드 재정의 없이 바로 사용가능합니다.
        main.aa();
    }
}

interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
}
  • static의 특성 그대로 인터페이스의 static 메서드 또한 객체 없이 호출 가능하다.
public class Main implements A {

    @Override
    public void a() {
        System.out.println("A");
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.a();
        main.aa();
        System.out.println();

        // static 메서드 aaa() 호출
        A.aaa();
    }
}

interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
    static void aaa() {
        System.out.println("static method");
    }
}

인터페이스와 추상 클래스의 주요 차이점

post-custom-banner

0개의 댓글