본문 바로가기

개발 서적/모던 자바 인 액션

[모던 자바 인 액션] chapter 1. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?

 


[목차]
chapter 1. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?

chapter 2. 동작 파라미터화 코드 전달하기
chapter 3. 람다 표현식(1)
chapter 3. 람다 표현식(2)

 

 

 

 

자바는 새로운 기능과 더불어 계속 발전했다. 자바의 변화를 우리가 눈여겨봐야 하는 이유가 뭘까?

1.1 역사의 흐름은 무엇인가?

자바 역사를 통틀어 가장 큰 변화가 자바 8에서 일어났다. 예를 들어 사과 목록을 무게순으로 정렬하는 고전적 코드를 자바 8에서는 다음과 같이 작성할 수 있다.

// 고전적인 코드
Collections.sort(inventory, new Comparator<Apple>() {
	public int compare(Apple a1, Apple a2) {
		return a1.getWeight().compareTo(a2.getWeight());
	}
});

// Java 8
inventory.sort(comparing(Apple::getWeight));

자바 8을 이용하면 자연어에 더 가깝게 간단한 방식으로 코드를 구현할 수 있다.

 

멀티코어 CPU 대중화와 같은 하드웨어직인 변화도 자바 8에 영향을 미쳤다. 지금까지의 대부분의 자바 프로그램은 코어 중 하나만을 사용했다. (나머지 코어는 유휴 idle 상태이거나, 운영체제나 바이러스 검사 프로그램과 프로세스 파워를 나눠서 사용)

 

자바 8이 등장하기 이전에는 나머지 코어를 활용하려면 스레드를 사용하는 것이 권장되었음. 하지만 스레드를 사용하면 관리하기 어렵고 많은 문제가 발생할 수 있었음. 자바는 이러한 병렬 실행 환경을 쉽게 관리하고 에러가 덜 발생하는 방향으로 진화하려 노력(자바1.0의 스레드와 락, 메모리 모델부터 자바 5의 스레드 풀, 병렬 실행 컬렉션, 자바 7의 포크/조인 프레임워크)했지만 여전히 개발자가 활용하기는 쉽지 않았다.

 

자바 8에서는 병렬 실행을 새롭고 단순한 방식으로 접근할 수 있는 방법을 제공한다. 자바 9에서는 리액티브 프로그래밍이라는 병렬 실행 기법을 지원. 고성능 병렬 시스템에서 특히 인기를 얻고 있는 RxJava를 표준적인 방식으로 지원

 

자바 8은 간결한 코드, 멀티코어 프로세서의 쉬운 활용이라는 두 가지 요구사항을 기반으로 한다. 자바 8에서 제공하는 새로운 기술을 요약하자면 다음과 같다.

  • 스트림 API
  • 메서드에 코드를 전달하는 기법(메서드 참조와 람다)
  • 인터페이스의 디폴트 메서드

 

 

1.2 왜 아직도 자바는 변화하는가?

학계에서는 프로그래밍 언어가 마치 생태계와 닮았다고 결론을 내렸다. 즉, 새로운 언어가 등장하면서 진화하지 않은 기존 언어는 사장되었다.

 

우리는 시공을 초월하는 완벽한 언어를 원하지만 현실적으로 그런 언어는 존재하지 않으며 모든 언어가 장단점을 갖고 있다.

 

특정 분야에서 장점을 가진 언어는 다른 경쟁 언어를 도태시킨다. 단지 새로운 하나의 기능 때문에 기존 언어를 버리고 새로운 언어와 툴 체인으로 바꾼다는 것은 쉽지 않은 일이다. 하지만 새로운 프로그래밍을 배우는 사람은 (기존 언어가 재빠르게 진화하지 않는다면) 자연스럽게 새로운 언어를 선택하게 되며 기존 언어는 도태된다.

 

자바는 지난 1995년 첫 베타 버전이 공개된 이후로 경쟁 언어를 대신하며 커다란 생태계를 성공적으로 구축했다. 이제 자바가 어떻게 그러한 성공을 거둘 수 있었는지 살펴보자.

 

 

1.2.1 프로그래밍 언어 생태계에서 자바의 위치

자바는 출발이 좋았다.

  • 등장 당시 각광받던 객체 지향 패러다임에 적합
  • 스레드와 락을 이용한 동시성 지원 등 처음부터 많은 유용한 라이브러리를 포함
  • 코드를 JVM 바이트코드로 컴파일하는 특징

 

