Comment 진짜 처음엔 별거 없을거라 생각했는데 생각보다 난이도가 높았던 문제 좀 애먹으면서 문제를 풀었던 것 같다. 메모리 제한이 생각보다 너무 컸고, 정답숫자 위치에서 인덱스 범위 잡는 것에 대한 고민...
hint for문의 범위는 상관없고 번호와 길이를 따로 저장해서 확인하면 좀 편하다.
Solution temp 인트를 선언해서 길이를 계속 저장해준다! 길이 저장 후 length가 n과 같거나 커졌을 때 for문을 탈출, 해당 i의 값이 n의 범위에 걸친 i이고, for문의 증강연산으로 인해 i-1이 정답번호이다. 여기서 값 추출은 length-n을 통해 0 ~ i.length()-1 까지 위치를 잡아서 출력하는데, 인덱스를 통해 출력하기 때문에 StringBuilder를 만들어서 reverse메서드로 먼저 뒤집어줬다.
public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes) {
return dishes.stream() // 요리 리스트에서 스트림 얻기
.filter(d -> d.getCalories() < 400) // 중간 연산
.sorted(comparing(Dish::getCalories)) // 중간 연산
.map(Dish::getName) // 중간 연산
.collect(toList()); // 최종 연산
}
중간 연산
단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것, 즉 게으르다 중간 연산을 합친 다음에 합쳐진 중간 연산 결과를 기반으로 최종 연산으로 한번에 처리하기 때문이다.
public static List<Apple> filter(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}
람다를 통한 filter사용
List<Apple> greenApples
= filter(inventory, (Apple a) -> GREEN.equals(a.getColor()));
Predicate의 자리에 람다가 들어간다.
그래서 함수형 인터페이스 이야기가 왜 나오는데?
람다가 인터페이스의 추상 메서드를 구현할 수 있는 추상 메서드의 구현체(디스크립터)가 될 수 있다.
함수 디스크립터
뜻 그대로 함수를 설명하는 것이라 생각하면 된다.
함수 Runnable의 run이 있을 때
파라미터 : null 반환 : void 으로 했을 때 해당 형태만 일치한다면, 해당 함수의 내부를 어떻게 사용할지 람다로 설명을 하는 것이다.
이 때 파라미터와 반환이 같은 것을 같은 시그니처를 갖는 함수라 볼 수 있는데 시그니처만 같게 한다면 람다를 생성할 수 있다는 뜻이다.
일치하는 시그니처
public Callable<String> fetch() {
return () -> "Tricky example ;-)";
}
파라미터 : x
반환 : String
일치하지 않는 시그니처
Predicate<Apple> p = (Apple a) -> a.getWeight();
파라미터 : Apple
반환 : boolean
@FunctionalInterface
함수형 인터페이스를 구현할 때 해당 어노테이션을 인터페이스 위에 붙였을 때 함수형 인터페이스가 불가하면 컴파일 에러를 발생시킨다.
ex) 추상메서드 2개
람다 활용 : 실행 어라운드 패턴
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(FILE))) {
return p.process(br);
}
}
함수형 인터페이스 사용
함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터라고 한다.
람다 표현식을 사용하려면 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.
이를 위해 자바에서는 java.util.function 패키지로 여러 함수형 인터페이스를 제공한다.
Predicate T → boolean
Consumer → void
Function<T, R> T → R
Suplier → T
UnaryOperator → T
기본형 특화
기본형의 경우 함수형 인터페이스 특성 상 제네릭을 받아서 디스크립터가 구성되는데, 이를 행하기 위해 박싱, 언박싱 과정이 불필요하게 들어가게된다.
때문에 이러한 불필요한 연산을 피할 수 있도록 돕는 IntPredicate, IntFunction 등 각 인터페이스에 맞게 지원한다.
함수 디스크립터는 Apple → boolean이기 때문에 형식 검사가 성공적으로 완료된다.
특별한 void 호환 규칙
람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다
void를 반환하는 함수 디스크립터는 다른 값이 반환되어도 사용이 가능함
형식 추론
Comparator<Apple> c =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
->
Comparator<Apple> c =
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
컴파일러가 람다의 표현식과 관련된 함수형 인터페이스를 추론한다.
지역 변수 사용
람다는 자기 바깥에 있는 접근 가능한 변수에 대해서도 사용이 가능하다
이와 같은 동작을 람다 캡처링 이라고 부른다.
쓰면 안되는 예시
<컴파일 에러>
int n = 0;
Runnable r = () -> System.out.println(n);
n++;
->
int n = 0;
do {
int finalN = n;
n++;
Runnable r = () -> System.out.println(finalN);
r.run();
} while (n != 5);
메서드 참조
기존에 있던 메서드 정의를 재활용해서 람다처럼 코드뭉치를 전달할 수 있다.
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
->
inventory.sort(comparing(Apple::getWeight));
그래서 왜 사용하는데?
기존 메서드 구현을 명시적으로 참조함으로써 가독성을 높일 수 있고, 재사용도 가능하기에 효율이 좋다.