목록이펙티브자바 (88)
도당탕탕
public static void main(String[] args) { primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE)) .filter(mersenne -> mersenne.isProbablePrime(50)) .limit(20) .forEach(System.out::println); } static Stream primes() { return Stream.iterate(TWO,BigInteger::nextProbablePrime); } 위 작업에 parallel() 함수를 추가한다고 해서, 성능이 개선되지는 않는다. 실제로 parallel() 함수를 추가하면 2배 정도 시간이 소요된다. pipeline을 병렬화 하는 작업은 Stream.iter..
원소 시퀀스, 즉 일련의 원소를 반환하는 메서드는 수없이 많다. 자바 7까지는 이런 메서드의 반환 타입으로 다음과 같은 타입을 사용했다. Collection, Set, List E[]와 같은 배열 Iterable 인터페이스 즉 기본인 컬렉션 인터페이스를 사용했다. for-each문에서만 쓰이거나 반환된 원소 시퀀스가 일부 Collection 메서드를 구현할 수 없을 때는 Iterable 같은 인터페이스를 사용했다. 아니면 성능이 민감한 상황에서는 배열을 썼다. 하지만 자바8에 스트림이 나오면서 다음과 같은 선택들이 더 복잡해지게 되었다. 스트림 문제 스트림 문제는 다음과 같다. 스트림은 반복을 지원하지 않는다. 따라서 스트림과 반복을 알맞게 조합해야 좋은 코드가 나온다. API를 스트림만 반환하도록 짜놓..
스트림 패러다임은 계산을 일련의 transformation으로 구조화하는 것인데, 여기서 transformation은 이전 단계의 결과에 대해 항상 pure function 성질을 유지해야 한다. pure function 이란 결과가 항상 input에만 의존하는 함수를 말한다. 이를 달성하기 위해서는, stream 작업도중에 중간 결과나 최종 결과들이 side effect가 없어야 한다. side effect는 외부 상태를 변경하거나 예상치 못한 에러가 발생하는 상황을 말한다. forEach 작업은 항상 stream의 상태를 확인하기 위해서만 사용할 것 다음 코드는 각각의 단어에 대한 빈도수 테이블을 만드는 코드이다. Map freq = new HashMap(); try(Stream words = new..
스트림 API는 다량의 데이터 처리 작업을 돕고자 자바 8에서 추가되었다. 이 API가 제공하는 추상 개념 중 핵심은 다음과 같이 두 가지이다. 스트림은 데이터 원소의 유한 혹은 무한 시퀀스를 뜻한다. 스트림 파이프라인은 이 원소들로 수행하는 연산 단계를 표현하는 개념이다. 즉 스트림 파이프라인은 소스 스트림에서 시작해 종단 연산으로 끝나며, 그 사이에 하나 이상의 중간 연산이 있을 수 있다. 각 중간 연산은 스트림을 어떠한 방식으로 변환한다. 중간 연산들은 모두 한 스트림을 다른 스트림으로 변환하는데, 변환된 스트림의 원소 타입은 변환 전 스트림의 원소 타입과 같을 수도 있고 다를 수도 있다. 종단 연산은 마지막 중간 연산이 내놓은 스트림에 최후의 연산을 가한다. 즉 원소를 정렬해 컬렉션에 담거나, 특..
LinkedHashMap 클래스에 있는 removeEldestEntry 함수를 오버라이딩 해서, 클래스를 캐시로 사용할 수 있다. 이 함수는 put 메서드가 호출될 때마다 호출이 되고, true를 반환할 경우 가장 오래된 데이터를 삭제되도록 구현이 돼있다. protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 100; } 위 방법 대신 람다를 사용해서 LinkedHashMap 의 static factory or constructor에 function object를 전달해서 구현하는 것이 낫다. @FunctionalInterface interface EldestEntryRemovalFunction { boolean remove(Ma..
람다가 익명 클래스보다 나은 점 중 가장 큰 특징은 간결함이다. 하지만 람다보다도 더 간결하게 만드는 방법이 존재한다. 바로 메서드 참조를 통해 더 간결하게 만들 수 있다. 다음 코드를 보자 람다를 사용한 map.merge() 코드 //merge 매개변수 : key, value, BiFunction(? super value, ? super vlaue, ? extends value,) map.merge(key, 1, (count, incr) -> count + incr); 이 코드는 키가 맵 안에 없다면 키와 숫자 1을 매핑하고, 이미 있다면 기존 매핑 값을 증가 시킨다. 깔끔해 보이지만, 매개변수인 count와 incr은 크게 하는 일 없이 공간을 꽤 차지한다. 즉 이 람다는 두 인수의 합을 단순히 반환..
Collections.sort(words, new Comparator() { @Override public int compare(String s1, String s2) { return Integer.compare(s1.length(),s2.length()) } }); 위와 같이 anonymous class를 사용해서 sort함수를 호출할 수 있다. 하지만 코드의 길이가 길어진다는 단점이 있다. Java8부터는 하나의 추상 메서드를 가진 인터페이스를 특별하게 취급하기 시작했다. 이러한 인터페이스를 functional interface 라고 부른다. 언어적 차원에서 이러한 인터페이스를 lamda expression으로 부를 수 있도록 허락하게 되었다. 람다는 anonymous class와 유사하지만 더 간결..
마커 인터페이스란 아무 메서드도 담지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스를 말한다. 대표적인 예로 Serializable 인터페이스가 있다. Serializable은 자신을 구현한 클래스의 인스턴스는 ObjectOutputStream을 통해 쓸 수 있다는, 즉 직렬화할 수 있다고 알려준다. 마커 인터페이스의 장점 마커 애너테이션이 신식이라 말하고 마커 인터페이스보다 좋다고 말하는데 이는 사실이 아니다. 오히려 다음과 같이 두 가지 측면에서 마커 애너테이션보다 마커 인터페이스가 낫다고 한다. 마커 인터페이스는 이를 구현한 클래스의 인스턴스들을 구분하는 타입으로 쓸 수 있으나, 마커 애너테이션은 그렇지 않다. 즉 인터페이스가 어엿한 타입이므로 런타임에야 발견될 오류..