EffectiveJava

[EffectiveJava] 7장 람다와 스트림

42. 익명 클래스보다는 람다를 사용하라

// 42-1 익명 클래스의 인스턴스를 함수 객체로 사용 - 낡은 기법이다! (254쪽)
Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

public static void main(String[] args) {
    Item42 item = new Item42();
    item.exam01();
}
// 42-2 람다식을 함수 객체로 사용 - 익명 클래스 대체 (255쪽)
Collections.sort(words, (s1,s2)-> Integer.compare(s1.length(), s2.length()));
Collections.sort(words, comparingInt(String::length));
words.sort(comparingInt(String::length));
public void exam02(int version){
    switch (version){
        case 1 : Collections.sort(words, (s1,s2)-> Integer.compare(s1.length(), s2.length())); break;
        case 2 : Collections.sort(words, Comparator.comparingInt(String::length)); break;
        case 3 : words.sort(Comparator.comparingInt(String::length)); break;
    }
    System.out.println("[42-exam02] "+words);
}

public static void main(String[] args) {
    Item42 item = new Item42();
    item.exam02(1);
}
// 42-3 상수별 클래스 몸체와 데이터를 사용한 열거 타입(코드 34-6)
public enum Operation {
    PLUS("+") {
        public double apply(double x, double y) { return x + y; }
    },
    MINUS("-") {
        public double apply(double x, double y) { return x - y; }
    },
    TIMES("*") {
        public double apply(double x, double y) { return x * y; }
    },
    DIVIDE("/") {
        public double apply(double x, double y) { return x / y; }
    };

    private final String symbol;

    Operation(String symbol) { this.symbol = symbol; }

    @Override public String toString() { return symbol; }
    public abstract double apply(double x, double y);
}
// 42-4 함수 객체(람다)를 인스턴스 필드에 저장해 상수별 동작을 구현한 열거 타입
public enum Operation {
    PLUS  ("+", (x, y) -> x + y),
    MINUS ("-", (x, y) -> x - y),
    TIMES ("*", (x, y) -> x * y),
    DIVIDE("/", (x, y) -> x / y);

    private final String symbol;
    private final DoubleBinaryOperator op;

    Operation(String symbol, DoubleBinaryOperator op) {
        this.symbol = symbol;
        this.op = op;
    }

    @Override public String toString() { return symbol; }

    public double apply(double x, double y) {
        return op.applyAsDouble(x, y);
    }
}

public static void main(String[] args) {
    double x = 10;
    double y = 2;
    for (Operation op : Operation.values()) {
        System.out.printf("%f %s %f = %f\\n", x, op.toString(), y, op.apply(x, y));
    }
}
// DoubleBinaryOperator - 함수 인터페이스중 하나로,
// Double 타입 인수 2개를 받아 Double 타입 결과를 돌려준다
@FunctionalInterface
public interface DoubleBinaryOperator {
    double applyAsDouble(double left, double right);
}

43. 람다보다는 메서드 참조를 사용하라

map.merge(key, 1, (count,incr) -> count+incr);
map.merge(key, 1, Integer::sum)
// 메서드 참조
service.execute(GoshThisClassNameIsHumongous::action);
// lamda
service.execute(()->action());
public static void main(String[] args) {
    Map<String, Integer> map = new HashMap();
    map.merge(key, 1, (count, incr) -> count + incr);
    map.merge(key, 1, Integer::sum);
}
service.execute(GoshThisClassNameIsHumongous::action);
service.execute(() -> action());

44. 표준 함수형 인터페이스를 사용하라

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
	return size() > 100;
}
// 44-1 불필요한 함수형 인터페이스 - 대신 표준 함수형 인터페이스를 사용하라.
@FunctionalInterface 
interface EldestEntryRemovalFunction<K,V> {
	boolean remove(Map<K,V> map, Map.Entry<K,V> eldest);
}
// Comparator
@FunctionInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

// ToIntBiFunction
@FunctionalInterface
public interface ToIntBiFunction<T, U> {
    int applyAsInt(T t, U u);
}
public interface ExecutorService extends Executor {
    // Callable<T>와 Runnable을 각각 인수로 하여 다중정의했다.
    // submit 메서드를 사용할 때마다 형변환이 필요해진다.
    <T> Future<T> submit(Callback<T> task);
    Future<?> submit(Runnable task);
}

45. 스트림은 주의해서 사용하라