하지만 프로그래밍 언어 생태계에 변화의 바람이 불었다. 프로그래머는 빅데이터라는 도전에 직면하면서 멀티코어 컴퓨터나 컴퓨팅 클러스터를 이용해서 빅데이터를 효과적으로 처리할 필요성이 커졌다. 즉, 병렬 프로세싱을 활용해야 하는 데 지금까지의 자바로는 충분히 대응할 수 없었다.

 

자바 8은 더 다양한 프로그래밍 도구 그리고 다양한 프로그래밍 문제를 더 빠르고 정확하며 쉽게 유지보수할 수 있다는 장점을 제공한다. 자바 8에 추가된 기능은 자바에 없던 완전히 새로운 개념이지만 현재 시장에서 요구하는 기능을 효과적으로 제공한다.

병렬성을 활용하는 코드, 간결한 코드를 구현할수 있도록 자바 8에서 제공하는 기능의 모태인 세 가지 프로그래밍 개념을 설명한다.

 

 

1.2.2 스트림 처리(stream processing)

스트림이란 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다. 이론적으로 프로그램은 입력 스트림에서 데이터를 한 개씩 읽어 들이며 마찬가지로 출력 스트림으로 데이터를 한 개씩 기록한다. 일례로 유닉스나 리눅스의 많은 프로그램은 표준 입력에서 데이터를 읽은 다음에, 데이터를 처리하고, 결과를 표준 출력으로 기록한다.

 

다음은 유닉스에서 파이프(|)를 이용해서 명령을 연결한 명령행이다.

// 파일의 단어를 소문자로 바꾼 다음, 사전순으로 단어를 정렬했을 때 마지막 세 단어 출력
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3

 

유닉스에서는 여러 명령을 병렬로 실행한다. cat이나 tr이 완료되지 않은 시점에서 sort가 행을 처리하기 시작할 수 있다. 기계적인 예로 자동차 생산 공장 라인에 비유할 수 있다. 조립 라인은 자동차를 물리적인 순서로 한 개씩 운반하지만 각각의 작업장에서는 동시에 작업을 처리한다.

 

자바 8에서는 java.util.stream 패키지에 스트림 API가 추가되었다. 우선은 스트림 API가 조립 라인처럼 어떤 항목을 연속으로 제공하는 어떤 기능이라고 단순하게 생각하자. 유닉스 명령어로 복잡한 파이프라인을 구성했던 것처럼 스트림 API는 파이프라인을 만드는데 필요한 많은 메서드를 제공한다.

 

스트림 API의 핵심은 기존에는 한 번에 한 항목을 처리했지만 이제 자바 8에서는 우리가 하려는 작업을 고수준으로 추상화해서 일련의 스트림으로 만들어 처리할 수 있다는 것. 또한 스트림 파이프라인을 이용해서 입력 부분을 여러 CPU 코어에 쉽게 할당할 수 있다는 점.

 

 

1.2.3 동작 파라미터화로 메서드에 코드 전달하기

자바 8에 추가된 두 번째 프로그램 개념은 코드 일부를 메서드로 전달하는 기능이다. 자바 8에서는 메서드(코드)를 다른 메서드의 인수로 넘겨주는 기능을 제공한다. 이러한 기능을 동작 파라미터화(behavior parameterization)라고 부른다. 스트림 API는 연산의 동작을 파라미터화하 수 있는 코드를 전달한다는 사상에 기초한다.

 

1.2.4 병렬성과 공유 가변 데이터

새 번째 프로그래밍의 개념은 ‘병렬성을 공짜로 얻을 수 있다'는 말에서 시작된다. 보통 다른 코드와 동시에 실행하더라도 안전하게 실행할 수 있는 코드를 만들려면 공유된 가변 데이터(shared mutable data)에 접근하지 않아야 한다. 하지만 공유된 변수나 객체가 있으면 병렬성에 문제가 발생한다. synchronized를 이용해서 보호하는 규칙을 만들 수 있을 것이다. 하지만 자바 8 스트림을 이용하면 기존의 자바 스레드 API보다 쉽게 병렬성을 활용할 수 있다.

 

1.2.5 자바가 진화해야 하는 이유

