Home 모던 자바 (2) 인터페이스와 클래스
Post
Cancel

모던 자바 (2) 인터페이스와 클래스

도입

  • 자바 8에서 람다함수형 프로그래밍을 도입하면서 인터페이스에 변화가 생김.
  • 자바 7부터 시작된 인터페이스 변경은 자바 8에서 대대적으로 이루어짐.
  • 이 변화는 함수형 프로그래밍스트림 등의 배경이 되었고, 컬렉션 프레임워크 개선의 기반이 됨.

인터페이스 사용 시 문제점

  • 인터페이스는 주로 여러 구현체를 통일된 명세서로 정의하기 위해 사용.
  • 자바 8 이전에는 한 번 배포된 인터페이스를 수정하는 것이 어렵고, 컴파일 에러를 발생시키는 경우가 있음.
  • 클래스를 수정하지 않고는 인터페이스를 수정할 수 없어서 불편함이 있었음.

인터페이스의 진화

  • 자바 8에서는 디폴트 메서드를 도입하여 인터페이스에 구현된 코드를 정의할 수 있게 됨.
  • 컬렉션 프레임워크 등에서 발생한 문제를 해결하기 위해 디폴트 메서드가 등장.
  • 자바 9에서는 private 메서드를 인터페이스에 추가하여 로직을 공통화하고 재사용성을 높임.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 자바 8 이전의 인터페이스와 클래스
interface CompanyEncryption {
    // 인터페이스에서는 추상 메서드만 정의 가능
    byte[] encrypt(byte[] bytes) throws Exception;
    byte[] decrypt(byte[] bytes) throws Exception;
}

// 해당 인터페이스를 구현하는 클래스
class EncryptionImpl implements CompanyEncryption {
    @Override
    public byte[] encrypt(byte[] bytes) throws Exception {
        // 구현체에서 추상 메서드를 반드시 구현해야 함
        // 실제 암호화 로직
        return /* 암호화된 결과 */;
    }

    @Override
    public byte[] decrypt(byte[] bytes) throws Exception {
        // 실제 복호화 로직
        return /* 복호화된 결과 */;
    }
}

// 자바 8 이후의 인터페이스와 클래스
interface CompanyEncryption {
    // 기존과 마찬가지로 추상 메서드 정의 가능
    byte[] encrypt(byte[] bytes) throws Exception;

    byte[] decrypt(byte[] bytes) throws Exception;

    // 디폴트 메서드로 실제 코드를 포함할 수 있음
    default boolean isEncoded(byte[] bytes) throws Exception {
        // 기본적으로는 구현체에서 재정의 가능
        return /* 암호화 여부 판단 로직 */;
    }
}

// 해당 인터페이스를 구현하는 클래스
class EncryptionImpl implements CompanyEncryption {
    @Override
    public byte[] encrypt(byte[] bytes) throws Exception {
        // 실제 암호화 로직
        return /* 암호화된 결과 */;
    }

    @Override
    public byte[] decrypt(byte[] bytes) throws Exception {
        // 실제 복호화 로직
        return /* 복호화된 결과 */;
    }
    
    // isEncoded 메서드를 구현하지 않아도 됨
}

// 기존과 마찬가지로 인터페이스를 구현한 클래스는 여전히 사용 가능
EncryptionImpl encryption = new EncryptionImpl();
byte[] encryptedData = encryption.encrypt(data);
byte[] decryptedData = encryption.decrypt(encryptedData);
boolean isEncoded = encryption.isEncoded(encryptedData);

default 메서드로 인한 다이아몬드 상속 문제

  • 자바 8에서 도입된 default 메서드로 인해 다이아몬드 상속이 발생할 수 있다.

  • 다이아몬드 상속하나의 클래스두 개 이상의 인터페이스로부터 같은 시그니처를 가진 default 메서드를 상속받을 때 발생한다.
  • 이 때 어떤 default 메서드를 사용해야 할지 모호성이 생기는데, 이를 해결하기 위해 몇 가지 원칙이 존재한다.
  1. 클래스 우선 순위: 클래스가 인터페이스보다 더 높은 우선순위를 가진다. 클래스에서 이미 구현된 메서드가 있으면 해당 메서드가 선택된다.

  2. 하위 클래스 인터페이스 우선: 인터페이스 간에는 해당 인터페이스를 구현하는 하위 클래스의 구현이 우선된다.

