📚 UnaryOperator란?
- : Java에서 제공하는 함수형 인터페이스
- 인수(argument)와 반환 결과(return)가 동일한 타입을 가진 경우에 사용하는 특수한 Function
- Java 8 부터 사용 가능
함수형 인터페이스란?
* = SAM Interface = Single Abstract Method Interface
* 1개의 추상 메서드를 가진 인터페이스
* UnaryOperator 외에도 Predicate, Consumer, Function 등이 있다.
UnaryOperator의 코드를 살펴보면 Function<T, T>를 상속받고 있는 것을 볼 수 있다. 제네릭 부분을 살펴보면 인수와 반환 결과 모두 동일한 타입으로 취급하는 것을 볼 수 있다.
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
위 설명들만으로는 UnaryOperator가 뭔지 아직 감이 잘 안잡힐거라 생각한다. 예제 코드를 통해 어떤 메서드를 지원하고 있는지 살펴보자.
📕 apply()
UnaryOperator는 람다 표현식으로 구현할 수 있다. apply()를 통해 인자를 전달하면, 해담 표현식에 따라 결과가 반환된다.
@Test
void multiply2() {
UnaryOperator<Integer> unaryOperator = i -> i * 2;
assertThat(unaryOperator.apply(1)).isEqualTo(2); // 1*2 = 2
assertThat(unaryOperator.apply(2)).isEqualTo(4); // 2*2 = 4
assertThat(unaryOperator.apply(3)).isEqualTo(6); // 3*2 = 6
assertThat(unaryOperator.apply(4)).isEqualTo(8); // 4*2 = 8
}
📙 identity()
위에서 잠깐 UnaryOperator를 살펴봤을 때, static 메서드인 identity를 확인할 수 있었다. return t -> t; 라는 아주 간단한 코드를 통해 예상할 수 있다시피, identity()를 통해 생성한 UnaryOperator는 인자로 받은 값을 그대로 반환하는 것을 확인할 수 있다.
@Test
void identity() {
UnaryOperator<Boolean> unaryOperator = UnaryOperator.identity();
assertThat(unaryOperator.apply(true)).isTrue();
assertThat(unaryOperator.apply(false)).isFalse();
}
📗 andThen()
여러개의 UnaryOperator를 엮을 수 있다. 아래와 같이 호출할 순서를 지정하고 싶은 경우 등에서 사용할 수 있다.
@Test
void andThen() {
UnaryOperator<Integer> plus10 = i -> i + 10;
UnaryOperator<Integer> multipleDouble = i -> i * 2;
assertThat(plus10.andThen(multipleDouble).apply(1)).isEqualTo(22); // (1+10)*2 = 22
assertThat(multipleDouble.andThen(plus10).apply(1)).isEqualTo(12); // (1*2)+10 = 12
}
📘 compose()
여러개의 UnaryOperator를 엮을 수 있다. andThen()과는 다르게 인자로 넣은 UnaryOperator를 먼저 수행한다.
@Test
void compose() {
UnaryOperator<Integer> plus10 = i -> i + 10;
UnaryOperator<Integer> multipleDouble = i -> i * 2;
assertThat(plus10.compose(multipleDouble).apply(1)).isEqualTo(12); // (1*2)+10 = 12
assertThat(multipleDouble.compose(plus10).apply(1)).isEqualTo(22); // (1+10)*2 = 22
}
📝 기타 예제
Java에서 UnaryOperator를 쓰는 곳이 있나 궁금해져서 예제를 찾아봤다. Stream::iterator에서도 사용하고 있었다. 두번째 인자에 UnaryOperator를 넣을 수 있다.
@Test
void iterate() {
Stream.iterate(1,n->n + 1)
.limit(10)
.forEach(System.out::println);
}
이외에도 어떤 상황에 사용할 수 있을지 예제를 생각해보았다. 아래의 multiply2 예제처럼 stream에서 여러번 재사용하기 위해 선언할 수도 있고, andThen() 예제처럼 여러 로직의 순서를 명시적으로 나타내며 실행하고 싶을 때 사용할 수도 있을 것 같다.
@Test
void multiply2() {
UnaryOperator<Integer> unaryOperator = i -> i * 2;
List<Integer> list = List.of(1, 2, 3, 4, 5);
List<Integer> doubleList = list.stream()
.map(unaryOperator)
.collect(Collectors.toList());
System.out.println(list);
System.out.println(doubleList);
}
@Test
void andThen() {
UnaryOperator<Integer> plus10 = i -> i + 10;
UnaryOperator<Integer> multipleDouble = i -> i * 2;
assertThat(plus10.andThen(multipleDouble).apply(1)).isEqualTo(22); // (1+10)*2 = 22
assertThat(multipleDouble.andThen(plus10).apply(1)).isEqualTo(12); // (1*2)+10 = 12
}
함수형 인터페이스를 반드시 써야하냐고 묻는다면 나는 반드시는 아니라고 생각한다. 너무 많이 사용하면 코드의 복잡성만 올라가는데 가끔 함수형 인터페이스를 사용함으로써 좀 더 가독성이 좋은 케이스들이 있다. 개인적으로는 함수형 인터페이스를 적용한 뒤에 아 이게 더 읽기 편한데? 라는 생각이 들 때 쓰는 것 같다.
참고
'Develop > Java' 카테고리의 다른 글
[Java] compiler message file broken 에러 (0) | 2024.03.17 |
---|---|
[Mockito] Invalid use of argument matchers! 에러 (0) | 2023.09.27 |
[Java] replaceAll 대신 replace 사용하기 (2) | 2023.01.22 |
[Java] 동시성 이슈 해결하기 (1) (0) | 2022.12.09 |
[JPA] delete문이 N개가 발생한다고요?😱 (2) | 2022.11.13 |