지금까지 자바는 진화해왔다. 제네릭(Generic)이 나타나고, List가 List<String> 등으로 바뀐 것처럼 초기에는 당황스러웠지만, 많은 이가 이미 변화에 익숙해져있으며 그것이 가져다주는 편리함을 누리고 있다. 기존 값을 변화시키는 데 집중했던 고전적인 객체지향에서 벗어나 함수형 프로그래밍으로 다가섰다는 것이 자바 8의 가장 큰 변화다. 자바 8에서 함수형 프로그래밍을 도입함으로써 두 가지 프로그래밍 패러다임의 장점을 모두 활용할 수 있게 되었다. 즉, 어떤 문제를 더 효율적으로 해결할 수 있는 다양한 도구를 얻게 된 것이다.

 

언어는 하드웨어나 프로그래머의 기대의 변화에 부응하는 방향으로 변화해야 한다. 자바는 계속 새로운 기능을 추가하며 인기 언어의 자리를 유지하고 있다.

 

 

1.3 자바 함수

자바 8에서는 함수를 새로운 값의 형식으로 추가했다. 멀티코어에서 병렬 프로그래밍을 활용할 수 있는 스트림과 연계될 수 있도록 함수를 만들었기 때문이다. 먼저 함수를 값처럼 취급한다고 했는데 이 특징이 어떤 장점을 제공하는지 살펴보자.

 

자바 프로그램에서 조작할 수 있는 값

  • int, double 등의 기본값
  • 객체(엄밀히 따지면 객체의 참조)
    • 객체 참조는 클래스의 인스턴스(instance)를 가리킴
    • 심지어 배열도 객체다

 

왜 함수가 필요할까?

프로그래밍 언어의 핵심은 값을 바꾸는 것. 역사적으로, 전통적으로 프로그래밍 언어에서는 이 값을 일급(first-class) 값 혹은 일급 시민이라고 부른다.

 

자바 프로그래밍 언어의 다양한 구조체(메서드, 클래스 같은)가 값의 구조를 표현하는 데 도움이 될 수 있다. 하지만 프로그램을 시행하는 동안 이러한 모든 구조체를 자유롭게 전달할 수는 없다. 이렇게 전달할 수 없는 구조체는 이급 시민이다. 위에서 언급한 값은 모두 일급 자바 시민이지만 메서드, 클래스 등은 이급 자바 시민에 해당한다.

 

인스턴스화한 결과가 값으로 귀결되는 클래스를 정의할 때 메서드를 아주 유용하게 활용할 수 있지만 여전히 메서드와 클래스는 그 자체로 값이 될 수 없다.

 

하지만 런타임에 메서드를 전달할 수 있다면, 즉 메서드를 일급 시민으로 만들면 프로그래밍에 유용하게 활용될 수 있다. 따라서 자바 8 설계자들은 이급 시민을 일급 시민으로 바꿀 수 있는 기능을 추가했다.

 

 

 

1.3.1 메서드와 람다를 일급 시민으로

메서드를 일급값으로 사용하면 프로그래머가 활용할 수 있는 도구가 다양해지면서 프로그래밍이 수월해진다.

 

그래서 자바 8의 설계자들은 메서드를 값으로 취급할 수 있게, 그리하여 프로그래머들이 더 쉽게 프로그램을 구현할 수 있는 환경이 제공되도록 자바 8을 설계하기로 결정했다. 더불어 자바 8에서 메서드를 값으로 취급할 수 있는 기능은 스트림 같은 다른 자바 8 기능의 토대를 제공했다.

 

첫 번째로 메서드 참조(method reference)라는 새로운 자바 8의 기능을 소개한다.

 

다음은 디렉토리에서 모든 숨겨진 파일을 필터링하는 코드다.

File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
	public boolean accept(File file) {
		return file.isHidden();
	}
});

단 세 행의 코드지만 각 행이 무슨 작업을 하는지 투명하지 않다. File 클래스에는 이미 isHidden이라는 메서드가 있는데 왜 굳이 FileFilter로 isHidden을 복잡하게 감싼 다음에 FileFilter를 인스턴스화해야 할까?

 

자바 8에서는 다음처럼 코드를 구현할 수 있다.

File[] hiddenFiles = new File(".").listFiles(File::isHidden);

