[모던 자바인 액션] chp 1. 자바, 무슨 일이 일어나고 있는가 ?

sameul__choi·2022년 2월 7일
0

[모던 자바인 액션]

목록 보기
1/11
post-thumbnail

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

언어는 하드웨어나 프로그래머 기대의 변화에 부응하는 방향으로 변화해야한다.

역사의 흐름

다음은 사과 목록을 무게순으로 정렬하는 고전적 코드다.

Collections.sort(inventory, new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight);
    }
});

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

inventory.sort(comparing(Apple::getWeight));

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

자바 8이 등장하기 전에는 나머지 코어를 활용하려면 스레드를 사용하는 것이 좋다고 조언했을 것이다. 하지만 스레드는 관리가 어렵고 많은 문제를 야기한다. 자바는 이러한 병렬 실행 환경을 쉽게 관리하고 에러가 덜 발생하는 방향으로 진화하려고 노력했다. 하지만 실무에서 이런 저수준 기능을 온전히 활용하기 어려웠다. 자바 5에서는 스레드 풀, 병렬실행 컬렉션 등 아주 강력한 도구를 도입했고, 자바 7에서는 포크 조인 프레임워크(병렬 실행에 도움을 줄 수 있는)를 제공했지만 여전히 개발자가 활용하기는 쉽지 않았다. 자바 8에서는 병렬 실행을 새롭고 단순한 방식으로 접근할 수 있는 방법을 제공한다.

자바 8은 멀티코어 프로세서의 쉬운 활용과 간결한 코드 이 두가지 요구사항을 기반으로 한다. chp1에서는 간단하게 훑어보는 식으로 구성되어 있다.

스트림 API

  • 데이터베이스 질의 언어에서 표현식을 처리하는 것처럼 병렬 연산을 지원하는 스트림이라는 새로운 API를 제공한다.
    • 즉, 스트림을 사용하면 에러가 많고 synchronized 같은 값 비싼 키워드를 사용하지 않아도 된다.

스트림이란 ?

  • 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임
  • 이론적으로 입력스트림에서 데이터를 한 개씩 읽어 들이며 마찬가지로 출력 스트림으로 데이터를 한 개씩 기록한다.
  • 즉 , 어떤 프로그램의 출력 스트림은 다른 프로그램의 입력 스트림이 될 수 있다.
  • 스트림 API의 핵심은 기존에는 한 번에 한 항목을 처리했지만 자바 8에서는 우리가 하려는 작업을 고수준으로 추상화해서 일련의 스트림으로 만들어 처리할 수 있다.
  • 파이프라인을 이용해서 입력 부분을 여러 CPU 코어에 쉽게 할당할 수 있다는 부가적인 이득도 얻을 수 있다.
  • 스레드라는 복잡한 작업을 사용하지 않으면서도 공짜로 병렬성을 얻을 수 있다.
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3

일례로 유닉스의 cat으로 두 파일을 연결하여 스트림을 생성하며, tr은 스트림의 문자를 번역하고, sort는 스트림의 행을 정렬하며, tail -3은 스트림의 마지막 3개 행을 제공한다.

유닉스에서는 여러 명령을 병렬로 실행한다. 따라서 cat, tr이 완료되지 않은 시점에서 sort가 행을 처리하기 시작할 수 있다. 이를 기계적인 예로 자동차 생산 공장라인에 비유할 수 있다. 자동차 생산공장은 여러 자동차로 구성된 스트림을 처리하는데, 각 작업장에서는 자동차를 받아 수리한 뒤 다음 작업장에서 다른 작업을 처리할 수 있도록 넘겨준다. 조립라인은 자동차를 물리적 순서로 한 개씩 운반하지만 각각의 작업장에서는 동시에 작업을 처리한다.

//리스트에서 고가의 거래만 필터링을 한다음 통화로 결과를 그룹화하는 코드이다.

Map<Currency, List<Transaction>> transactionsByCurrencies = new HasMap<>();
for (Transaction transaction : transactions){
    if (transaction.getPrice() > 1000){
        Currency currency = transaction.getCurrenct();
        List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
        if(transactionsForCurrenc == null){
            transactionsForCurrency = new ArrayList<>();
            transactionsByCurrencies.put(currency, transactionsForCurrency);
        }
        transactionsForCurrency.add(transaction);
    }
}

위의 코드를 스트림을 활용하면 다음처럼 작성할 수 있다.

Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream()
                .filter((Transaction t) -> t.getPrice() > 1000)
                .collect(groupingBy(Transaction::getCurrency));