// 45-1 사전 하나를 훑어 원소 수가 많은 아나그램 그룹들을 출력한다.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;

import static java.util.stream.Collectors.groupingBy;

public class Anagrams {
    public static void main(String[] args) throws IOException {
       File dictionary = new File(args[0]);
       int minGroupSize = Integer.parseInt(args[1]);

       Map<String, Set<String>> groups = new HashMap<>();
       try(Scanner s = new Scanner(dictionary)) {
           while(s.hasNext()){
               String word = s.next();
               groups.computeIfAbsent(alphabetize(word), 
									(unused) -> new TreeSet<>()).add(word);
           }
       }
       for (Set<String> group : groups.values())
           if (group.size() >= minGroupSize)
               System.out.println(group.size() + ": " + group);
		}

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}
// 45-2 스트림을 과하게 사용했다. - 따라 하지 말 것!
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;

import static java.util.stream.Collectors.groupingBy;

public class Anagrams {
    public static void main(String[] args) throws IOException {

       Path dictionary = Paths.get(args[0]);
       int minGroupSize = Integer.parseInt(args[1]);

       try (Stream<String> words = Files.lines(dictionary)) {
           words.collect(
            groupingBy(word -> word.chars().sorted()
                .collect(StringBuilder::new,
                 (sb, c) -> sb.append((char) c), 
                 StringBuilder::append).toString()))
           .values().stream()
           .filter(group -> group.size() >= minGroupSize)
           .map(group -> group.size() + ": " + group)
           .forEach(System.out::println);

       }
    }
}
// 45-3 스트림을 적절히 활용하면 깔끔하고 명료해진다.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;

import static java.util.stream.Collectors.groupingBy;

public class Anagrams {
    public static void main(String[] args) throws IOException {
        Path dictionary = Paths.get(args[0]);
        int minGroupsize = Integer.parseInt(args[1]);

        try(Stream<String> words = Files.lines(dictionary)) {
            words.collect(groupingBy(word -> alphabetize(word)))
                    .values().stream()
                    .filter(group -> group.size() >= minGroupsize)
                    .forEach(g -> System.out.println(g.size() + ": " + g));
        }
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}
static Stream<BigInteger> primes() {
    return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
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);
}
.forEach(mp -> System.out.println(mp.bitLength()+": "+mp))
// 45-4 데카르트 곱 계산을 반복 방식으로 구현
private static List<Car> newDeck() {
    List<Card> result = new ArrayList<>();
    for (Suit suit : Suit.values())
        for (Rank rank : Rank.values())
            result.add(new Card(suit, rank));
    return result;
}
// 45-5 데카르트 곱 계산을 스트림 방식으로 구현
private static List<Car> newDeck() {
    return Stream.of(Suit.values())
    .flatMap(Suit-> 
        Stream.of(Rank.values())
            .map(**rank-> new Card(suit, rank)**))
    .collect(toList());
}

46. 스트림에서는 부작용 없는 함수를 사용하라

// 46-1 스트림 패러다임을 이해하지 못한 채 API만 사용했다 - 따라 하지 말 것!
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()){
    words.forEach(word -> {
        freq.merge(word.toLowerCase(), 1L, Long::sum);
    });
}
// 46-2 스트림을 제대로 활용해 빈도표를 초기화한다.
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()){
    freq = words
				.collect(groupingBy(String::toLowerCase, counting()))
}
// 46-3 빈도표에서 가장 흔한 단어 10개를 뽑아내는 파이프라인
List<String> topTen = freq.keySet().stream()
    .sorted(comparing(freq::get).reversed()) // 빈도수 반환해서 reversed 정렬
    .limit(10)
    .collect(toList());
// 46-4 toMap 수집기를 사용하여 문자열을 열거 타입 상수에 매핑한다.
private static final Map<String, Operation> stringToEnum =
        Stream.of(values()).collect(
               **toMap(Object::toString, e -> e)**);