자바 8에서 메서드 참조(method reference) ::(‘이 메서드를 값으로 사용하라'는 의미)를 이용해서 listFiles에 직접 전달할 수 있다. 기존에 비해 문제 자체를 더 직접적으로 설명한다는 점이 자바 8 코드의 장점이다.

 

자바 8에서는 더 이상 메서드가 이급값이 아닌 일급값. 기존에 객체 참조(new로 객체 참조를 생성함)를 이용해서 객체를 이리저리 주고받았던 것처럼 자바 8에서는 File::isHidden을 이용해서 메서드 참조를 만들어 전달할 수 있게 되었다.

 

 

람다 : 익명 함수

자바 8에서는 메서드를 일급값으로 취급할 뿐 아니라 람다(또는 익명 함수)를 포함하여 함수도 값으로 취급할 수 있다. 예를 들어 (int x) → x + 1, 즉 ‘x라는 인수로 호출하면 x + 1을 반환’하는 동작을 수행하도록 코드를 구현할 수 있다.

 

클래스를 만들어 클래스 내부에 add1()이라는 메서드를 정의해서 Utils::add1을 만드는 방식처럼 직접 메서드를 정의할 수도 있지만, 이용할 수 있는 편리한 클래스나 메서드가 없을 때 새로운 람다 문법을 이용하면 더 간결하게 코드를 구현할 수 있다. 람다 문법 형식으로 구현된 프로그램을 함수형 프로그래밍, 즉 ‘함수를 일급값으로 넘겨주는 프로그램을 구현한다'라고 한다.

 

1.3.2 코드 넘겨주기 : 예제

Apple 클래스와 getColor 메서드가 있고, Apples 리스트를 포함하는 변수 inventory가 있다고 가정하자. 이때 모든 녹색 사과를 선택해서 리스트를 반환하는 프로그램을 구현하려고 한다. 이처럼 특정 항목을 선택해서 반환하는 동작을 filter라고 한다.

 

자바 8 이전

public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
      if ("green".equals(apple.getColor())) {
        result.add(apple);
      }
    }
    return result;
  }

사과를 무게로 필터링하는 경우는 다음과 같을 것이다.

public static List<Apple> filterHeavyApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
      if (apple.getWeight() > 150) {
        result.add(apple);
      }
    }
    return result;
  }

소프트웨어공학적인 면에서 복붙의 단점 → 어떤 코드에 버그가 있다면 복붙한 모든 코드를 고쳐야 한다. 위의 두 코드는 한 줄의 코드만 다르다. 이러한 위험에서 안전하지 않기도 하다.

 

자바 8에서는 코드를 인수로 넘겨줄 수 있으므로 filter 메서드를 중복으로 구현할 필요가 없다. 자바 8에서는 다음과 같이 구현할 수 있다.

public static boolean isGreenApple(Apple apple) {
    return "green".equals(apple.getColor());
  }

  public static boolean isHeavyApple(Apple apple) {
    return apple.getWeight() > 150;
  }

  public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
      if (p.test(apple)) {
        result.add(apple);
      }
    }
    return result;
  }

다음과 같이 호출할 수 있다.

filterApples(inventory, Apple::isGreenApple);
filterApples(inventory, Apple::isHeavyApple);

 

Predicate

filterApples는 Apple::isGreenApple 메서드를 Predicate<Apple>이라는 타입의 파라미터로 받는다. 인수로 값을 받아 true나 false를 반환하는 함수를 프레디케이트라고 한다.

 

1.3.3 메서드 전달에서 람다로

메서드를 값으로 전달하는 것은 분명 유용한 기능이다. 하지만 한두 번만 사용할 메서드를 매번 정의하는 것은 귀찮은 일이다. 자바 8에서는 익명 함수 또는 람다라는 새로운 개념을 이용해서 이 문제도 간단히 해결 가능하다.

filterApples(inventory, (Apple a) -> "green".equals(a.getColor()));
filterApples(inventory, (Apple a) -> a.getWeight() > 150);

filterApples(inventory, (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()));

이처럼 한 번만 사용할 메서드는 따로 정의를 구현할 필요가 없다. 하지만 람다가 몇 줄 이상으로 길어진다면(즉, 조금 복잡한 동작을 수행하는 상황) 익명 람다보다는 코드가 수행하는 일을 잘 설명하는 이름을 가진 메서드를 정의하고 메서드 참조를 활용하는 것이 바람직하다. 코드의 명확성이 우선시 되어야 한다.

 

멀티 코어 CPU가 아니었다면 원래 자바 8 설계자들의 계획은 여기까지였을 것이다.

 

아마도 자바는 filter 등과 같은 몇몇 일반적인 라이브러리 메서드를 추가하는 방향으로 발전했을 수도 있다. 하지만 병렬성이라는 중요성 때문에 설계자들은 이와 같은 설계를 포기했다. 대신 자바 8에서는 filter와 비슷한 동작을 수행하는 연산집합을 포함하는 새로운 스트림 API(컬렉션과 비슷하며 함수형 프로그래머에게 더 익숙한 API)를 제공한다. 또한 컬렉션과 스트림 간에 변활할 수 있는 메서드(map, reduce 등)도 제공한다.

 

 

