JAVA/자바의 신

13장. 인터페이스와 추상클래스, enum

hyunsb 2024. 1. 5. 23:18
💡 해당 글은 『자바의 신 3판』을 복습하며 도서의 내용과 본인의 주관적인 생각을 정리한 글입니다.

☁️ 내용정리

interface

interface는 구현할 기능의 명세를 추상화 시켜놓은 타입? 이라고 생각한다.

클래스의 다중 상속이 불가능한 한계를 해결하여 다중 상속이 가능하다.

개발의 일반적인 프로세스는 다음과 같다. 분석 → 설계 → 개발 및 테스트 → 시스템 릴리즈

요구사항을 분석한다. → 분석 단계에서 도출된 문서를 통해 프로그램을 설계한다.

 

해당 단계에서 interface 혹은 abstract class를 정의하면 개발 및 테스트 단계에서 개발 속도를 향상시킬 수 잇다고 한다. 나는 아직 사용해보질 않아서 잘 모르겠다.

interface나 abstaract class를 사용하면 메서드 시그니처를 미리 정해두고 협업 시 코드 컨벤션을 지킬 수 있게 되어 코드 품질이 좋아진다는 것은 경험해봤다.

 

인터페이스는 아래와 같이 선언할 수 있다.

public interface MyInterface {

    void myMethod();
}

public class MyInterfaceImple implements MyInterface {

    @Override
    public void myMethod() {
        // ...
    }
}

// 혹은 아래처럼 익명 클래스도 가능
public static void main(String[] args) {
    MyInterface my = new MyInterface() {
        @Override
        public void myMethod() {
            //... 
        }
    }
}

인터페이스에 선언된 메서드에 접근 제어자를 선언하지 않으면 자동으로 public 접근 권한을 부여한다.

protected 접근 권한은 지원하지 않는다.

java8 부터는 interface에 staic과 default 메서드를 지원한다. 인터페이스에서 메서드를 구현할 수 있다.

java9 부터는 interface에 private 접근 권한이 지원된다. private 접근 권한을 가지는 메서드는 반드시 interface 내부에서 메서드 로직이 구혀되어 있어야 한다.

default 메서드에서 로직을 분리하고 외부로 해당 로직을 감추기 위함인 것 같다. 어찌 점점 abstract 클래스의 완벽한 상위호환이 되는 것 같다.

 

abstract

abstract 클래스는 일부 메서드만 구현해놓은 클래스이다.

public abstract class MyabstractClass {

    public abstract void method();

    public void method(String str) {
        // ...
    }
}

abstract class를 extends하는 클래스가 반드시 모든 abstract 메서드를 구현해야 하는 것은 아니다. 하지만 하나라도 구현하지 않을 시 해당 클래스 또한 abstract 클래스로 선언해야 한다.

abstract와 final은 함께 쓰일 수 없다. 상속 받은 클래스가 구현해야 하는 메서드인데 상속이 불가능하다는 것은 어불성실이기 때문에 당연하다.

abstract 클래스에서 구현해놓은 메서드에 final 키워드를 통해 상속이 불가능하게 하는 것은 가능하다.

 

final

메서드나 클래스에 final을 붙이는 것은 상속이 불가능하다는 의미이다.

변수에 final을 붙이는 것은 조금 다르게 사용된다. final이 붙은 변수는 더 이상 변경이 불가능하다는 것을 의미한다.

public static void main(String[] args) {
    final B a;
    a = new B();
    a = new B(); // 컴파일 오류
}

이렇게 지역변수는 선언할 땐 초기화를 해주지 않아도 된다. 하지만 한 번 값이 할당되고 난 후에는 변경할 수 없다.

인스턴스 변수가 final로 선언된 경우 필드 혹은 생성자에서 초기화를 해주어야만 한다.

 

reference type을 final로 선언한 경우 유의해야할 점이 있다. 만약 final은 변수 공간에 저장된 값이 변경되지 않도록 하는 것이다. 즉 final reference type은 다른 인스턴스로 변경될 수 없다. 하지만 해당 인스턴스가 자신의 프로퍼티의 변경을 허용한다면, 이에 접근하여 프로퍼티를 변경할 수 있다는 점을 유의해야 한다.

 

아래와 같은 코드가 가능하다는 것이다.

private final Map<String, String> map = new HashMap();

public void put(String key, String value) {
    map.put(key, value);
}

 

enum

enum은 static final 상수의 집합이다.

public abstract class Enum<E extends Enum<E>>
        implements Constable, Comparable<E>, Serializable {

java.lang.Enum 은 모든 열거체의 조상이다.

public enum MyEnum {
   VAR1, VAR2, VAR3;
}

이런식으로 사용할 수 있다. enum의 상수들은 모두 객체이다.

public enum MyEnum {

    A(1),
    B(2),
    C(3);

    private final int value;

    private MyEnum(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

이런식으로 값을 할당하고 프로퍼티에 접근도 가능하다.

 


 

☁️ 내 생각

default 메서드를 사용하는 경우 문제는?

Java8부터 interface는 default 메서드를 지원한다. default 메서드는 인터페이스에서 메서드를 구현할 수 있는 기술이다. 여기서 의문이 생겼다. 자바에서 다중 상속을 지원하지 않는 이유 중 하나는 다이아몬드 상속 문제이다.

 

그럼 아래와 같은 코드는 어떻게 실행되어야 할까?

interface A {
    default void method() {
        System.out.println("A");
    }
}

interface B {
    default void method() {
        System.out.println("B");
    }
}

class C implements A, B {
    // C inherits unrelated defaults for method() from types A and B   
}

직접 코드를 작성해보니 컴파일 오류가 발생한다. method를 override하여 사용하라는 의미이다.

근데 이럴거면 그냥 interface는 높은 추상화의 개념으로 놔두고, 클래스간 다중상속이 가능하게 해주면 안되는거냐?

고 잠깐 생각이 들긴 했는데 사이드 이펙트를 생각하면 불가능할 거 같긴 하다.

class C implements A, B {
    @Override
    public void method() {
        A.super.method(); // 이렇게 A 인터페이스의 method를 호출할 수 있다. 신기하다
    }
}

어우 default 메서드는 오히려 추상화의 개념이 깨지는 것 같아 잘 사용하지 않을 것 같다.