1장: 자바의 역사

자바 8,9,10,11: 무슨 일이 일어나고 있는가?

자바 역사의 흐름

  • 자바 8에서 가장 큰 변화가 나타났다.

    • 함수형 프로그래밍 지원 (스트림 API, 람다)

    • 인터페이스의 디폴트 메서드

병렬 실행 기법

  • 기존에는 저수준 기능만을 제공하여 스레드를 사용하여 병렬 실행 환경을 관리하는 것이 어려웠다.

  • 자바 5에서는 스레드 풀, concurrent collection을, 자바 7에서는 fork/join 프레임워크를 제공했지만 직접 활용하기엔 쉽지 않았다.

  • 자바 8의 경우 병렬 실행을 새롭고 단순한 방식으로 접근할 수 있도록 제공한다.

  • 자바 9에서는 리액티브 프로그래밍을 지원하며 RxJava를 표준 방식으로 제공한다.

프로그래밍 언어 생태계

  • C, C++은 프로그래밍 안전성이 낮아 프로그램이 예기치 않게 종료되거나 보안 약점이 존재할 수 있지만 작은 런타임 풋프린트로 운영체제 및 다양한 임베디드 시스템에서 여전히 많이 사용되고 있다.

  • 런타임 풋프린트에 여유가 있는 애플리케이션의 경우 보통 Java, C#같은 안전한 형식의 언어를 사용한다.

자바는 어떻게 성장했는가?

  • 풍부한 라이브러리와 잘 설계된 객체 지향 언어

  • 초기 브라우저에서 지원하는 JVM 바이트코드로 컴파일하여 안전하게 실행할 수 있었다.

  • Write Once Run Anywhere 으로 JVM만 있다면 어디든 코드를 실행할 수 있다.

  • 자바가 대학교로 깊숙이 자리잡아 졸업생들이 자바를 업계에서 활용했다.

자바 8에 추가된 개념

  • 스트림

    • 한 번에 한 개씩 만들어지는 연속적인 데이터들의 모임

    • 일반적으로 입력 스트림에서 데이터를 한개씩 읽어 출력 스트림으로 하나씩 기록한다.

    • 출력 스트림은 다른 프로그램의 입력 스트림이 될 수 있다.

    • 유닉스의 cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3 명령어는 두 파일을 연결하고, 단어를 소문자로 바꾼 후, 단어를 사전순으로 정렬해, 가장 마지막에 위치한 세 단어를 출력한다.

    • 유닉스에서는 위 작업들을 병렬로 실행하며, 이 때 sortcat이나 tr 연산이 완료되지 않은 시점에서 행을 처리하기 시작할 수 있다.

      • 쉽게 말하면 모든 행이 준비된 후에 한번에 sorting 하는 게 아니라, 하나씩 데이터를 추가하며 sorting 해주게 된다.

    • 데이터가 하나씩 컨베이어 벨트처럼 쭉 이동하고 있으므로, 모든 작업장에서 병렬적으로 연산이 되고 있다고 이해하면 된다.

  • 동작 파라미터화

    • 메서드를 다른 메서드의 인수로 넘겨주는 기능을 제공한다.

    • 즉, 동작(메서드)을 파라미터화 해서 전달 가능하다.

    • 함수형 프로그래밍 기술을 응용해 동작 파라미터를 활용할 수 있다.

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

    • 병렬성을 위해 스트림 메서드로 전달하는 코드는 공유된 가변 데이터에 접근하지 않아야 한다.

    • 여러 메서드를 실행하며 동시에 공유된 가변 데이터를 변경하는 불상사가 발생하면 안된다.

    • 즉, pure, side-effect-free, stateless 함수여야 한다.

    • synchronized를 사용하면 코드가 순차적으로 실행되도록 하여 병렬을 무력화시켜 성능에 악영향을 미친다.

자바 함수

  • 자바 8에서 함수는 새로운 값의 형식으로 추가되었다.

  • 병렬 프로그래밍을 활용할 수 있는 스트림과 연계될 수 있도록 하기 위함이다.

  • 함수를 값으로 사용하면 더 쉽게 프로그램을 구현할 수 있다는 것이 스칼라와 그루비 등으로 증명되었다.

  • 아래에서 다루는 내용들을 통해 자세하게 장점을 확인해보자!