1.4 스트림

스트림 API를 이용하면 컬렉션 API는 상당히 다른 방식으로 데이터를 처리할 수 있다. 컬렉션에서는 fore-each 루프를 이용해서 반복 과정을 직접 처리해야했다. (external iteration) 반면, 스트림 API를 이용하면 루프를 신경 쓸 필요가 없이 라이브러리 내부에서 모든 데이터가 처리된다. (internal iteration)

 

컬렉션을 이용할 때 많은 요소를 가진 목록을 반복한다면 오랜 시간이 걸릴 수 있다. 거대한 리스트의 경우 단일 CPU로는 처리하기 힘들 것이다. 멀티코어 환경이라면 CPU 코어에 작업을 각각 할당해서 처리 시간을 줄일 수 있을 것이다.

 

1.4.1 멀티스레딩은 어렵다.

이전 자바 버전에서 제공하는 스레드 API로 멀티스레딩 코드를 구현해서 병렬성을 이용하는 것은 쉽지 않다. 멀티스레딩 환경에서 각각의 스레드는 동시에 공유된 데이터에 접근하고, 데이터를 갱신하는데 스레드를 잘 제어하지 못하면 원치 않는 방식으로 데이터가 바뀔 수 있다.

자바 8은 스트림 API로 ‘컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제' 그리고 ‘멀티코어 활용 어려움'이라는 두 가지 문제를 모두 해결했다.

 

스트림 API는 자주 반복되는 패턴으로 주어진 조건에 따라 데이터를 필터링(filtering)하거나, 데이터를 추출(extracting), 데이터를 그룹화(grouping)하는 등의 기능을 제공한다.

 

또한 이러한 동작들을 쉽게 병렬화할 수 있다. 두 CPU를 가진 환경에서 리스트를 필터링할 때 한 CPU는 리스트의 앞부분을 처리하고, 다른 CPU는 리스트의 뒷부분을 처리하도록 요청할 수 있다.

 

새로운 스트림 API도 기존의 컬렉션 API와 비슷한 방식으로 동작하는 것 같아 보이지만 컬렉션은 어떻게 데이터를 저장하고 접근할지에 중점을 두는 반면 스트림은 데이터에 어떤 계산을 할 것인지 묘사하는 것에 중점을 둔다.

 

다음은 스트림 API를 이용한 순차 처리 방식의 코드와 병렬 처리 방식의 코드다.

List<Apple> heavyApples = 
	inventory.**stream()**.filter((Apple a) -> a.getWeight() > 150)
										.collect(toList());

List<Apple> heavyApples = 
	inventory.**parallelStream()**.filter((Apple a) -> a.getWeight() > 150)
										.collect(toList());

 

1.5 디폴트 메서드와 자바 모듈

자바의 변화 과정에서 자바 8 개발자들이 겪는 어려움 중 하나는 기존 인터페이스의 변경이다. 인터페이스를 업데이트하려면 해당 인터페이스를 구현하는 모든 클래스도 업데이트해야 한다. 자바 8, 9는 이 문제를 다른 방법으로 해결한다.

 

자바 9의 모듈 시스템은 모듈을 정의하는 문법을 제공하므로 이를 이용해 패키지 모음을 포함하는 모듈을 정의할 수 있다. 모듈 덕분에 JAR 같은 컴포넌트에 구조를 적용할 수 있으며 문서화와 모듈 확인 작업이 용이해졌다.

 

자바 8에서는 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드(default method)를 지원한다. 디폴트 메서드는 특정 프로그램을 구현하는 데 도움을 주는 기능이 아니라 미래에 프로그램이 쉽게 변화할 수 있는 환경을 제공하는 기능이다.

List<Apple> heavyApples = 
	inventory.**stream()**.filter((Apple a) -> a.getWeight() > 150)
										.collect(toList());

List<Apple> heavyApples = 
	inventory.**parallelStream()**.filter((Apple a) -> a.getWeight() > 150)
										.collect(toList());

