[Ch1] 자바 8,9,10,11 What’s going on here?

이 내용은 모던 자바 인 액션 책을 읽고 개인적으로 정리한 내용입니다.

Intro

1장에서는 자바의 주요변화 (람다 표현식, 메서드 참조, 스트림, 디폴트 메서드) 가 무엇인지 간단히 살펴본다.

자바 8에서 자바 역사상 큰 변화가 일어났다. 그 중심에는 병렬성 이 있다고 생각하면 된다. 자바10은 형 추론과 관련해 약간의 변화만 있었다.

여기서 말하는 병렬성이란, 멀티코어 CPU를 최대한 동시에 사용해서 속도를 높이는 것이라고 나는 이해했다.

자바5는 thread pool, concurrent collection 등 (단일 코어) 외에 나머지 코어를 활용하기 위한 도구를 지원했다. 자바7은 fork/join 프레임워크를 제공했지만 개발자가 활용하기 어려웠다. (있는지도 몰랐다.) 자바8에서는 병렬 실행을 새롭고 단순한 방식으로 접근할 수 있는 방법을 제공한다.

  • 스트림API
  • 메서드에 코드를 전달하는 기법
  • 인터페이스의 디폴트 메서드

그렇다면 자바는 왜 이렇게 병렬성에 집중할까?

그 이유는, 프로그래밍 언어 생태계에 변화의 바람이 불었는데 그것은 빅데이터 라는 도전이었다. 즉, 대용량 데이터를 효과적으로 처리할 필요성이 커진 것이다.

자바8은 멀티코어 병렬성을 강화했다고 볼수 있다.

스트림

스트림이란 한 번에 한 개씩 만들어지는 연속적인 데이터 항목들의 모임이다. (유닉스와 C의 stdin, 자바의 System.in)

자바8에는 java.util.stream 패키지에 스트림 API가 추가되었는데, Stream<T>T 타입으로 구성된 일련의 항목을 의미한다.

해당 스트림으로 파이프라인을 만드는 데 필요한 많은 메서드를 제공한다.

스트림 API는 스레드를 사용하지 않고서도 여러 CPU 코어에 쉽게 input을 할당할 수 있는 병렬성을 가지고 있다. (4장과 7장에서 자세히 알아보자)

스트림의 연산작업

거의 모든 자바 애플리케이션은 컬렉션을 만들고 활용한다. 또한 for-each루프를 이용해서 각 요소를 반복하면서 작업을 수행하는 코드를 자주 봤을 것 이다.

하지만 스트림에서는 라이브러리 내부에서 루프가 처리되기 때문에 무분별한 for-each 문을 사용하지 않아도 된다.

public static void streamTest() {

  Item item1 = Item.builder().itemId("100001").cuinv(100).build();
  Item item2 = Item.builder().itemId("100001").cuinv(50).build();
  Item item3 = Item.builder().itemId("100002").cuinv(30).build();
  Item item4 = Item.builder().itemId("100003").cuinv(400).build();

  List<Item> itemList = Arrays.asList(item1, item2, item3, item4);

  Map<String, List<Item>> filterList = itemList.stream().filter((Item item) -> item.getCuinv() > 50) // 필터링
                                .collect(groupingBy(Item::getItemId)); // 그룹핑

  System.out.print(filterList);

  // {100001=[ch1.sunmin.StreamTest$Item@6fffcba5], 100003=[ch1.sunmin.StreamTest$Item@34340fab]}

}

스트림의 이러한 내부 반복 기능은 병렬 작업에도 도움이 된다.

컬렉션API는 어떻게 데이터를 저장하고 접근할지에 중점을 두는 반면 스트림은 데이터에 어떤 계산을 할 것인지 묘사하는 것에 중점을 둔다는 점을 기억하자!

동적 파라미터화로 메서드에 코드 전달

코드 일부를 파라미터로 전달하는 기능이다. behavior parameterization 이라고 부른다. 스트림이 연산 동작을 할 때, 특정 코드(메서드)를 파라미터로 보내서 의도하는 특정 동작을 할 수 있게 하는 기능이다. (2장과 3장에서 자세히)

병렬성과 공유 가변 데이터

병렬성을 얻는 대신, 공유 가변 데이터(shared mutable data)를 쓰지 말아야 한다.

Stream<String> stream = Arrays.asList("선민", "광운", "혜린", "세정").stream();
int count = 0;

stream.forEach(i -> {
  count ++;
});

위와 같이 스트림을 선언하고, 메서드에 함수를 전달한 것이다. 여기서 쓰는 count는 공유된 가변 데이터이므로 컴파일이 안된다.

Description Resource Path Location Type Local variable count defined in an enclosing scope must be final or effectively final

공유된 변수나 객체가 있으면 병렬성에 문제가 발생하기 때문이다.

추후 이와 같은 유형의 문제를 어떻게 해결하는지 확인할 수 있다.

메서드(함수)와 람다를 일급 시민 (값으로)

자바8에서는 함수를 새로운 값의 Type으로 추가했다. int, double, Object, List와 같이 함수 자체를 값으로 취급한다는 뜻이다.

이렇게 함수를 “값” 으로 취급하면서 함수를 파라미터로 전달할 수 있게 되었다. 그리고 이러한 기능은 스트림과 같은 다른 자바8 기능의 토대를 제공했다.

메서드 참조

File.class 에 isHidden 이라는 메서드가 있는데, 이 메서드를 :: 를 이용해서 참조하는 코드이다.

메서드를 값으로 이용하기 때문에 아래와 같이 구현할 수 있다.

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

람다

람다 표현식이란 메서드(함수)를 하나의 식으로 표현해서 값으로 취급하는 것이다.

람다 문법을 이용하면 간결하게 코드를 구현할 수 있다. 함수를 일급값으로 넘겨주는 프로그램을 구현하는 것을 람다 문법 형식이라고 한다.

@FunctionalInterface
interface Calc { // 함수형 인터페이스의 선언
    public int plus(int x);
}

public static void lamdaTest() {
  Calc test = (x) -> x + 1; // 추상 메소드의 구현
  System.out.print(test.plus(3));
}

디폴트 메서드와 자바 모듈

  • 자바9에서는 패키지 모음을 포함하는 모듈 이라는 개념이 추가됐다.
  • 자바8에서는 인터페이스를 쉽게 바꿀 수 있도록 디폴트 메서드를 지원한다.

디폴트 메서드는 미래에 프로그램이 쉽게 변화할 수 있는 환경을 제공하는 기능이다.

즉, 이미 공개된 인터페이스를 변경하기 위해서, 구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스에 추가할 수 있는 기능을 제공해서 구현 클래스에서 해당 메서드를 구현하지 않아도 정상 작동할 수 있게 지원하는 것이다.

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

기타 함수형 프로그래밍

Optional<T> 와 패턴 매칭 기법 (if-else, switch 대체)에 대해서 알아본다.

Summary

자바는 지금까지 진화해왔다. 자바5 이후로 제너릭이 생기면서 ListList<String>등으로 진화했다. 또, Iterator 대신 for-each문을 사용할 수 있게 되었다. (원래 있던 건줄ㅋㅋ) 자바8에서는 고전적인 객체지향에서 벗어나 함수형 프로그래밍으로 다가섰다.

자바의 주요변화 : 람다 표현식, 메서드 참조, 스트림, 디폴트 메서드

댓글남기기