메서드 참조

  • 기존에는 메서드를 다른 메서드의 인자로 넣으려면 익명 클래스의 메서드를 구현해야만 했다.

  • 다음과 같이 익명 클래스를 사용하면 각 행이 무슨 작업을 하는지 한눈에 파악하기 어렵다.

File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
    public boolean accept(File file) {
        return file.isHidden();
    }
});
  • 자바 8의 메서드 참조 기능을 사용해 아래와 같이 바로 특정 메서드를 다른 메서드의 인자로 넣게 되면, "현재 디렉토리에서 모든 숨김 파일을 찾아 결과를 반환한다"라는 의미를 쉽게 이해할 수 있다.

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

람다

  • 익명 함수라고도 부른다.

  • 클래스가 없어도 메서드를 정의할 수 있어 간결하게 코드를 구현할 수 있다.

  • 리스트에 특정 함수를 기준으로 필터링된 객체들만 추가하는 메서드를 만드는 요구사항을 생각해보자.

  • Apple의 색깔이 초록색인 것만 리스트로 모으는 메서드와 무게가 150g 이상인 것만 리스트로 모으는 메서드가 필요하다고 한다면 아래와 같이 Predicate 인터페이스의 구현체를 인자로 받도록 하여 중복을 줄일 수 있다.

Predicate 인터페이스: 입력 인자를 받아 true나 false를 반환하는 함수형 인터페이스이다. 즉 어떠한 입력 인자를 받아 boolean을 반환하도록 만든 메서드는 이 인터페이스의 구현체에 해당하게 된다.

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);
  • 간결한 로직이며 한번만 사용된다면, 따로 메서드를 구현할 필요 없이 람다를 넣어 바로 전달해줄 수 있다.

  • 물론 조금 복잡한 동작을 수행하려 한다면 메서드를 정의해 참조하도록 하는 것이 가독성이 좋다.

filterApples(inventory, (Apple a) -> a.getWeight() > 150);
filterApples(inventory, (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()));
  • 병렬성을 위해 단순히 filter 등과 같은 몇몇 일반적인 라이브러리 메서드를 추가하는 대신 자바 8에서는 filter와 비슷한 동작을 수행하는 연산집합을 포함하는 스트림 API를 제공한다.

스트림

  • 컬렉션을 반복하면서 특정 조건을 만족하는 것을 찾거나, 특정 데이터만 추출해내거나 데이터를 그룹화하는 등의 여러 작업을 진행하는 것은 가독성이 좋지 않으며 멀티스레딩으로 구현하기 어렵다.

  • 이를 쉽게 이해할 수 있도록 스트림은 여러 API를 제공한다.

  • 기존에는 컬렉션을 활용할 때 for-each 루프를 이용하여 각 요소를 반복하면서 작업을 수행했다. (외부 반복)

  • 스트림 API를 이용하면 컬렉션을 스트림으로 바꾸고 라이브러리 내부에서 모든 데이터를 처리할 수 있다.(내부 반복)

  • 스트림의 병렬 API를 사용하면 서로 다른 CPU 코어에 각 스레드를 할당해 처리 시간을 줄일 수 있다.

디폴트 메서드와 자바 모듈

  • 디폴트 메서드를 제공하여 기존 인터페이스를 구현하는 클래스를 모두 바꾸지 않고도 쉽게 인터페이스를 변경할 수 있다.

  • 디폴트 메서드는 인터페이스의 구현체에서 반드시 Override하지 않아도 된다.

  • 여러 인터페이스 구현 시 다중 디폴트 메서드가 존재할 때 어떻게 처리하는지는 9장에서 다룬다.

  • 자바 9부터 모듈 시스템을 제공하여 JAR같은 컴포넌트에 구조를 적용할 수 있다.

  • Optional<T>를 제공해 NullPointerException을 피할 수 있다.

  • 케이스로 정의하는 함수형 프로그래밍의 기능인 패턴 매칭 기법을 지원한다.

  • 데이터 형식 분류와 분석을 한 번에 수행할 수 있다.

Last updated