우선은 스트림 API를 이용하면 컬렉션 API와 상당히 다른 방식으로 데이터를 처리할 수 있다는 사실을 기억하자.

컬렉션에서는 for-each loop으로 반복 과정을 직접 처리(외부 반복)했지만, 스트림 API를 이용하면 루프를 신경쓸 필요가 없다. 스트림 API에서는 라이브러리 내부에서 모든 데이터가 처리된다(내부반복).

컬렉션을 사용하면 거대한 리스트를 처리하는데 시간이 오래 걸리겠지만, 서로 다른 코어에 작업을 각각 할당하여 처리시간을 줄일 수 있다면, 병렬로 코어를 활용하여 단일 CPU에 비해 빠른 작업이 가능하다.

메서드에 코드를 전달하는 기법

  • 새롭고 간결한 방식으로 동작 파라미터화 구현
  • 코드 일부를 API로 전달하는 개념
  • 동작의 전달을 위해 익명 클래스를 만들고 메서드를 구현해서 넘길 필요 없이, 준비된 함수를 메서드 참조 :: 를 이용해서 전달할 수 있다.

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

  • 라이브러리의 간결성 유지 및 재컴파일을 줄인다.

요즘은 외부에서 만들어진 컴포넌트를 이용해 시스템을 구축하는 경향이 있다. 이와 관련해 지금까지 자바에서는 특별한 구조가 아닌 평범한 자바 패키지 집합을 포함하는 JAR을 제공하는 것이 전부였다. 때문에 패키지의 인터페이스를 바꿔야 하는 과정에서는 인터페이스를 구현하는 모든 클래스의 구현을 바꿔야했다 -> 고통스럽다

자바 8에서는 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드를 지원하다. 인터페이스에 새로운 메서드를 추가한다면 인터페이스를 구현하는 모든 클래스는 새로 추가된 메서드를 구현해야 한다. 이건 현실적으로 말이 안되는 상황이고, 기존의 구현을 고치지 않으면서 공개된 인터페이스를 변경할 수 있는 방법이 필요하다.

결정적으로 자바 8은 기존의 코드를 건드리지 않고, 원래의 인터페이스 설계를 자유롭게 확장할 수 있도록 구현하지 않아도 되는 메서드를 인터페이스에 추가할 수 있는 기능을 제공한다.

default void sort(Comparator<? super E> c){
	Collections.sort(this, c);
}

예를 들어 자바 8에서는 sort메서드를 호출할 수 있다. 이는 자바 8의 List 인터페이스에 다음과 같은 디폴트 메서드 정의가 추가되었기 때문이다.
따라서 자바 8이전에는 List를 구현하는 모든 클래스가 sort를 구현해야 했지만 자바 8부터는 디폴트 sort를 구현하지 않아도 된다.

일급 객체

  • 일급 객체는 아래 3개의 조건을 충족하는 객체를 일급객체라고 정의할 수 있다.
(1) 변수와 데이터에 할당 할 수 있어야 한다.
(2) 객체의 인자로 넘길 수 있어야 한다.
(3) 객체의 리턴값으로 리턴 할 수 있어야 한다.

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

스칼라와 그루비 같은 언어에서 메서드를 일급값으로 사용하면 프로그래머가 활용할 수 있는 도구가 다양해지면서 프로그래밍이 수월해진다는 사실을 이미 실험을 통해 확인했다. 그래서 자바 8의 설계자들은 메서드를 값으로 취급할 수 있게, 그리하여 프로그래머들이 더 쉽게 프로그램을 구현할 수 있는 환경이 제공되도록 자바 8을 설계하였다. 더불어 자바 8에서 메서드를 값으로 취급할 수 있는 기능은 스트림 같은 다른 자바 8의 기능의 토대를 제공했다.

마치며

자바의 역사에 대해서 책을 통해 배울 수 있었고, 언제 어떤 요구로 자바가 업데이트 되어왔는지, 프로그래밍 언어에 대한 시대의 요구사항이 무엇인지에 대해서 간략하게나마 배울 수 있었다. 앞으로 세부 챕터들을 통해 더 자세히 학습하기로 한다. 나를 힘들게 했던 스트림을 정복해보자 라는 책을 구매하였는데 첫 장을 읽고나니 스트림 뿐아니라 다양한 지식들이 버무려져서 그때 그래서 이랬던거구나와 같은 생각을 들게끔 했다. 시작이 반이라고 다른 블로깅보다 힘들긴 하지만 꾸준히 블로깅을 이어나가야 겠다는 다짐도 해본다.

0개의 댓글