아래는 이러한 원칙에 대한 예시 코드:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
interface InterfaceA {
    default void print() {
        System.out.println("Default implementation in InterfaceA");
    }
}

interface InterfaceB {
    default void print() {
        System.out.println("Default implementation in InterfaceB");
    }
}

class BaseClass {
    void print() {
        System.out.println("Implementation in BaseClass");
    }
}

// 다이아몬드 상속이 발생하는 경우
class DiamondInheritanceClass extends BaseClass implements InterfaceA, InterfaceB {
    // 여기서는 어떤 print 메서드를 사용해야 할지 모호함이 발생
}

public class Main {
    public static void main(String[] args) {
        DiamondInheritanceClass obj = new DiamondInheritanceClass();
        obj.print();
    }
}

위의 코드에서 DiamondInheritanceClassInterfaceAInterfaceB에서 동일한 시그니처의 print 메서드를 가지고 있다. 이때 컴파일러는 클래스 우선 순위를 따라 BaseClass에서 이미 구현된 print 메서드를 선택한다.

만약에 DiamondInheritanceClass에서 print 메서드를 명시적으로 오버라이드하여 우선순위를 변경하고 싶다면 다음과 같이 할 수 있다:

1
2
3
4
5
6
7
class DiamondInheritanceClass extends BaseClass implements InterfaceA, InterfaceB {
    @Override
    public void print() {
        // 명시적으로 BaseClass의 print 메서드 호출
        BaseClass.super.print();
    }
}

위의 코드에서 BaseClass.super.print();를 사용하여 명시적으로 BaseClassprint 메서드를 호출하고 있다. 이렇게 함으로써 우선순위를 변경할 수 있다.

요약

  • 자바 8 이후의 인터페이스 변화는 기존 코드의 유지보수 및 확장성 측면에서 큰 이점을 제공한다. 디폴트 메서드를 활용하면 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있어서 업데이트 과정이 더욱 유연해졌다.

  • 디폴트 메서드를 이용하면 기존 인터페이스를 구현하는 클래스들이 새로운 메서드에 대한 구현을 갖지 않아도 되므로, 기존 코드의 영향을 최소화하면서 새로운 기능을 도입할 수 있다.

  • 개발자들은 이러한 특징을 활용하여 라이브러리나 프레임워크를 설계할 때 유연하고 확장 가능한 인터페이스를 정의할 수 있다. 이는 특히 오픈 소스 프로젝트에서 협업 시에 유용하게 활용될 수 있다.

  • private 메서드의 도입으로 인터페이스 내부에서의 로직을 캡슐화하고 코드의 재사용성을 높일 수 있게 되었다. 이로써 인터페이스 내부의 복잡한 구현 로직이 감춰져 사용자에게는 단순한 명세서로만 노출될 수 있다.

  • 이러한 변화는 자바의 진화에 따라 기존 코드와의 호환성을 유지하면서도 새로운 기능을 효과적으로 도입할 수 있도록 도와주고 있다. 개발 생산성코드 유지 보수성을 높이는 측면에서 매우 긍정적인 발전으로 평가된다.

  • 자바 8의 default 메서드 도입으로 다이아몬드 상속 문제가 발생하며, 클래스 우선 순위하위 클래스 인터페이스 우선 원칙이 적용된다. 클래스에 이미 구현된 메서드가 우선 선택되고, 우선순위를 변경하려면 해당 메서드를 명시적으로 호출해야 한다. 이는 두 개 이상의 인터페이스에서 동일한 시그니처의 default 메서드를 상속받을 때 발생하는 모호성을 해결하는 규칙이다.