람다는 이름도 없고, 문서화도 못한다. 따라서 코드 자체로 동작이 명확하지 않거나 코드 줄 수가 많아지면 람다를 쓰지 말아야 한다.
람다는 함수형 인터페이스에서만 쓰인다.
추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 쓸 수 있다.
자기 자신을 참조할 수 없다.
람다를 직렬화하는 일은 극히 삼가야 한다.
익명 클래스 사용 예시
new ActionListener() {
public void actionPerformed() {
System.out.println("Sdf");
}
}
이걸 함수 매개변수 자리에 작성 가능하다.
1번만 사용가능하며 1개만 생성가능하다.
그런데 문제점은 코드가 너무길다는것이 문제다. 특히나 추상 클래스의 인스턴스를 사용하거나, 추상 메서드를 여러개 가졌을 때
간결함 정도는 메소드참조 > 람다 > 익명 클래스 순이다.
람다 사용 예시
map.merge(key, 1, (cnt, inc) -> cnt + inc;);
map.merge(key, 1, Integer::sum);
정리하면 람다로 작성할 코드를 새로운 메소드에 담은 다음, 람다 대신 그 메소드 참조를 사용하는 방식으로 쓰면 i) 이름을 지을 수 있고, ii) 설명을 문서로 남길 수 있고 iii) 간결성 까지 챙길 수 있는 이점이 있다.
하지만 항상 그런 것은 아니다.
메소드 참조와 람다가 같은 클래스에 있을 때는 차라리 람다가 나을 수 있다.
service.execute(GoThisClassNameIsHumongous::action);
service.execute(() -> action());
람다에서 type은 컴파일 시점에 추론한다.
대신 박싱된 기본 타입을 넣어서 사용하지 말아야 한다.
표준 인터페이스 중 필요한 용도에 맞는 것이 없을 때는 @FunctionalInterface 애노테이션을 사용해야 한다. 1) 인터페이스가 람다용으로 설계된 것임을 명시할 수 있고 2) 추상 메소드가 오직 1개여야 컴파일 될 수 있게 하고, 3) 유지보수과정에서 누군가 추가하지 않게 되는 이점이 있기 때문이다.
내부 Data 원소(객체 참조나 기본 타입 값)들은 어디로부터든 올 수 있다. 기본적으로 순차적으로 수행된다. 병렬로도 할 수는 있으나, 효과를 볼 수 있는 상황은 많지 않다.
기존 코드는 스트림을 사용하도록 리팩토링을 하되, 새 코드가 더 나아보일 때만 반영해야 한다.
1) 원소들의 시퀀스를 일관되게 변환할 때
2) 원소들의 시퀀스를 필터링
3) 원소들의 시퀀스를 하나의 연산을 사용해 결합.
4) 원소들의 시퀀스를 컬렉션에 모을 때
5) 원소들의 시퀀스를 특정 조건을 만족하는 원소를 찾을 때
계산을 일련된 변환으로 재구성할 때. 각 변환 단계는 가능한 이전 단계의 결과를 받아 처리하는 순수함수여야 한다. 순수함수는 오직 입력만이 결과에 영향을 주는 함수를 의미한다. 다른 가변 상태를 참조하지 않고 함수 스스로도 다른 상태를 변경하지 않는 함수를 의미한다.
words.forEach( word -> {
freq.merge(word.toLowerCase(), 1L, Long::sum);
});
위 코드에서 freq.merge 부분이 문제다. 외부 상태를 수정하기 때문에..
for-Each 연산은 스트림의 계산 결과를 보고할 때만 사용하고 계산할 때는 사용하면 안된다.
i) Stream.iterate
ii) 중간 연산에 limit()
두 가지는 성능 향상이 되지 못한다.
ArrayList, HashMap, HashSet, ConcurrentHashMap, 배열 등이 병렬화에 효과가 좋다.
왜냐하면 모든 Data를 원하는 크기로 나눌 수 있고, 참조들이 가리키는 객체들이 메모리에 연속해서 저장되어 있기 때문이다. (Locality)
이 자체가 bulk연산을 병렬화할 때 중요하다.
collect : 병렬화 성능 x
reduce : 병렬화에서 good
i) 결합 법칙을 만족하고, ii) non-interfering iii) state-less 의 3가지 조건이 parrallel로 수행하기 위한 조건이다.
스트림의 요소를 수집한다.
사용 시 Collectors임에 주의가 필요하다.
주의사항
.collect(Collectors.toList()) // Java 8이상
stream.toList() // Java 16 이상
그룹화(groupingBy()) : 스트림의 요소를 특정 기준으로 그룹화
분할(partitioningBy()) : 지정된 조건에 맞게 일치하는 그룹과 일치하지 않는 그룹으로 분할하는 것.
그룹화 사용 예시
.collect(groupingBy( s-> {
if( ) return ~~;
else if( ) return ~~;
else retrun ~~;
}, counting())
);
분할 사용 예시
.collect(partitioningBy());