// 46-5 각 키완 해당 키의 특정 원소를 연관 짓는 맵을 생성하는 수집기
Map<Artist, Album> toHits = albums.collect(
                 toMap(Album::artist, a->a, maxBy(comparing(Album::sales)));
// 46-7 마지막에 쓴 값을 취하는 수집기
toMap(KeyMapper, valueMapper, (oldVal, newVal) -> newVal) // 마지막에 쓴 값을 취하는 수집기
words.collect(groupingBy(word -> alphabetize(word))); // 아나그램 프로그램에서 사용한 수집기이다.
Map<String, Long> freq = words
                  .collect(groupingBy(String::toLowerCase, counting()));

47. 반환 타입으로는 스트림보다 컬렉션이 낫다

// 47-1 자바 타입 추론의 한계로 컴파일 되지 않는다.
for (ProcessHandle ph : ProcessHandle.allProcesses()::iterator) { 
	// 프로세스를 처리한다.
}
// 47-2 스트림을 반복하기 위한 '끔찍한' 우회 방법
for (ProcessHandle ph : (Iterable<ProcessHandle>) 
									ProcessHandle.allProcess()::iterator) { // 프로세스를 처리한다. }
// 47-3 stream<E>를 Iterable<E>로 중개해주는 어댑터
public static <E> iterable<E> iterableOf(Stream<E> stream) {
    return stream::iterator;
}
for (ProcessHandle p : iterableOf(ProcessHandle.allprocesses())) { 
    // 프로세스 처리한다.
}
// 47-4 Iterable<E>를 Stram<E>로 중개해주는 어댑터
public static <E> Stream<E> streamOf(Iterable<E> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false);
}
// 47-5 입력 집합의 멱집합을 전용 컬렉션에 담아 반환한다.
public class PowerSet {
    public static final <E> Collection<Set<E>> of(Set<E> s) {
       List<E> src = new ArrayList<>(s);
       if(src.size() > 30) {
           throw new IllegalArgumentException("집합에 원소가 너무 많습니다(최대 30개).: " + s);
       }

       return new AbstractList<Set<E>>() {
           @Override
           public int size() {
							 // 멱집합의 크기는 2를 원래 집합의 원소 수만큼 거듭제곱한 것과 같다.
               return 1 << src.size(); 
           }

           @Override
           public boolean contains(Object o) {
               return o instanceof Set && src.containsAll((Set) o);
           }

           @Override
           public Set<E> get(int index) {
               Set<E> result = new HashSet<>();
               for (int i = 0; index != 0; i++, index >>=1) {
                   if((index & 1) == 1) {
                       result.add(src.get(i));
                   }
               }
               return result;
           }
       };
    }
}
// 47-6 입력 리스트의 모든 부분리스트를 스트림으로 반환한다.
public class SubLists {
  public static <E> Stream<List<E>> of(List<E> list) {
      return Stream.concat(Stream.of(Collections.emptyList()), 
					prefixes(list).flatMap(SubLists::suffixes));
  }

  private static <E> Stream<List<E>> prefixes(List<E> list) {
      return IntStream.rangeClosed(1, list.size())
              .mapToObj(end -> list.subList(0, end));
  }

  private static <E> Stream<List<E>> suffixes(List<E> list) {
      return IntStream.range(0, list.size())
              .mapToObj(start -> list.subList(start, list.size()));
  }
}
for (int start = 0; start < src.size(); start++) {
      for (int end = start + 1; end <= src.size(); end++) {
          System.out.println(src.subList(start, end));
      }
}
// 47-7 입력 리스트의 모든 부분리스트를 스트림으로 반환한다.
private static <E> Stream<List<E>> prefixes(List<E> list) {
  return IntStream.rangeClosed(0, list.size())
        .mapToObj(start -> 
            IntStream.rangeClosed(start + 1, list.size()).mapToObj(end -> list.subList(start,end)))
        .flatMap(x->x);}
}

48. 스트림 병렬화는 주의해서 적용하라

// 48-1 스트림을 사용해 처음 20개의 메르센 소수를 생성하는 프로그램
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<BigInteger> primes() {
        return Stream.iterate(TWO, BigInteger::nextProbablePrime);
    }
// 48-2 소수 계산 스트림 파이프라인 - 병렬화에 적합하다.
static long pi(long n) {
    return LongStream.rangeClosed(2, n)
                     .mapToObj(BigInteger::valueOf)
                     .filter(i -> i.isProbablePrime(50))
                     .count();
} // 31초
// 48-3 소수 계산 스트림 파이프라인 - 병렬화 버전
static long pi(long n) {
    return LongStream.rangeClosed(2, n)
                     .parallel() // 병렬화!
                     .mapToObj(BigInteger::valueOf)
                     .filter(i -> i.isProbablePrime(50))
                     .count();
} // 9.2초

Leave a Reply