함수를 1급 객체처럼 다룰 수 있게 해주는 @FunctionalInterface 어노테이션으로, 인터페이스에 선언하여 단 하나의 추상 메서만 선언하도록 제한하는 역할을 한다. 함수형 인터페이스를 사용하는 이유는 Java의 람다식이 함수형 인터페이스의 구현체이기 때문이다. 람다식으로 생성된 순수 함수는 함수형 인터페이스로만 선언이 가능하다.
*1급 객체: 변수에 담을 수 있거나 파라미터로 전달할 수 있거나 반환값으로 전달할 수 있는 객체

*참고자료 - Java의 정석
1) Supplier
Supplier는 매개변수 없이 반환값 만을 갖는 함수형 인터페이스이다.
Supplier는 T get()을 추상 메서드로 갖고 있다.
2) Consumer
Consumer는 객체 T를 매개변수로 받아서 사용하며, 반환값은 없는 함수형 인터페이스이다.
Consumer는 void accept(T t)를 추상 메서드로 갖는다.
3) Function <T, R>
Function은 객체 T를 매개변수로 받아서 처리한 후 R로 반환하는 함수형 인터페이스다.
Function은 R apply(T t)를 추상 메서드로 갖는다.
4) Predicate
Predicate는 객체 T를 매개 변수로 받아 처리한 후 Boolean을 반환한다.
Supplier는 Boolean test(T t)을 추상 메서드로 갖고 있다.
5) compose
조합 함수로서 compose에 파라미터로 넘긴 값에 대한 연산을 먼저하고 그 결과값으로 앞에 연산을 수행한다.
Function<Integer, Integer> plus10 = (number) -> number + 10;
Function<Integer, Integer> multiply2 = (number) -> number * 2;
Function<Integer, Integer> multiply2AndPlus10 = plus10.compose(multiply2);
System.out.println(multiply2AndPlus10.apply(2)); // 14
6) andThen
앞에 있는 연산 수행 후 그 결과값을 가지고 andThen 메서드의 파라미터값에 대한 연산을 수행한다.
Function<Integer, Integer> plus10 = (number) -> number + 10;
Function<Integer, Integer> multiply2 = (number) -> number * 2;
System.out.println(plus10.andThen(multiply2).apply(2)); // 24
7) UnaryOperator
매개변수의 타입과 리턴 타입이 동일한 경우 매개변수를 하나만 사용할 수 있다. Function<T, T>를 상속 받았기 때문에 Function 인터페이스에 제공하는 디폴트 메서드도 사용 가능하다.
Function<Integer, Integer> plus10 = (number) -> number + 10;
UnaryOperator<Integer> plus10 = (number) -> number + 10;
8) BiFunction<U, T, R>
매개변수는 2개이고, 리턴 타입이 하나인 인터페이스이다.
9) BinaryOperator< T>
BiFunction 인터페이스에서 3개의 타입이 모두 같을 때 사용하는 인터페이스이다.
1) 람다식이 하나의 메서드만 호출하는 경우와
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
--> Function<String, Integer> f = Integer::parseInt; // 메서드 참조
2) 이미 생성된 객체의 메서드를 람다식에서 사용한 경우에는 클래스 이름 대신 그 객체의 참조변수를 적어줘야 한다.
MyClass obj = new MyClass();
Function<String, Boolean> f = (x) -> obj.equals(x);
-> Function<String, Boolean> f2 = obj::equals; // 메서드 참조
*하나의 메서드만 호출하는 람다식은 "클래스이름::메서드이름" 또는 "참조변수::메서드이름" 으로 바꿀 수 있다.
<메서드 참조의 3가지 경우>

Greeting greeting = new Greeting();
// static 메서드 참조
UnaryOperator<String> hi = Greeting::hi;
System.out.println(hi.apply("hong"));
// 특정 객체의 인스턴스 메서드 참조
UnaryOperator<String> hi2 = greeting::hello;
System.out.println(hi2.apply("hong"));
// 디폴트 생성자 참조
Supplier<Greeting> newGreeting = Greeting::new;
newGreeting.get();
// 매개변수가 있는 생성자 참조
Function<String, Greeting> hongGreeting = Greeting::new;
Greeting hong = hongGreeting.apply("hong");
System.out.println(hong.getName());
// 임의 객체의 인스턴스 메서드 참조
String[] names = {"hong", "whiteship", "toby"};
Arrays.sort(names, String::compareToIgnoreCase); // 임의의 인스턴스를 거쳐가며 compareToIgnoreCase 인스턴스 메서드를 사용
System.out.println(Arrays.toString(names)); // [hong, toby, whiteship]
*참고자료
Java의 정석
https://mangkyu.tistory.com/112