자바 8 이전에는 List<T>가 stream()이나 parallelStream() 메서드를 지원하지 않았다. 이 기능을 추가하려면 Collection 인터페이스에 해당 메서드들을 추가하고 ArrayList 등과 같은 구현체에 메서드를 구현해야한다. 하지만 이미 컬렉션 API의 인터페이스를 구현하는 많은 컬렉션 프레임워크가 존재한다. 인터페이스에 새로운 메서드를 추가한다면 인터페이스를 구현하는 모든 클래스는 새로 추가된 메서드를 구현해야 한다.

 

자바 8은 기존의 구현을 고치지 않고도 이미 공개된 인터페이스를 변경할 수 있는 방법을 고민했고, 결과적으로 구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스에 추가할 수 있는 기능을 제공한다. 메서드 본문은 클래스 구현이 아니라 인터페이스의 일부로 포함된다. 이를 디폴트 메서드라고 부른다.

 

 

번외1 : 자바8의 인터페이스 vs 추상 클래스

자바8 이전에는 인터페이스는 메소드 구현부를 갖지 못한다는 점에서 추상 클래스와 크게 차이가 있었는데, 디폴트 메서드 지원으로 인해 추상 클래스와 유사한 성격을 띄게 되었다. 이 둘을 어떻게 구분하여 사용해야할까?

 

자바8 에서 인터페이스와 추상클래스의 차이

개요 자바8 부터 인터페이스도 메소드 구현부를 가질 수 있게 되면서, 추상클래스와 인터페이스의 차이가 모호해졌다. 이 부분에 대해서 정리해본다.

yaboong.github.io

결론만 간단히 이야기하면, 추상 클래스의 경우는 관련성이 높은 클래스들 간의 코드 공유가 필요한 경우 사용한다. 반대로 인터페이스는 Comparable과 같이 서로 관련성 없는 클래스들이 구현하게 되는 경우 사용된다. 또한, 자바는 추상클래스는 다중 상속(extends)이 안되기 때문에 다중 상속(implement)이 필요한 경우 인터페이스를 사용한다.

 

 

번외2: 자바에서 다중 상속이 허용되지 않는 이유 - 다이아몬드 상속 문제

 

자바는 왜 다중상속을 지원하지 않을까? (다이아몬드 문제)

다이아몬드 문제 다중 상속을 지원하게 되면 하나의 클래스가 여러 상위 클래스를 상속 받을 수 있습니다. 이런 특징 때문에 발생하게 되는 문제가 있는데, 바로 '다이아몬드 문제' 입니다. 위의

siyoon210.tistory.com

A라는 클래스가 FatherA와 FatherB를 다중 상속하였고, FatherA와 FatherB는 GrandFather클래스를 상속받아 myMethod()라는 메서드를 각각 오버라이딩 하여 구현했다고 하자. 그렇다면 이 경우 A에서 myMethod()라는 메서드를 호출한다면 FatherA의 구현과 FatherB의 구현 중 어떤 코드가 실행되어야 할까? 다중 상속을 지원하게 되면 이렇게 충돌이 발생하는 문제가 발생하며 이를 다이아몬드 상속 문제라고 한다.

 

인터페이스의 경우에는 구현부를 따로 둘 수 없었기에 다중 상속 문제로부터 자유로웠지만, 디폴트 메서드의 등장으로 구현부가 존재하게 되버려서 다중 상속의 문제를 고려해야할 수 밖에 없게 되었다.

 

디폴트 메서드의 경우에는 우선순위를 두어 충돌을 피하도록 하지만 동일한 우선순위로 인해 충돌이 발생한 경우는 다중 상속을 지원하지 않도록 하거나 새롭게 오버라이딩하여 사용하도록 되어 있다고 한다.

 

 

1.6 함수형 프로그래밍에서 가져온 다른 유용한 아이디어

함수형 언어도 프로그램을 돕는 여러 장치를 제공한다.

일례로 명시적으로 서술형의 데이터를 이용해 null을 회피하는 기법이 있다. 자바 8에서는 NullPointer 예외를 피할 수 있도록 도와주는 Optional<T> 클래스를 제공한다. Optional<T>는 값이 없는 상황을 어떻게 처리할지 명시적으로 구현하는 메서드를 포함하고 있다.

 

또한 구조적 패턴 매칭 기법도 있다. 자바에서는 if-then-else나 switch문을 이용하지만 다른 언어에서는 패턴 매칭으로 더 정확한 비교를 구현할 수 있다는 사실을 증명했다. 아쉽게도 자바 8은 패턴 매칭을 완벽하게 지원하지 않는다. 현재는 자바 개선안으로 제안된 상태다.

반응형