1. 소개
이 튜토리얼은 Java 8에있는 다양한 기능적 인터페이스와 일반적인 사용 사례 및 표준 JDK 라이브러리에서의 사용에 대한 가이드입니다.
2. Java 8의 Lambda
Java 8은 람다 표현식의 형태로 강력하고 새로운 구문 개선을 가져 왔습니다. 람다는 일류 언어 시민으로서 처리 할 수있는 익명 함수입니다. 예를 들어 메서드로 전달하거나 메서드에서 반환 할 수 있습니다.
Java 8 이전에는 일반적으로 단일 기능을 캡슐화해야하는 모든 경우에 대해 클래스를 만들었습니다. 이것은 원시 함수 표현으로 사용되는 것을 정의하기 위해 불필요한 상용구 코드가 많이 있음을 의미했습니다.
"Lambda 식 및 기능 인터페이스 : 팁 및 모범 사례" 문서에서는 기능 인터페이스 와 람다 작업 모범 사례에 대해 자세히 설명합니다. 이 가이드는 java.util.function 패키지 에있는 일부 특정 기능 인터페이스에 중점을 둡니다 .
3. 기능적 인터페이스
모든 기능적 인터페이스에는 유익한 @FunctionalInterface 어노테이션 이있는 것이 좋습니다 . 이는 인터페이스의 목적을 명확하게 전달하고 어노테이션이 달린 인터페이스가 조건을 충족하지 않는 경우 컴파일러가 오류를 생성 할 수 있도록합니다.
SAM (Single Abstract Method)을 사용하는 모든 인터페이스는 기능적 인터페이스 이며 그 구현은 람다 식으로 처리 될 수 있습니다.
Java 8의 기본 메소드는 추상 이 아니며 계산되지 않습니다. 기능 인터페이스에는 여전히 여러 기본 메소드 가있을 수 있습니다 . 함수의 문서를 보면이를 관찰 할 수 있습니다 .
4. 기능
람다의 가장 간단하고 일반적인 경우는 하나의 값을 받고 다른 값을 반환하는 메서드가있는 기능적 인터페이스입니다. 단일 인수의이 함수는 해당 인수의 유형과 반환 값으로 매개 변수화 된 Function 인터페이스로 표시됩니다 .
public interface Function<T, R> { … }
표준 라이브러리에서 Function 유형 의 용도 중 하나는 Map.computeIfAbsent 메서드입니다. 이 메서드는 키별로 맵에서 값을 반환하지만 키가 맵에 아직없는 경우 값을 계산합니다. 값을 계산하기 위해 전달 된 함수 구현을 사용합니다.
Map<String, Integer> nameMap = new HashMap<>();
Integer value = nameMap.computeIfAbsent("John", s -> s.length());
이 경우 키에 함수를 적용하고 맵에 넣고 메서드 호출에서 반환하여 값을 계산합니다. W e는 전달 및 반환 된 값 유형과 일치하는 메서드 참조로 람다를 대체 할 수 있습니다 .
우리가 메서드를 호출하는 객체는 사실 메서드의 암시 적 첫 번째 인수라는 것을 기억하십시오. 이를 통해 인스턴스 메서드 길이 참조를 Function 인터페이스 로 캐스팅 할 수 있습니다.
Integer value = nameMap.computeIfAbsent("John", String::length);
기능 인터페이스는 기본이 작성 우리는 하나에 여러 기능을 결합하고 순차적으로 실행할 수 있습니다 방법 :
Function<Integer, String> intToString = Object::toString;
Function<String, String> quote = s -> "'" + s + "'";
Function<Integer, String> quoteIntToString = quote.compose(intToString);
assertEquals("'5'", quoteIntToString.apply(5));
quoteIntToString의 함수의 조합 시세 의 결과에 적용되는 함수 intToString 기능.
5. 원시 함수 전문화
기본 유형은 제네릭 유형 인수가 될 수 없으므로 가장 많이 사용되는 기본 유형 double , int , long 및 인수 및 반환 유형의 조합에 대한 Function 인터페이스 버전이 있습니다.
- IntFunction , LongFunction , DoubleFunction : 인수가 지정된 유형이고 반환 유형이 매개 변수화 됨
- ToIntFunction , ToLongFunction , ToDoubleFunction : 반환 유형이 지정된 유형이고 인수가 매개 변수화 됨
- DoubleToIntFunction , DoubleToLongFunction , IntToDoubleFunction , IntToLongFunction , LongToIntFunction , LongToDoubleFunction : 인수와 반환 유형 모두 이름에 지정된대로 기본 유형으로 정의 됨
예를 들어, short 를 취하고 byte를 반환하는 함수에 대한 기본 기능 인터페이스는 없지만 우리가 직접 작성하는 것을 막는 것은 없습니다.
@FunctionalInterface
public interface ShortToByteFunction {
byte applyAsByte(short s);
}
이제 ShortToByteFunction에 의해 정의 된 규칙을 사용하여 short 배열을 바이트 배열로 변환하는 메서드를 작성할 수 있습니다 .
public byte[] transformArray(short[] array, ShortToByteFunction function) {
byte[] transformedArray = new byte[array.length];
for (int i = 0; i < array.length; i++) {
transformedArray[i] = function.applyAsByte(array[i]);
}
return transformedArray;
}
단락 배열을 바이트 배열에 2를 곱한 값으로 변환하는 방법은 다음과 같습니다.
short[] array = {(short) 1, (short) 2, (short) 3};
byte[] transformedArray = transformArray(array, s -> (byte) (s * 2));
byte[] expectedArray = {(byte) 2, (byte) 4, (byte) 6};
assertArrayEquals(expectedArray, transformedArray);
6. Two-Arity 함수 전문화
두 개의 인수로 람다를 정의하려면 BiFunction , ToDoubleBiFunction , ToIntBiFunction 및 ToLongBiFunction 이라는 이름에 " Bi" 키워드 를 포함하는 추가 인터페이스를 사용해야 합니다.
BiFunction 에는 인수와 반환 유형이 모두 생성 된 반면 ToDoubleBiFunction 및 기타 기능을 사용하면 원시 값을 반환 할 수 있습니다.
표준 API에서이 인터페이스를 사용하는 일반적인 예 중 하나는 Map.replaceAll 메소드에 있습니다.이를 통해Map의 모든 값을 일부 계산 된 값으로 바꿀 수 있습니다.
급여에 대한 새 값을 계산하고 반환하기 위해 키와 이전 값을받는 BiFunction 구현을 사용합시다 .
Map<String, Integer> salaries = new HashMap<>();
salaries.put("John", 40000);
salaries.put("Freddy", 30000);
salaries.put("Samuel", 50000);
salaries.replaceAll((name, oldValue) ->
name.equals("Freddy") ? oldValue : oldValue + 10000);
7. 공급자
공급 기능 인터페이스는 또 다른입니다 기능의 인수를 고려하지 않습니다 전문. 우리는 일반적으로 값의 지연 생성에 사용합니다. 예를 들어, 이중 값 을 제곱하는 함수를 정의 해 보겠습니다 . 값 자체는받지 않지만 다음 값 의 공급자 :
public double squareLazy(Supplier<Double> lazyValue) {
return Math.pow(lazyValue.get(), 2);
}
이를 통해 공급자 구현을 사용하여이 함수를 호출하기위한 인수를 느리게 생성 할 수 있습니다 . 인수 생성에 상당한 시간이 걸리는 경우 유용 할 수 있습니다. Guava의 sleepUninterruptibly 메서드 를 사용하여 시뮬레이션합니다 .
Supplier<Double> lazyValue = () -> {
Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS);
return 9d;
};
Double valueSquared = squareLazy(lazyValue);
공급 업체의 또 다른 사용 사례 는 시퀀스 생성을위한 로직을 정의하는 것입니다. 이를 설명하기 위해 정적 Stream.generate 메서드를 사용하여 피보나치 수 의 스트림 을 생성 해 보겠습니다 .
int[] fibs = {0, 1};
Stream<Integer> fibonacci = Stream.generate(() -> {
int result = fibs[1];
int fib3 = fibs[0] + fibs[1];
fibs[0] = fibs[1];
fibs[1] = fib3;
return result;
});
Stream.generate 메서드에 전달하는 함수 는 공급자 기능 인터페이스를 구현합니다 . 생성자로서 유용하려면 공급 업체는 일반적으로 일종의 외부 상태가 필요합니다. 이 경우 상태는 마지막 두 개의 피보나치 시퀀스 번호로 구성됩니다.
이 상태를 구현하려면 람다 내부에서 사용되는 모든 외부 변수가 사실상 final이어야 하므로 두 개의 변수 대신 배열을 사용합니다 .
의 다른 전문 공급 기능 인터페이스를 포함 BooleanSupplier , DoubleSupplier , LongSupplier 및 IntSupplier 그 반환 유형 프리미티브 해당됩니다.
8. 소비자
받는 반대로 공급자 는 소비자가 제네릭 인수와 반환 값 없음을지지 않습니다. 부작용을 나타내는 기능입니다.
예를 들어, 콘솔에 인사말을 인쇄하여 이름 List에있는 모든 사람을 맞이해 봅시다. List.forEach 메서드에 전달 된 람다 는 소비자 기능 인터페이스를 구현합니다 .
List<String> names = Arrays.asList("John", "Freddy", "Samuel");
names.forEach(name -> System.out.println("Hello, " + name));
의 전문 버전도 있습니다 소비자 - DoubleConsumer , IntConsumer 및 LongConsumer 인수로 원시 값을받을 -. 더 흥미로운 것은 BiConsumer 인터페이스입니다. 사용 사례 중 하나는 맵 항목을 반복하는 것입니다.
Map<String, Integer> ages = new HashMap<>();
ages.put("John", 25);
ages.put("Freddy", 24);
ages.put("Samuel", 30);
ages.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));
또 다른 특수 BiConsumer 버전 세트는 ObjDoubleConsumer , ObjIntConsumer 및 ObjLongConsumer로 구성 되며 두 개의 인수를받습니다. 인수 중 하나는 생성되고 다른 하나는 기본 유형입니다.
9. 술어
수학적 논리에서 술어는 값을 받고 부울 값을 반환하는 함수입니다.
조건부 기능 인터페이스는 특수화의 기능 총칭 값을 수신하고, 부울을 리턴한다. 술어 람다 의 일반적인 사용 사례 는 값 모음을 필터링하는 것입니다.
List<String> names = Arrays.asList("Angela", "Aaron", "Bob", "Claire", "David");
List<String> namesWithA = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
위의 코드에서는 Stream API를 사용하여 List을 필터링 하고 문자 "A"로 시작하는 이름 만 유지합니다. 술어 구현 필터링 로직을 캡슐화한다.
이전의 모든 예에서 와 같이 기본 값을받는이 함수의 IntPredicate , DoublePredicate 및 LongPredicate 버전이 있습니다.
10. 연산자
운영자 인터페이스는 동일한 값 유형을 수신하고 반환하는 함수의 특수한 경우입니다. UnaryOperator의 인터페이스는 하나의 인자를 수신한다. Collections API의 사용 사례 중 하나는 List의 모든 값을 동일한 유형의 일부 계산 된 값으로 바꾸는 것입니다.
List<String> names = Arrays.asList("bob", "josh", "megan");
names.replaceAll(name -> name.toUpperCase());
List.replaceAll의 기능은 반환 무효 가 제자리에있는 값을 대체한다. 목적에 맞게 List의 값을 변환하는 데 사용되는 람다는 수신 한 것과 동일한 결과 유형을 반환해야합니다. 이것이 UnaryOperator 가 여기서 유용한 이유 입니다.
물론 name-> name.toUpperCase () 대신 메서드 참조를 사용할 수 있습니다.
names.replaceAll(String::toUpperCase);
BinaryOperator 의 가장 흥미로운 사용 사례 중 하나는 축소 작업입니다. 모든 값의 합계에서 정수 모음을 집계한다고 가정합니다. 함께 스트림 API, 우리는 컬렉터 사용하여이 작업을 수행 할 수 있습니다 , 하지만이 (가) 사용하는 것입니다 할 수있는보다 일반적인 방법으로 줄일 방법 :
List<Integer> values = Arrays.asList(3, 5, 8, 9, 12);
int sum = values.stream()
.reduce(0, (i1, i2) -> i1 + i2);
이 저감 방법은 초기 누산기 값과 수신 BinaryOperator의 기능. 이 함수의 인수는 동일한 유형의 값 쌍입니다. 함수 자체에는 동일한 유형의 단일 값으로 결합하는 논리도 포함되어 있습니다. 전달 된 함수는 연관 적이어야합니다 . 즉, 값 집계의 순서는 중요하지 않습니다. 즉, 다음 조건이 유지되어야합니다.
op.apply(a, op.apply(b, c)) == op.apply(op.apply(a, b), c)
BinaryOperator 연산자 함수 의 연관 속성을 사용하면 축소 프로세스를 쉽게 병렬화 할 수 있습니다.
물론, 전문의도있다 UnaryOperator 및 BinaryOperator 프리미티브 값, 즉 함께 사용할 수 DoubleUnaryOperator , IntUnaryOperator , LongUnaryOperator , DoubleBinaryOperator , IntBinaryOperator 및 LongBinaryOperator이 .
11. 레거시 기능 인터페이스
모든 기능 인터페이스가 Java 8에 등장한 것은 아닙니다. 이전 버전의 Java에서 나온 많은 인터페이스가 FunctionalInterface 의 제약 조건을 따르며이를 람다로 사용할 수 있습니다. 눈에 띄는 예에는 동시성 API에서 사용되는 Runnable 및 Callable 인터페이스가 포함됩니다. Java 8에서 이러한 인터페이스는 @FunctionalInterface 어노테이션으로 도 표시됩니다 . 이를 통해 동시성 코드를 크게 단순화 할 수 있습니다.
Thread thread = new Thread(() -> System.out.println("Hello From Another Thread"));
thread.start();
12. 결론
이 기사에서는 람다 식으로 사용할 수있는 Java 8 API에있는 다양한 기능 인터페이스를 조사했습니다. 기사의 소스 코드는 GitHub에서 사용할 수 있습니다 .
- https://docs.spring.io/spring-framework/docs/current/reference/html
- https://www.baeldung.com/java-8-functional-interfaces
'Java' 카테고리의 다른 글
Keycloak과 함께 사용자 지정 사용자 공급자 사용 (0) | 2021.03.11 |
---|---|
Spring에서 캐싱 사용방법 (0) | 2021.03.10 |
스프링 부트의 활성 및 준비 상태 프로브 (0) | 2021.03.10 |
스트림을 사용하여Map 작업 (0) | 2021.03.10 |
자바 HashMap 가이드 (0) | 2021.03.10 |