@FunctionalInterface
- 단 하나의 추상 메서드(Single Abstract Method) 만을 가지는 인터페이스
- static / default method 선언 가능 (Java 8 부터)
- java.lang.Runnable, java.util.Comparator 등
Function<T, R> | R apply(T t); | |
BiFunction<T, U, R> | R apply(T t, U u); | |
Supplier<T> | T get(); | |
Consumer<T> | void accept(T t); | T타입의 인자를 받아 로직 수행 (리턴 x) |
BiConsumer<T, U> | void accept(T t, U t); | T, U 타입 인자를 받아 로직 수행 (리턴x) |
Predicate<T> | boolean test(T t); | |
Comparator<T> | int compare(T o1, T o2); | o1 < o2 (음수, 오름차순) o1 == o2 (동일한 경우) o1 > o2 (양수, 내림차순) |
Lambda Expression
- 함수형 인터페이스를 가장 쉽게 구현할 수 있도록 해주는 방법
- 파라미터가 함수형 인터페이스의 경우 함수 인자(람다 표현식) 전달 가능
자바에서 제공해주는 @FunctionalInterface
interface Function<T, R>
T : 매개변수 타입, R : 반환 타임
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> { // 제네릭 인터페이스
R apply(T var1);
//..
}
(1) Function<T, R> 인터페이스 구현 하여 사용하는 경우
public class Adder implements Function<Integer, Integer> {
@Override
public Integer apply(Integer t) {
return 10 + t; // 단순히 인자값에 10 더함
}
}
public class Main {
public static void main(String[] args) {
Function<Integer, Integer> myAdder = new Adder(); // 인스턴스 생성
System.out.println(myAdder.apply(5)); // 15
}
}
simple 하게 Function 인터페이스 구현하여 사용한 형태이지만, apply() method 하나만 구현하여 사용하는데다 생성자 선언까지 하는게 번거롭다. 이때 람다 표현식을 사용하게 되면 간소화 시킬 수 있다. ( Java 8 이상 )
(2) 람다 표현식 사용하는 경우
public static void main(String[] args) {
Function<Integer, Integer> myAdder = (Integer a) -> {
return a + 10;
};
System.out.println(myAdder.apply(5)); // 15
}
익명 함수(Annonymous function)을 사용하게 될 경우 구현 클래스를 인스턴스화 하여 사용하지 않아도 간단히 동일한 기능이 동작하가능하다.
추가로 조건 하에 아래와 같이 람다 표현식을 간소화 시키는 것도 가능하다.
/*
1. 매개변수 타입 유추 가능할 경우 타입 생략 가능
2. 매개변수가 하나일 경우 괄호 생략 가능
3. 바로 리턴하는 경우 중괄호, return 생략 가능
*/
Function<Integer, Integer> myAdder = x -> x + 10;
람다 표현식 사용시 Java Compiler는 아래의 정보를 통해 target type을 결정하게 된다
- Variable declarations
- Assignments
- Return statements
- Array initializers
- Method or constructor arguments
- Lambda expression bodies
- Conditional expressions, ?:
- Cast expressions
interface BiFunction<T, U, R>
R apply(T var1, U var2) : T, U 타입의 매개변수를 받아 R 타입을 리턴하는 인터페이스
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T var1, U var2);
// ..
}
마찬가지로 람다 표현식으로 간단하게 덧셈 연산 함수를 만들 수 있다
import java.util.function.BiFunction;
public class Main {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> simpleAdder = (a, b) -> a + b;
System.out.println(simpleAdder.apply(2, 8)); // 10
}
}
참고. 매개변수 타입 3개인 @FunctionalInterface 인터페이스 만들기
(1) 매개변수를 3개 받는 FunctionInterface 선언
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
(2) TriFunction 인터페이스 람다 구현
public class Main {
public static void main(String[] args) {
TriFunction<Integer, Integer, Integer, Integer> triAdder = (a, b, c) -> a + b + c;
System.out.println(triAdder.apply(1,2, 3)); // 6
}
}
Supplier<T>
T get() : T 타입의 리턴값을 반환하는 추상 메서드
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
예시1
Supplier<String> myStringSupplier = () -> "hell word";
System.out.println(myStringSupplier.get()); // hell word
예시2.
public class Ch4 {
public static void main(String[] args) {
// 함수가 1등 시민이 되었으므로, 매개변수로 인자로 전달 가능
Supplier<Double> myRandomSupplier = () -> Math.random();
printRandomDouble(myRandomSupplier, 5);
}
private static void printRandomDouble(Supplier<Double> supplier, int count) {
for(int i = 1; i <= count; i++) {
System.out.println(supplier.get());
}
}
}
[출력 결과]
0.1601446203176864
0.6172793918604935
0.8425539834564604
0.26516593030460756
0.8304236464480694
Consumer<T>
void accept(T var1) : T 타입의 인자를 받아 내부 로직 처리 수행만 함 (리턴 x)
@FunctionalInterface
public interface Consumer<T> {
void accept(T var1);
// ..
}
예시1.
단순 String 인자 전달 받아 출력
//1. 익명 함수 선언 사용
Consumer<String> myStringConsumer = (String str) -> {
System.out.println(str);
};
myStringConsumer.accept("hello world!"); // hello world
//2. Method Reference(함수 참조) 사용
Consumer<String> myStringConsumer = System.out::println;
myStringConsumer.accept("hello world!");
예시2.
static method 인자로 여러 Consumer 함수를 전달하여 활용
public class Main {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
Consumer<Integer> myIntegerProcess = x -> System.out.printf("Process Integer 1 : %s%n", x);
process(integers, myIntegerProcess);
List<Integer> integers2 = Arrays.asList(4, 5, 6);
Consumer<Integer> myIntegerProcess2 = x -> System.out.printf("Process Integer 2 : %s%n", x);
process(integers2, myIntegerProcess2);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
Consumer<Double> myDoubleProcess = x -> System.out.printf("Process Double : %f%n", x);
process(doubles, myDoubleProcess);
}
// 제네릭 매개변수 타입 선언
public static <T> void process(List<T> inputs, Consumer<T> consumer) {
for(T input : inputs) {
consumer.accept(input);
}
}
}
[출력 결과]
Process Integer 1 : 1
Process Integer 1 : 2
Process Integer 1 : 3
Process Integer 2 : 4
Process Integer 2 : 5
Process Integer 2 : 6
Process Double : 1.100000
Process Double : 2.200000
Process Double : 3.300000
참고. Arrays.asList() 와 new ArrayList()
- Arrays.asList()은 가변 인자로 전달받은 값을 ArrayList (Arrays.class 위치한 static class ArrayList 호출) 로 반환
- 이때 값이 들어가는 E[]이 final이기 때문에 immutable(불변)
반대로 new ArrayList() 의 경우 mutable 하다
BiConsumer<T, U>
Consumer에서 매개변수가 2개로 늘어난 형태
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T var1, U var2);
// ..
}
예시
public class Main {
public static void main(String[] args) {
// Integer와 Double 타입 인자 받아 처리
BiConsumer<Integer, Double> myBiConsumer =
(index, value) -> System.out.printf("Processing index %s, value %f%n", index, value);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4);
process(doubles, myBiConsumer);
}
public static <T> void process(List<T> input, BiConsumer<Integer, T> biConsumer) {
for(int i = 0; i < input.size(); i++) {
biConsumer.accept(i, input.get(i));
}
}
}
Predicate<T>
- boolean test(T t) : T 타입 인자를 받아 조건에 따라 true/false(boolean) 반환
- default, static method 의 경우에도 논리 연산 형태를 보임
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T var1);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> {
return this.test(t) && other.test(t);
};
}
default Predicate<T> negate() {
return (t) -> {
return !this.test(t);
};
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> {
return this.test(t) || other.test(t);
};
}
static <T> Predicate<T> isEqual(Object targetRef) {
return null == targetRef ? Objects::isNull : (object) -> {
return targetRef.equals(object);
};
}
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return target.negate();
}
}
예시
마찬가지로 static method 선언하여 인자로 Predicate(함수형 인터페이스) 전달함으로써 다른 동작을 수행할 수 있다
public class Main {
public static void main(String[] args) {
Predicate<Integer> isPositive = x -> x > 0;
List<Integer> inputs = Arrays.asList(10, -5, 4, -2, 0, 3);
System.out.println(filter(inputs, isPositive)); // [10, 4, 3]
System.out.println(filter(inputs, isPositive.negate())); // [-5, -2, 0]
System.out.println(filter(inputs, isPositive.or(x -> x == 0))); // [10, 4, 0, 3]
System.out.println(filter(inputs, isPositive.and(x -> x % 2 == 0))); // [10, 4]
}
public static <T> List<T> filter(List<T> inputs, Predicate<T> condition) {
List<T> result = new ArrayList<>();
for(T input : inputs) {
if(condition.test(input)) result.add(input);
}
return result;
}
}
Comparator<T>
- int compare(T var1, T var2) : T 타입의 두 인자를 전달받아 비교하여 int 결과값 리턴
- Comparator가 Comparable 보다 우선 순위가 높음
- Comparable 외에 새로운 정렬 기준이 필요할 때 Comparator를 덮어씌워 사용
@FunctionalInterface
public interface Comparator<T> {
int compare(T var1, T var2);
// ..
}
참고. Comparable 과 Comparator
https://dev-ljw1126.tistory.com/62
예시
public class Ch4_Comparator {
static class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student(2, "홍길동"));
students.add(new Student(1, "이순신"));
students.add(new Student(3, "김철수"));
students.add(new Student(4, "최영희"));
// id 기준 내림차순 정렬
Comparator<Student> sortByIdDesc = (s1, s2) -> s2.id - s1.id;
Collections.sort(students, sortByIdDesc);
System.out.println(students);
// name 기준 내림차순 정렬
Comparator<Student> sortByNameDesc = (s1, s2) -> s2.name.compareTo(s1.name);
Collections.sort(students, sortByNameDesc);
System.out.println(students);
}
}
[출력 결과]
// id 내림차순
[Student{id=4, name='최영희'},
Student{id=3, name='김철수'},
Student{id=2, name='홍길동'},
Student{id=1, name='이순신'}]
// name 내림차순
[Student{id=2, name='홍길동'},
Student{id=4, name='최영희'},
Student{id=1, name='이순신'},
Student{id=3, name='김철수'}]
참고
https://fastcampus.co.kr/dev_red_lsh
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
https://tecoble.techcourse.co.kr/post/2020-07-17-Functional-Interface/
'공부 > Java' 카테고리의 다른 글
[Java] Arrays 클래스 메서드 (자바의 정석) (0) | 2023.07.31 |
---|---|
[Java] String 클래스 메서드 (자바의 정석) (0) | 2023.07.31 |
[Java] Enum (이펙티브 자바 3판) (0) | 2023.07.26 |
[Java] Enum values 조회 (Baeldung) (0) | 2023.07.26 |
[Java] Enum values 배열을 리스트 변환 (Baeldung, Enum values to List) (0) | 2023.07.21 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!