모던 자바 인 액션 스터디 - 자바 8 의 변화
1장 자바 8, 9, 10, 11
객체지향은 90년대에 각광을 받았습니다. 하나는 캡슐화 덕분에 C에 비해 소프트웨어 엔지니어링적인 문제가 훨씬 적었고 다른 하나는 객체지향 패러다임 덕에 윈도우 95 및 다른 프로그래밍 모델에 쉽게 대응할 수 있었기 때문입니다. 이 패러다임은 '모든 것은 객체다' 라고 요약이 가능합니다.
하지만 프로그래밍 언어 생태계에 변화의 바람이 불기 시작했습니다. 빅데이터라는 도전에 직면하면서 멀티코어 컴퓨터나 컴퓨팅 클러스터를 이용해서 데이터를 효과적으로 처리할 필요성이 생겼습니다.
이에 따라 자바 8에서는 새로운 개념이 추가되었습니다.
자바 함수
프로그래밍 언어에서 함수(function)는 메서드(method)라는 의미로 사용됩니다. 자바의 함수는 이에 더해 수학적인 함수처럼 사용되며 부작용을 일으키지 않는 함수를 의미합니다.
자바 8 에서는 함수를 새로운 값의 형식으로 추가를 했습니다. 이것은 멀티코어에서 병렬 프로그래밍을 활용할 수 있는 스트림과 연계될 수 있도록 함수를 만든 것인데요. 이것의 장점을 알아보겠습니다.
장점은 바로 값을 조작할 수 있다는 것입니다. 무슨 말이냐면, 프로그래밍 언어의 핵심은 값을 바꾸는 것입니다. 역사적으로 프로그래밍 언어에서는 이 값을 일급 값이라고 부릅니다.
일급: First Class (일급 시민에서 유래)
이전 자바에서 메서드나 클래스 같은 구조체들을 자유롭게 전달할 수 없기 때문에 이런 것들은 이급 시민이라고 부릅니다.
런타임 때 메서드를 일급 시민으로 만든다면 프로그래밍할 때 아주 유용하게 사용할 수 있습니다. 따라서 자바 8에서는 이급 시민을 일급 시민으로 바꿀 수 있는 기능을 추가했습니다. 즉 메서드를 값으로 전달할 수 있다는 것이죠.
참고로 이런 기능들은 SmallTalk, Javascript 에서는 이미 가능합니다.
메서드 참조
메서드를 일급 값으로 사용을 하려면 메서드 참조(Method Reference)를 이용합니다.
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isHidden();
}
}
이전 자바에서는 File 클래스에 isHidden() 메서드가 있음에도 불구하고 이런식으로 복잡하게 감싼 후에 전달해야 했습니다. 하지만 메서드 레퍼런스 ( :: ) 를 이용하여 일급 값으로 취급할 수 있습니다.
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
자바 8 에서는 더 이상 메서드가 이급값이 아닌 일급 값으로 취급합니다.
List<Apple> greenApples = filterApples(inventory, FilteringApples::isGreenApple);
이런 식으로 메서드의 레퍼런스가 들어갈 수 있습니다.
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;
}
isGreenApple이라는 boolean을 리턴하는 함수의 레퍼런스가 메서드의 인자로 들어가는 모습입니다.
여기서 Predicate라는 게 나오는데 predicate 란 수학에서 인수로 값을 받아 true나 false를 반환하는 함수를 말한다고 합니다.
혹은 다음과 같이 람다를 사용할 수 있습니다.
List<Apple> greenApples2 = filterApples(inventory, (Apple a) -> "green".equals(a.getColor()));
한 번만 쓰고 말 거면 굳이 메서드를 따로 만들 필요 없이 다음과 같이 더 짧은 식으로 가능합니다.
람다와 메서드 레퍼런스 중 무엇을 써야 할까?
람다가 간편하긴 하지만 몇 줄 이상으로 길어진다고 생각해볼까요? 그렇게 되면 동작을 한눈에 파악하기 어렵게 됩니다. 그러면 익명 람다보다는 코드가 수행하는 일을 잘 설명하는 이름을 가진 메서드를 따로 정의하는 것이 더 좋겠습니다. 즉 코드의 명확성이 우선시 되어야 한다는 것입니다.
스트림
스트림 API라는 것을 이용하면 루프를 신경 쓸 필요가 없습니다.
기존의 for 루프를 이용하는 방법은 외부 반복 (Externl Iteration)이라고 하고 스트림 API 는 내부 반복 (Internal Iteration) 이라고 합니다. 즉 중첩된 반복과 긴 코드를 굉장히 간결하게 바꿀 수 있습니다.
멀티코어
기존 자바의 컬렉션을 이용하게 된다면 어떤 문제가 발생할 수 있습니다. 많은 요소를 가진 목록을 반환한다면 시간이 오래 걸릴 수 있겠죠. 단일 CPU로 동작했을 때 말입니다. 하지만 멀티코어 컴퓨터에서는 서로 다른 CPU에 작업을 할당할 수 있습니다. 병렬성이라고 하죠. 즉 멀티코어를 최대한 활용하기 위해 스트림을 사용한다고 볼 수도 있습니다.
디폴트 메서드
자바 List를 정렬하려면 어떻게 해야 할까?
List 자체에는 sort 가 없기 때문에 (자바 8 이전에서는) Collections.sort() 를 이용해야 합니다.
자바 8 에서는 인터페이스 자체에 (List) 메서드를 추가할 수 있는 기능을 제공합니다. 결정적으로 구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스에 추가할 수 있다는 말입니다.
방법은 인터페이스 규격 명세에 default라는 키워드를 붙이면 됩니다.
List 인터페이스를 열어보면 다음과 같은 sort 메서드가 정의된 것을 볼 수 있습니다. 결과적으로 자바 8부터는 sort 메서드를 일일이 구현하지 않아도 바로 쓸 수 있게 되는 것입니다.
한 가지 의문점은 자바에서는 다중 상속이 되지 않는데 저렇게 돼버린다면 다중 상속이 되는 구조가 아닌가 생각할 수 있겠죠. 책에서는 어느 정도 그렇다고 말을 합니다. 나중에 이것을 피할 수 있는 방법을 설명한다고 하니 나중에 읽어보겠습니다.
함수형 프로그래밍 아이디어
자바에 포함된 함수형 프로그래밍의 핵심 아이디어 두 가지는 메서드와 람다를 일급 값으로 사용하는 것이고 다른 하나는 가변 공유 상태가 없는 병렬 실행을 이용해 효율적이고 안전하게 메서드를 호출하는 것입니다.
1장 스터디 완료
코드: https://github.com/tech-book-study/modern-java-in-action
깃 허브의 코드 출처는 한빛미디어 홈페이지 소스파일입니다.
http://www.hanbit.co.kr/support/supplement_survey.html?pcode=B4926602499