티스토리 뷰
- 아래 내용은 '모던 자바 인 액션'을 읽고 정리한 글로 책 내용의 순서를 따라간다.
- 상세한 내용이나 예시는 책과 상이할 수 있다.
- 예제 코드에서 사용되고 있는 스펙
- Java 15 preview (record라는 새로운 클래스 개념을 사용하기 위해서 해당 프리뷰 버전을 사용. 하위 버전의 경우는 일반 클래스를 생성한 후 getter를 만들고 사용하면 됨) - 참고
- JUnit 5
- Gradle 6.7
- 소스코드
람다(Lambda)란?
-
람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다.
-
람다의 특징
- 익명 : 메서드의 이름이 없기 때문에 구현해야 할 코드에 대한 걱정거리가 줄어든다.
- 함수 : 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트를 포함하지만 클래스에 종속되지 않으므로 함수라고 부른다.
- 전달 : 메서드 인수로 전달하거나 변수로 저장할 수 있다.
- 간결성 : 익명 클래스처럼 클래스 이름, 메서드 이름, 파라미터 타입, 반환 타입 등이 없기 때문에 코드가 간결하다.
-
람다의 구조
- 파라미터 리스트
- 파라미터 타입 생략 가능
- 파라미터가 하나일때
()
생략 가능
- 화살표
- 람다 바디
- 실행 내용이 단일 실행(한 줄)일때
{}
생략 가능.{}
이 생략되면 return 키워드와 ;(세미콜론)도 같이 생략해야 한다.
- 실행 내용이 단일 실행(한 줄)일때
- 파라미터 리스트
-
표현식 스타일(expression style) 람다(기본 문법)
(parameters) -> expression
-
블록 스타일(block style) 람다
(parameters) -> { statements; }
Q. 퀴즈-1) 람다 문법
- 앞에서 설명한 람다 규칙에 맞지 않는 람다 표현식을 고르시오.
- () → {}
- () → "Raoul"
- () → { return "Mario"; }
- (Integer i) → return "Alan" + i;
- (String s) → { "Iron Man"; }
A. 정답
- 정답
- 4번과 5번이 유효하지 않은 람다 표현식이다.
- 4번은 (Integer i) → { return "Alan" + i; }처럼 되어야 올바른 람다 표현식이다.
- 5번은 (String s) → "Iron Man" 또는 (String s) → { return "Iron Man"; }처럼 되어야 올바른 람다 표현식이다.
람다 예제
-
불리언 표현식
(List<String> list) -> list.isEmpty()
-
객체 생성
() -> new Apple(10)
-
객체에서 소비
(Apple a) -> { System.out.println(a.getWeight()); }
-
객체에서 선택/추출
(String s) -> s.length()
-
두 값을 조합
(int a, int b) -> a * b
-
두 객체 비교
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
어디에, 어떻게 람다를 사용할까?
- 함수형 인터페이스(Functional Interface)
- 함수 디스크립터(Function Descriptor)
- 위의 내용은 따로 포스팅해서 다룰 예정이다.
람다 활용 : 실행 어라운드 패턴
- 자원 처리 (예를 들면 데이터베이스의 파일 처리)에 사용하는 순환 패턴(recurrent pattern)은 자원을 열고, 처리한 다음에, 자원을 닫는 순서로 이루어진다.
- 설정(setup)과 정리(cleanup) 과정은 대부분 비슷하다.
- 아래 그림과 같은 형식의 코드를 실행 어라운드 패턴(execute around pattern)이라고 부른다.
public String processFile() throw IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine(); // <- 실제 필요한 작업을 하는 행이다.
}
}
1단계 : 동작 파라미터화를 기억하라
- 현재 코드는 파일에서 한 번에 한 줄만 읽을 수 있다.
- 한 번에 두 줄을 읽거나 가장 자주 사용되는 단어를 반환하려면 어떻게 해야 할까?
- 기존의 설정, 정리 과정은 재사용하고 processFile 메서드만 다른 동작을 수행하도록 명령할 수 있다면 좋을 것이다.
- 여기서는 processFile의 동작을 파라미터화하면 된다.
- processFile 메서드가 한 번에 두 행을 읽게 하려면 우선
BufferedReader
를 인수로 받아서String
을 반환하는 람다가 필요하다.
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
2단계 : 함수형 인터페이스를 이용해서 동작 전달
- 함수형 인터페이스 자리에 람다를 사용할 수 있다.
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
- 정의한 인터페이스를 processFile 메서드의 인수로 전달할 수 있다.
public String processFile(BufferedReaderProcessor p) throws IOException {
...
}
3단계 : 동작 실행
- 이제
BufferedReaderProcessor
에 정의된 process 메서드의 시그니처 (BufferedReader → String
)와 일치하는 람다를 전달할 수 있다. - 따라서 processFile 바디 내에서
BufferedReaderProcessor
객체의 process를 호출할 수 있다.
public String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
4단계 : 람다 전달
// 한 행을 처리하는 코드다.
String oneLine = processFile((BufferedReader br) -> br.readLine());
// 두 행을 처리하는 코드다.
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
함수형 인터페이스 사용
- 별도 포스팅
형식 검사, 형식 추론, 제약
형식 검사
- 람다가 사용되는 컨텍스트(context)를 이용해서 람다의 형식(type)을 추론할 수 있다.
- 어떤 컨텍스트에서 기대되는 람다 표현식의 형식을 대상 형식(target type)이라고 부른다.
List<Apple> heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
- 위 코드의 형식 확인 과정을 보여준다.
- filter 메서드의 선언을 확인한다.
- filter 메서드는 두 번째 파라미터로
Predicate<Apple>
형식(대상 형식)을 기대한다. Predicate<Apple>
은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스다.- test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
- filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.
- 람다 표현식이 예외를 던질 수 있다면 추상 메서드도 같이 예외를 던질 수 있도록 throws로 선언해야 한다.
같은 람다, 다른 함수형 인터페이스
- 대상 형식(target typing)이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.
Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
ToIntBiFunction<Apple, Apple> c2 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
BiFunction<Apple, Apple, Integer> c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
-
다이아몬드 연산자
-
Java 7에서도 다이아몬드 연산자(<>)로 컨텍스트에 따른 제네릭 형식을 추론할 수 있었다.
-
주어진 클래스 인스턴스 표현식을 두 개 이상의 다양한 컨텍스트에 사용할 수 있다.
-
이때 인스턴스 표현식의 형식 인수는 컨텍스트에 의해 추론된다.
List<String> listOfStrings = new ArrayList<>(); List<Integer> listOfIntegers = new ArrayList<>();
-
-
특별한 void 호환 규칙
-
람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다.
-
물론 파라미터 리스트도 호환되어야 한다.
// Predicate는 불리언 반환값을 갖는다. Predicate<String> p = s -> list.add(s); // Consumer는 void 반환값을 갖는다. Consumer<String> b = s -> list.add(s);
-
-
할당문 컨텍스트, 메서드 호출 컨텍스트(파라미터, 반환값), 형변환(cast) 컨텍스트 등으로 람다 표현식의 형식을 추론할 수 있다.
Q. 퀴즈-2) 형식 검사 문제, 다음 코드를 컴파일할 수 없는 이유는?
-
다음 코드의 문제를 해결하시오.
Object o = () -> { System.out.println("Tricky example"); };
A. 정답
-
정답
-
람다 표현식의 컨텍스트는 Object(대상 형식)다.
-
하지만 Object는 함수형 인터페이스가 아니다.
-
따라서 void 형식의 함수 디스크립터를 갖는
Runnable
로 대상 형식을 바꿔서 문제를 해결할 수 있다.Runnable r = () -> { System.out.println("Tricky example"); };
-
람다 표현식을 명시적으로 대상 형식을 제공하는
Runnable
로 캐스팅해서 문제를 해결할 수도 있다.Object o = (Runnable) () -> { System.out.println("Tricky example"); };
-
같은 함수형 디스크립터를 가진 두 함수형 인터페이스를 갖는 메서드를 오버로딩할 때 이와 같은 기법을 활용할 수 있다.
-
어떤 메서드의 시그니처가 사용되어야 하는지를 명시적으로 구분하도록 람다를 캐스트할 수 있다
-
예를 들어
execute(() -> {})
라는 람다 표현식이 있다면Runnable
과Action
의 함수 디스크립터가 같으므로 누구를 가리키는지가 명확하지 않다.public void execute(Runnable runnable) { runnable.run(); } public void execute(Action<T> action) { action.act(); } @FunctionalInterface interface Action { void act(); }
-
하지만
execute((Action) () -> {});
처럼 캐스트를 하면 누구를 호출할 것인지가 명확해진다.
-
형식 추론
- 자바 컴파일러는 람다 표현식이 사용된 컨텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.
- 즉, 대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론할 수 있다.
- 결과적으로 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략할 수 있다.
- 즉, 자바 컴파일러는 다음처럼 람다 파라미터 형식을 추론할 수 있다.
- 여러 파라미터를 포함하는 람다 표현식에서는 코드 가독성 향상이 더 두드러진다.
- 상황에 따라 명시적으로 형식을 포함하는 것이 좋을 때도 있고 형식을 배제하는 것이 가독성을 향상시킬 때도 있다.
- 어떤 방법이 좋은지 정해진 규칙은 없다.
- 개발자 스스로 어떤 코드가 가독성을 향상시킬 수 있는지 결정해야 한다.
- 필자의 경우 잘 모르는 메서드를 쓰거나 할때 간혹 명시하기도 하지만 대부분 생략한다.
지역 변수 사용
- 람다 표현식에서는 익명 함수가 하는 것처럼 자유 변수(free variable, 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있다.
- 이와 같은 동작을 람다 캡처링(capturing lambda)이라고 부른다.
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
-
람다는 인스턴스 변수와 정적 변수를 자유롭게 캡처(자신의 바디에서 참조할 수 있도록)할 수 있다.
-
하지만 그러려면 지역 변수는 명시적으로 final로 선언되어 있어야 하거나 실질적으로 final로 선언된 변수와 똑같이 사용되어야 한다.
-
즉, 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처할 수 있다.
-
인스턴스 변수 캡처는 final 지역 변수 this를 캡처하는 것과 마찬가지다.
-
지역 변수의 제약
- 인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치한다.
- 람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있다.
- 따라서 자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공한다.
- 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에는 한 번만 값을 할당해야 한다는 제약이 생긴 것이다.
- 또한 지역 변수의 제약 때문에 외부 변수를 변화시키는 일반적인 명령형 프로그래밍 패턴(병렬화를 방해하는 요소)에 제동을 걸 수 있다.
- 클로저(closure)
- 원칙적으로 클로저란 함수의 비지역 변수를 자유롭게 참조할 수 있는 함수의 인스턴스를 가리킨다.
- 예를 들어 클로저를 다른 함수의 인수로 전달할 수 있고 클로저는 클로저 외부에 정의된 변수의 값에 접근하고, 값을 바꿀 수 있다.
- Java 8의 람다와 익명 클래스는 클로저와 비슷한 동작을 수행한다.
- 다만 람다와 익명 클래스는 람다가 정의된 메서드의 지역 변수의 값을 바꿀 수 없다.
- 람다가 정의된 메서드의 지역 변숫값은 final 변수여야 한다.
- 인스턴스 변수는 스레드가 공유하는 힙에 존재하므로 특별한 제약이 없다.
메서드 참조(Method References)
- Java 8의 새로운 기능이다.
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
- 위와 같이 람다로 표현된 코드가 있을 때
java.util.Comparator.comparing
메서드를 활용해서 다음과 같이 표현할 수 있다.
inventory.sort(comparing(Apple::getWeight));
- 메서드 참조는 특정 메서드만 호출하는 람다의 축약형이라고 생각할 수 있다.
- 메서드 참조를 이용하면 기존 메서드 구현으로 람다 표현식을 만들 수 있다.
- 이때 명시적으로 메서드명을 참조함으로써 가독성을 높일 수 있다.
메서드 참조 만드는 방법
- 정적 메서드 참조 : ex)
s -> Integer.parseInt(s)
→Integer::parseInt
- 다양한 형식의 인스턴스 메서드 참조 : ex)
s -> s.length()
→String::length
- 기존 객체의 인스턴스 메서드 참조 : ex) 람다 외부 변수 apple에 대해
() -> apple.getWeight()
→apple::getWeight
- 생성자, 배열 생성자, super 호출 등에 사용할 수 있는 특별한 형식의 메서드 참조도 있다.
- 컴파일러는 람다 표현식의 형식을 검사하던 방식과 비슷한 과정으로 메서드 참조가 주어진 함수형 인터페이스와 호환하는지 확인한다.
- 즉, 메서드 참조는 컨텍스트의 형식과 일치해야 한다.
Q. 퀴즈-3) 메서드 참조
-
다음의 람다 표현식과 일치하는 메서드 참조를 구현하시오.
1. ToIntFunction<String> stringToInt = (String s) -> Integer.parseInt(s); 2. BiPredicate<List<String, String>> contains = (list, element) -> list.contains(element); 3. Predicate<String> startsWithNumber = (String string) -> this.startsWithNumber(string);
A. 정답
-
1. ToIntFunction<String> stringToInt = Integer::parseInt 2. BiPredicate<List<String, String>> contains = List::contains 3. Predicate<String> startsWithNumber = this::startsWithNumber
생성자 참조
ClassName::new
처럼 클래스명과 new 키워드를 이용해서 깆곤 생성자의 참조를 만들 수 있다.- 인수가 없는 생성자, 즉
Supplier
의() -> Apple
과 같은 시그니처를 갖는 생성자가 있다고 가정하자.
Supplier<Apple> c1 = Apple::new; // Apple()인 디폴트 생성자 참조
Apple a1 = c1.get(); // Supplier의 get 메서드를 호출해서 새로운 Apple 객체를 만들 수 있다.
// 위 코드는 다음과 같다.
Supplier<Apple> c1 = () -> new Apple();
Apple(Integer weight)
라는 시그니처를 갖는 생성자는Function
인터페이스의 시그니처와 같다.
Function<Integer, Apple> c2 = Apple::new; // Apple(Integer weight)의 생성자 참조
Apple a2 = c2.apply(110); // Function의 apply 메서드에 무게를 인수로 호출해서 새로운 Apple 객체를 만들 수 있다.
// 위 코드는 다음과 같다.
Function<Apple> c2 = (weight) -> new Apple(weight);
- 이제 다양한 색과 무게를 갖는 사과를 다음과 같은 방식으로 만들어 보려고 한다.
BiFunction<String, Integer, Apple> c3 = Apple::new; // Apple(String color, Integer weight)의 생성자 참조
Apple a3 = c3.apply(GREEN, 110); // BiFunction의 apply 메서드에 색과 무게를 인수로 제공해서 새로운 Apple 객체를 만들 수 있다.
// 위 코드는 다음과 같다.
BiFunction<String, Integer, Apple> c3 = (color, weight) -> new Apple(color, weight);
- 인스턴스화하지 않고도 생성자에 접근할 수 있는 기능을 다양한 상황에 응용할 수 있다.
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static {
map.put("apple", Apple::new);
map.put("orange", Orange::new);
// 등등
}
public static Fruit giveMeFruit(String fruit, Integer weight) {
return map.get(fruit.toLowerCase()) // map에서 Function<Integer, Fruit>를 얻었다.
.apply(weight); // Function의 apply 메서드에 정수 무게 파라미터를 제공해서 Fruit를 만들 수 있다.
}
Q. 퀴즈-4) 생성자 참조
- 지금까지 인수가 없거나, 하나 또는 둘인 생성자를 생성자 참조로 바꾸는 방법을 살펴봤다.
Color(int, int, int)
처럼 인수가 세 개인 생성자의 생성자 참조를 사용하려면 어떻게 해야 할까?
A. 정답
-
-
생성자 참조 문법은
ClassName::new
이므로Color
생성자의 참조는Color::new
가 된다. -
하지만 이를 사용하려면 생정자 참조와 일치하는 시그니처를 갖는 함수형 인터페이스가 필요하다.
-
현재 이런 시그니처를 갖는 함수형 인터페이스는 제공되지 않으므로 우리가 직접 다음과 같은 함수형 인터페이스를 만들어야 한다.
public interface TriFunction<T, U, V, R> { R apply(T t, U u, V v); }
-
이제 다음처럼 새로운 생성자 참조를 사용할 수 있다.
TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new;
-
람다 표현식을 조합할 수 있는 유용한 메서드
- Java 8 API의 몇몇 함수형 인터페이스는 다양한 유틸리티 메서드를 포함한다.
- 예를 들어,
Comparator
,Function
,Predicate
같은 함수형 인터페이스는 람다 표현식을 조합할 수 있도록 유틸리티 메서드를 제공한다. - 여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수 있다.
- 디폴트 메서드(default method)가 이것을 가능하게 해준다.
- 디폴트 메서드에 대해서는 다른 포스팅에서 다루도록 하겠다.
Comparator 조합
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
역정렬
- 사과의 무게를 역정렬하고 싶다고 다른
Comparator
인스턴스를 만들 필요가 없다. - 인터페이스 자체에서 주어진 비교자의 순서를 뒤바꾸는 reverse라는 디폴트 메서드를 제공하기 때문이다.
inventory.sort(comparing(Apple::getWeight).reversed());
Comparator 연결
- 무게가 같은 두 사과가 존재한다면 다른 정렬 조건이 필요할 수도 있다.
- thenComparing 메서드로 두 번째 비교자를 만들 수 있다.
- thenComparing은 (comparing 메서드처럼) 함수를 인수로 받아 첫 번째 비교자를 이용해서 두 객체가 같다고 판단되면 두 번째 비교자에 객체를 전달한다.
inventory.sort(comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getCountry)); // 두 사과의 무게가 같으면 국가별로 정렬
Predicate 조합
Predicate
인터페이스는 복잡한 프레디케이트를 만들 수 있도록 negate, and, or 세 가지 메서드를 제공한다.
Predicate<Apple> notRedApple = redApple.negate(); // 기존 프레디케이트 객체 redApple의 결과를 반전시킨 객체를 만든다.
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150); // 두 프레디케이트를 연결해서 새로운 프레디케이트 객체를 만든다.
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150)
.or(apple -> GREEN.equals(a.getColor())); // 프레디케이트 메서드를 연결해서 더 복잡한 프레디케이트 객체를 만든다.
Function 조합
Function
인터페이스에서 제공하는 람다 표현식도 조합할 수 있다.Function
인터페이스는Function
인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공한다.- andThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환한다.
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); // 수학으로는 write g(f(x)) 또는 (g ∘ f)(x)라고 표현
int result = h.apply(1); // 4를 반환
- compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공한다.
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g); // 수학으로는 f(g(x)) 또는 (f ∘ g)(x)라고 표현
int result = h.apply(1); // 3을 반환
- 다음처럼 문자열로 구성된 편지 내용을 변환하는 다양한 유틸리티 메서드가 있다고 가정하자.
public class Letter {
public static String addHeader(String text) {
return "From Raoul, Mario and Alan: " + text;
}
public static String addFooter(String text) {
return text + " Kind regards";
}
public static String checkSpelling(String text) {
return text.replaceAll("labda", "lambda");
}
}
- 여러 유틸리티 메서드를 조합해서 다양한 변환 파이프라인을 만들 수 있다.
Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
비슷한 수학적 개념
적분
$f(x) = x + 10$
$\int_3^7f(x)dx,또는,\int_3^7(x+10)dx$
- 공학에서는 함수가 차지하는 영역을 묻는 질문이 자주 등장한다.
- 이 예제에서 함수 f는 직선이므로 사다리꼴 기법으로 정답을 쉽게 찾을 수 있다.
1 / 2 X ((3 + 10) + (7 + 10)) X (7 - 3) = 60
- 이제 이 공식을 어떻게 자바로 표현할 수 있을까?
- 먼저 적분 기호나 dy/dx 등 이상한 기호를 어떻게 처리할 것인지가 문제다.
- 우선은 f와 한계값(여기서는 3.0과 7.0)을 인수로 받는 integrate라는 함수를 만들어야 한다.
integrate(f, 3, 7)
- 그러나 다음처럼 간단히 구현할 수는 없다.
integrate(x + 10, 3, 7)
- 우선 이 식에서는 x의 범위가 불분명하며, f를 전달하는 것이 아니라
x + 10
이라는 값을 전달하게 되기 때문에 잘못된 식이다. - 수학에서 dx의 정체는 'x를 인수로 받아 x + 10의 결과를 만드는 함수'로 정의할 수 있다.
자바 8 람다로 연결
-
람다를 이용해서
integrate((double x) -> x + 10, 3, 7)
또는integrate((double x) -> f(x), 3, 7)
같이 나타낼 수 있다. -
C가 정적 메서드 f를 포함하는 클래스라 가정하면 메서드 참조를 사용해서 코드를 더 간단하게 만들 수 있다.
integrate(C::f, 3, 7)
-
여기서 integrate 메서드를 구현할 때
DoubleFunction
인터페이스를 사용해서 구현할 수 있다.public double integrate(DoubleFunction<Double> f, double a, double b) { return (f.apply(a) + f.apply(b)) * (b - a) / 2.0; }
-
또한
DoubleUnaryOperator
를 이용해도 결과를 박싱할 필요가 없다.public double integrate(DoubleUnaryOperator f, double a, double b) { return (f.applyAsDouble(a) + f.applyAsDouble(b)) * (b - a) / 2.0; }
-
참고로 수학처럼 f(a)라고 표현할 수 없고
f.apply(a)
라고 구현했는데, 이는 자바가 진정으로 함수를 허용하지 않고 모든 것을 객체로 여기는 것을 포기할 수 없기 때문이다.
'프로그래밍 > Java' 카테고리의 다른 글
[Modern Java] 동작 파라미터화(Behavior Parameterization) (2) | 2021.01.17 |
---|---|
[Stream API] 중간 연산 - sorted 메서드 (0) | 2021.01.07 |
[Stream API] 중간 연산 - peek 메서드 (0) | 2021.01.07 |
[Stream API] 중간 연산 - flatMap 메서드 (0) | 2021.01.05 |
[Stream API] 중간 연산 - map 메서드 (2) | 2021.01.04 |
- Total
- Today
- Yesterday
- import문
- jdk14
- 변경사항
- #배열 #array #map 함수
- Stream API
- 스트림
- flatMapToDouble
- flatMapToInt
- java
- modern java
- 람다
- 자바
- 중간 연산
- 다짐
- 충북 콕! 콕!
- 계획
- 회고
- Java8
- IntelliJ
- 토이 프로젝트
- flatMapToLong
- mapToObj
- 개발자
- 목표
- #React #ReactJS #리액트
- lambda
- flaMap
- 익명 클래스
- #예제 #example #가계부 #Account Book
- java14
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |