Java 8에서 기본으로 제공하는 함수형 인터페이스는 무엇이 있을까?
1. 표준 함수형 인터페이스
- Java 8부터 기본으로 제공하는 함수형 인터페이스
- Java.lang.function 패키지
활용 예시
- ArrayList 클래스의 forEach 메소드를 보면 Consumer<T>를 매개 값으로 활용하고 있는 것을 알 수 있다
- Consumer<T> : T를 받아서 아무 값도 리턴하지 않는 함수형 인터페이스
public class ArrayList<E> extends AbstractList<E>
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
final Object[] es = elementData;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++)
action.accept(elementAt(es, i));
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
따라서 람다를 활용하여 편하게 ArrayList를 출력할 수 있다.
public class Exe {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("B");
names.add("D");
names.add("A");
names.add("C");
names.forEach(name -> System.out.println(name));
}
}
2. Function<T, R>
- 구현할 추상 메소드 = R apply(T t);
- T 타입을 받아서 R을 리턴
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
사용 예시 1
import java.util.function.Function;
public class Test {
public static void main(String[] args) {
// 익명 내부 클래스로 구현
Function<Integer, String> numberToStringInner = new Function<Integer, String>() {
@Override
public String apply(Integer integer) {
return integer.toString();
}
};
// 람다를 활용하여 구현
Function<Integer, String> numberToStringLambda = num -> Integer.toString(num);
System.out.println(numberToStringInner.apply(10) + " : " + numberToStringInner.apply(10).getClass());
System.out.println(numberToStringLambda.apply(10) + " : " + numberToStringLambda.apply(10).getClass());
}
}
람다형식이 더 가독성이 좋다
사용 예시 2
- andThen(), compose()를 활용하여 조합해서 사용할 수 있다.
- andThen() = 현재 메소드를 실행 후 매개 변수로 받은 함수를 나중에 처리.
- compose() = 매개 변수로 받은 함수를 먼저 처리. ( andThen()과 반대 )
import java.util.function.Function;
public class Test {
public static void main(String[] args) {
Function<Integer, Integer> plus10 = num -> num+10;
Function<Integer, Integer> div2 = num -> num/2;
System.out.println("plus10(10) : " + plus10.apply(10));
System.out.println("div2(10) : " + div2.apply(10));
System.out.println("plus10.andThen(div2).apply(10) : " + plus10.andThen(div2).apply(10)); // (10+10)/2
System.out.println("plus10.compose(div2).apply(10) : " + plus10.compose(div2).apply(10)); // (10/2)+10
}
}
3. BiFunction<T, U, R>
- 구현할 추상 메서드 = R apply(T t, U u);
- T, U를 받아서 R타입으로 반환하는 인터페이스
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
사용 예시
import java.util.function.BiFunction;
public class Test {
public static void main(String[] args) {
BiFunction<Integer, Integer, String> addToString = (num1, num2) -> Integer.toString(num1 + num2);
System.out.println(addToString.apply(1,2) + " : " + addToString.apply(1,2).getClass());
}
}
4. UnaryOperator<T>
- 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스
- Function<T, R>의 특수한 형태로 Function<T, T>를 상속 받음, 따라서 apply()로 구현
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
사용 예시
import java.util.function.UnaryOperator;
public class Test {
public static void main(String[] args) {
UnaryOperator<Integer> plus50 = (i) -> i + 50;
UnaryOperator<Integer> multiply2 = (i) -> i * 2;
System.out.println(plus50.andThen(multiply2).apply(5));
}
}
5. BinaryOperator<T>
- 동일한 타입의 입력값 두 개를 받아 리턴하는 함수 인터페이스
- BiFunction<T, U, R>의 특수한 형태로 BiFunction<T,T,T>를 상속받음. 따라서 apply()로 구현
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
사용 예시
import java.util.function.BinaryOperator;
public class Test {
public static void main(String[] args) {
BinaryOperator<String> introduce = (name, food) -> "My name is " + name + " I like " + food;
System.out.println("introduce(veneas, chicken) : "+ introduce.apply("veneas", "chicken"));
}
}
6. Consumer<T>
- 구현할 추상 메소드 = void accept(T t);
- T를 받아서 아무 값도(void) 리턴하지 않는다
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
사용 예시
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
Consumer<String> printName = System.out::println;
List<String> fruits = Arrays.asList("AAPL", "MSFT", "GOOGL");
Consumer<List<String>> lowerCase = list -> list.forEach(e -> System.out.print(e.toLowerCase() + " "));
printName.accept("veneas");
lowerCase.accept(fruits);
}
}
7. Supplier<T>
- 구현할 추상 메서드 = T. get();
- 아무 값도 받지 않고 T를 반환
@FunctionalInterface
public interface Supplier<T> {
T get();
}
사용 예시
import java.util.function.Supplier;
public class Test {
public static void main(String[] args) {
Supplier<Integer> get10 = () -> 10;
System.out.println("get10 : " + get10.get());
}
}
8. Predicate<T>
- 구현할 추상 메소드 = boolean test(T t);
- T를 받아서 boolean(true, false)를 반환
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
사용 예시
import java.util.function.Predicate;
public class Test {
public static void main(String[] args) {
Predicate<String> containJava = s -> s.contains("Java");
Predicate<Integer> isEven = (i) -> i%2 == 0;
Predicate<Integer> moreThan5 = (i) -> i>5;
System.out.println("containJava(Java) : "+ containJava.test("Java")); // true
System.out.println("containJava(Python) : " + containJava.test("Python")); // false
System.out.println("isEven(10) : " + isEven.test(10)); // true
System.out.println("moreThan5.and(isEven).test(6) : " + moreThan5.and(isEven).test(6)); // true
System.out.println("moreThan5.or(isEven).test(4) : " + moreThan5.or(isEven).test(3)); // false
}
}