자바는 안전한 언어다. C, C++같은 언어에서 흔히 보이는 메모리 충돌 오류에서 안전하고, 자바로 작성한 클래스는 시스템의 다른 부분에서 무슨 짓을 하든 불변식이 지켜지기 때문이다. 하지만 자바라도 다른 클랜스로부터의 침범을 다 막을 수 있는 건 아니다. 따라서 누군가 불변식을 깨뜨리려 한다는 가정하에 방어적으로 프로그래밍을 해야한다. 외부에서 내부를 수정할 수 있는 경우 보통 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능하다. 하지만 의도치 않게 외부에서 내부를 수정하도록 허락하는 상황이 생길 수 있다. 다음과 같이 기간(Period)을 표현하는 클래스 있다. 개발자는 Period 값이 한번 정해지면 변하지 않게 할 의도로 만들었다고 가정해보자. public final class Perio..
오류는 가능한 한 빨리 발생한 곳에서 잡아야 한다. 오류를 발생한 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 오류 발생 지점을 찾기 어려워진다. 메서드의 매개변수 또한 마찬가지이다. 메서드 몸체가 실행되기 전에 매개변수를 확인한다면 잘못된 값이 넘어왔을 때 깔끔한 방식으로 예외를 던질 수 있다. 매개변수 검사를 제대로 하지 못하면 발생하는 문제 1. 메서드가 수행 중간에 모호한 예외를 던지며 실패할 수 있다. 2. 메서드가 잘 수행되지만 잘못된 결과를 반환할 수 있다. 3. 메서드는 문제없이 수행됐지만, 미래의 알 수 없는 시점에 메서드와 관련 없는 오류를 낼 수 있다. (실패 원자성을 어기는 상황) public과 protected 메서드는 예외를 문서화하자 @throws 자바독 태그를 사용해 ..
Java8부터는 parallel 메서드만 호출하면 파이프라인을 병렬 실행할 수 있는 스트림을 지원한다. 동시성 프로그램을 작성하기는 쉬워지고 있지만, 올바르고 빠르게 작성하는 일은 여전히 어려운 일이다. 동시성 프로그래밍을 할 때는 안전성과 응답 가능 상태를 유지해야 한다. 병렬 스트림 파이프라인 프로그래밍에서도 다를 바 없다. 파이프라인 병렬화로 성능 개선을 기대하기 어려운 경우 // 스트림을 사용해 처음 20개의 메르센 소수를 생성하는 프로그램 public class MersennePrime { public static void main(String[] args) { primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE)) //.parallel() ...
Java8 이전에는 원소를 반환하는 타입을 결정하는 것이 어렵지 않았다. 그런데 Java8에 스트림이 등장하면서 이 선택이 복잡한 일이 되어버렸다. 스트림이 반복(iteration)을 지원하지 않기 때문이다. Stream 인터페이스는 Iterable 인터페이스의 추상 메서드를 모두 포함하고 정의한 방식대로 동작하지만 Iterable 인터페이스를 확장하지 않았다. 그래서 for-each로 스트림을 반복할 수 없다. 스트림을 반복하기 위한 방법 어댑터 메서드 Stream를 Iterable로 중개해주는 어댑터를 생성해서 사용한다. // Stream를 Iterable로 중개해주는 어댑터 public static Iterable iterableOf(Stream stream){ return stream::itera..
스트림을 처음 접하면 이해하기 어렵거나 어떤 장점이 있는지 공감하기 힘들 수 있다. 스트림은 그저 또 하나의 API가 아닌, 함수형 프로그래밍에 기초한 패러다임이기 때문이다. 스트림 패러다임의 핵심은 계산을 일련의 변환으로 재구성하는 것이다. 이때 각 변환 단계는 이전 단계의 결과를 받아 처리하는 순수 함수여야 한다. 순수 함수란? 오직 입력만이 결과에 영향을 주는 함수를 말한다. 순수 함수는 다른 가변 상태를 참조하지 않고, 함수 스스로 다른 상태를 변경하지 않는다. 이렇듯 스트림 연산에 전달되는 함수 객체는 모두 부작용(Side Effect)가 없어야 한다. 스트림 제대로 활용하기 forEach 아래 예제는 스트림 API의 이점을 잘 살리지 못한, 스트림 코드를 가장한 반복 코드이다. Map freq..
Java8에 추가된 스트림 API는 다량의 데이터 처리를 돕고자 만들어졌다. 스트림 API가 제공하는 핵심 추상 개념은 스트림과 스트림 파이프라인 이렇게 두 가지다. 스트림 API 핵심 추상 개념 1. 스트림 데이터 원소의 유한 또는 무한 시퀀스를 나타낸다. 2. 스트림 파이프라인 원소들로 수행하는 연산 단계를 표현하는 개념이다. 대표적인 스트림 소스로는 컬렉션, 배열, 파일, 정규표현식 패턴 매처, 난수 생성기 등이 있다. 컬렉션 배열 파일 정규표현식 패턴 매처 난수 생성기 무한 스트림 or 유한 스트림 기본 스트림 - IntStream, LongStream, DoubleStream 스트림 파이프라인 특징 스트림 파이프라인은 소스 스트림으로 시작해 종단 연산으로 끝나며, 그 사이에 중간 연산이 들어갈 ..
자바가 람다를 지원하면서 API를 작성하는 모범 사례도 바뀌었다. 대표적으로 상위 클래스의 기본 메서드를 재정의해 원하는 동작을 구현하는 템플릿 메서드 패턴을 예로 들 수 있다. 모던 자바에서는 템플릿 메서드 패턴 대신 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 방식을 해법으로 제시하고 있다. 이 말은 함수 객체를 매개변수로 받는 생성자와 메서드를 더 많이 만들어야 한다는 뜻이다. 이 경우에는 함수형 매개변수 타입을 올바르게 선택해야 한다. 이미 자바 표준 라이브러리에는 다양한 용도의 표준 함수형 인터페이스를 제공하고 있다. 따라서 용도에 맞는 게 있다면, 직접 구현하기보다는 표준 함수형 인터페이스를 활용하자. 표준 함수형 인터페이스 java.util.function은 총 43개의 함수형 인터페이..
람다는 익명 클래스보다 간결하다는 장점이 있다. 그런데 함수 객체를 람다보다 더 간결하게 만드는 방법이 있다. 바로 메서드 참조(Method Reference)다. 메서드 참조의 장점 메서드 참조는 람다의 거추장스러운 코드를 명확하고 간결하게 만들어준다. 다음 예제는 Map에 Key가 있다면 기존 매핑 값을 증가시키는 코드다. //람다 map.merge(key,1,(count,incr)-> count + incr); count와 incr는 크게 하는 일 없이 공간을 차지한다. 이를 메서드 참조로 개선하면 똑같은 결과를 더 보기 좋게 표현할 수 있다. //정적 메서드 참조 map.merge(key,1,Integer::sum); 메서드 참조를 사용하면 매개변수의 수가 늘어날수록 제거할 수 있는 코드가 많아진..
예전에는 함수 객체를 만드는 주요 수단으로 익명 클래스를 많이 사용했다. 함수 객체 추상 메서드를 하나만 담은 인터페이스의 인스턴스 // 익명 클래스를 함수 객체로 사용 - 낡은 기법이다. Collections.sort(words, new Comparator() { public int compare(String s1, String s2){ return Integer.compare(s1.length(), s2.length()); } }); 하지만 이 방식은 낡은 기법이고 코드가 너무 길어서 함수형 프로그래밍에 적합하지 않다. Java8부터는 추상 메서드가 하나인 인터페이스는 특별한 대우를 받게 되었다. 지금은 함수형 인터페이스로 부르는 이 인터페이스를 람다 표현식으로 만들 수 있게 된 것이다. // 람다식..