2023. 12. 9. 18:04ㆍjava/java
자바에서 제공되는 표준 API에서 한 개의 추상 메소드를 가지는 인터페이스들은 모둔 람다식을 이용해서 익명 구현 객체로 표현이 가능하다.
public static void main(String[] args) {
Runnable runnable = () -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
};
Thread thread = new Thread(runnable);
thread.start();
}
Thread 생성자를 호출할 때 다음과 같이 람다식을 매개값으로 대입해도 된다.
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
자바 8부터 빈번하게 사용되는 함수적 인터페이스는 java.util.function 표준 API 패키지로 제공한다. 이 패키지에서 제공하는 함수적 인터페이스의 목적은 메소드 또는 생성자의 매개 타입으로 사용되어 람다식을 대임할 수 있도록 하기 위해서이다.
java.util.function 패키지의 함수적 인터페이스는 크게 Cunsumer, Supplier, Function, Operator, Predicate로 구분된다. 구분 기준은 추상 메소드의 매개값과 리턴값의 유무이다.
종류 | 추상 메소드 특징 |
Cunsumer | - 매개값은 있고, 리턴값은 없음 |
Supplier | - 매개값은 없고, 리턴값은 있음 |
Function | - 매개값도 있고, 리턴값도 있음 - 주로 매개값을 리턴값으로 매핑(타입 변환) |
Operator | - 매개값도 있고, 리턴값도 있음 - 주로 매개값을 연산하고 결과를 리턴 |
Predicate | - 매개값도 있고, 리턴값도 있음 - 주로 매개값을 조사해서 true/false를 리턴 |
4.1 Consumer 함수적 인터페이스
Consumer 함수적 인터페이스의 특징은 리턴값이 없는 accept() 메소드를 가지고 있다.
accept() 메소드는 단지 소비하는 역할만 한다.(매개값을 사용할 뿐 리턴하지 않는다.)
매개변수의 타입과 수에 따라 다양한 Consumer들이 있다.
인터페이스명 | 추상 메서드 | 설명 |
Consumer<T> | void accept(T t) | 객체 T를 받아 소비 |
BiConsumer<T, U> | void accept(T t, U u) | 객체 T와 u를 받아 소비 |
DoubleConsumer | void accept(double value) | value를 받아 소비 |
IntConsumer<T> | void accept(int value) | value를 받아 소비 |
LongConsumer<T> | void accept(long value) | value를 받아 소비 |
ObjDoubleConsumer<T> | void accept(T t, double value) | 객체 T와 value를 받아 소비 |
ObjIntConsumer<T> | void accept(T t, int value) | 객체 T와 value를 받아 소비 |
ObjLongConsumer<T> | void accept(T t, long value) | 객체 T와 value를 받아 소비 |
Consumer<T> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.
public static void main(String[] args) {
Consumer<String> consumer = t -> System.out.println(t);
consumer.accept("consumer!");
BiConsumer<String, String> bigConsumer = (t, u) -> System.out.println(t + u);
bigConsumer.accept("consumer", "!");
DoubleConsumer doubleConsumer = d -> System.out.println("consumer" + d);
doubleConsumer.accept(808.0);
ObjIntConsumer<String> objIntConsumer = (t, i) -> System.out.println(t + i);
objIntConsumer.accept("consumer", 8);
}
=>
consumer!
consumer!
consumer808.0
consumer8
4.2 Supplier 함수적 인터페이스
Supplier 함수적 인터페이스의 특징은 매개 변수가 없고 리턴값이 있는 getXXXX() 메소드를 가지고 있다.
이 메소드들은 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.
리턴 타입에 따라 아래와 같은 Supplier 함수적 인터페이스들이 있다.
인터페이스명 | 추상 메소드 | 설명 |
Supplier<T> | T get() | T 객체를 리턴 |
BooleanSupplier | boolean getAsBoolean() | boolean 값을 리턴 |
DoubleSupplier | double getAsDouble() | double 값을 리턴 |
IntSupplier | int getAsInt() | int 값을 리턴 |
LongSupplier | long getAsLong() | long 값을 리턴 |
Supplier<T> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.
public class SupplierExample {
public static void main(String[] args) {
IntSupplier intSupplier = () -> {
Random random = new Random();
return random.nextInt(6);
};
int num = intSupplier.getAsInt();
System.out.println(num);
}
}
4.3 Function 함수적 인터페이스
Function 함수적 인터페이스의 특징은 매개값과 리턴값이 있는 applyXXXX() 메소드를 가지고 있다.
=> 주로 매개값을 리턴값으로 타입 변환한다.
인터페이스명 | 추상 메소드 | 설명 |
Function<T, R> | R apply (T t) | 객체 T를 R로 매핑 |
BiFunction<T, U, R> | R apply (T t, U u) | 객체 T와 U를 R로 매핑 |
DoubleFunction<R> | R apply (double value) | double을 객체 R로 매핑 |
IntFunction<R> | R apply (int value) | int를 객체 R로 매핑 |
IntToDoubleFunction | double applyAsDouble(int value) | int를 double로 매핑 |
IntToLongFunction | long applyAsLong(int value) | int를 long으로 매핑 |
LongToDoubleFunction | double applyAsDouble(long value) | long을 double로 매핑 |
LongToIntFunction | int applyAsInt(long value) | long을 int로 매핑 |
ToDoubleBiFunction<T, U> | double applyAsDouble<T t, U u> | 객체 T와 U를 double로 매핑 |
Function<T, R> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.
public class FunctionExample {
private static List<Student> list = Arrays.asList(
new Student("홍길동", 90, 96),
new Student("김길동", 40, 50)
);
public static void printString(Function<Student, String> function) {
for (Student student : list) {
System.out.println(function.apply(student) + " ");
}
System.out.println();
}
public static void printInt(ToIntFunction<Student> function) {
for (Student student : list) {
System.out.println(function.applyAsInt(student) + " ");
}
System.out.println();
}
public static void printAvg(ToIntFunction<Student> function) {
int studentNum = list.size();
int sum = 0;
for (Student student : list) {
sum += function.applyAsInt(student);
}
System.out.println((double) sum / (double) studentNum);
}
public static void main(String[] args) {
System.out.println("[학생 이름]");
printString(student -> student.getName());
System.out.println("[수학 점수]");
printInt(student -> student.getMathScore());
System.out.println("[수학 평균]");
printAvg(student -> student.getMathScore());
System.out.println("[영어 평균]");
printAvg(student -> student.getEnglishScore());
}
}
=>
[학생 이름]
홍길동
김길동
[수학 점수]
96
50
[수학 평균]
73.0
[영어 평균]
65.0
4.4 Operator 함수적 인터페이스
Operator 함수적 인터페이스는 Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다.
=> 매객값을 이용해 연산을 수행한 후 동일한 타입으로 리터값을 제공하는 역할을 한다.
인터페이스명 | 추상 메소드 | 설명 |
BinaryOperator | T apply(Tt, Tt) | T와 T를 연산한 후 T 리턴 |
UnaryOperator | T apply(Tt) | T를 연산한 후 T 리턴 |
DoubleBinaryOperator | double applyAsDouble(double, double) | 두 개의 double 연산 |
DoubleUnaryOperaotr | double applyAsdouble(double) | 한 개의 double 연산 |
IntBinaryOperator | int applyAsInt(int, int) | 두 개의 int 연산 |
IntUnaryOperator | int applyAsInt(int) | 한 개의 int 연산 |
LongBinaryOperator | long applyAsLong(long, long) | 두 개의 long 연산 |
LongUnaryOperator | long applyAsLong(long) | 한 개의 long 연산 |
Operator 함수적 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.
public class OperatorExample {
private static final int[] scores = {92, 94, 95, 96};
public static int maxOrMin(IntBinaryOperator operator) {
int result = scores[0];
for (int score : scores) {
result = operator.applyAsInt(result, score);
}
return result;
}
public static void main(String[] args) {
//최댓값 얻기
int max = maxOrMin(
(a, b) -> {
if (a <= b) return b;
else return a;
}
);
System.out.println("최댓값 : " + max);
//최솟값 얻기
int min = maxOrMin(
(a, b) -> {
if (a >= b) return b;
else return a;
}
);
System.out.println("최솟값 : " + min);
}
}
4.5 Predicate 함수적 인터페이스
Predicate 함수적 인터페이스는 매개 변수와 boolean 리턴값이 있는 testXXX() 메소드를 가지고 있다.
인터페이스명 | 추상 메소드 | 설명 |
Predicate<T> | boolean test(T t) | 객체 T를 조사 |
BiPredicate | boolean test(T t, U u) | 객체 T와 U를 비교 조사 |
DoublePredicate | boolean test(double value) | double 값을 조사 |
intPredicate | boolean test(int value) | int 값을 조사 |
LongPredicate | boolean test(long value) | long 값을 조사 |
Predicate<T> 인터페이스를 타겟 타입으로 하는 람다식은 다음과 같이 작성할 수 있다.
public class PredicateExample {
private static List<Student> list = List.of(
new Student("홍길동", "남자", 90),
new Student("김길동", "남자", 40),
new Student("이길동", "여자", 91),
new Student("최길동", "여자", 39)
);
public static double avg(Predicate<Student> predicate) {
int count = 0, sum = 0;
for (Student student : list) {
if (predicate.test(student)) {
sum += student.getScore();
count ++;
}
}
if (count == 0)
return 0;
else
return (double) sum / (double) count;
}
public static void main(String[] args) {
double maleAvg = avg(
student -> student.getSex().equals("남자")
);
System.out.println("남자 평균 점수 : " + maleAvg);
double femaleAvg = avg(
student -> student.getSex().equals("여자")
);
System.out.println("여자 평균 점수 : " + femaleAvg);
}
}
=>
남자 평균 점수 : 65.0
여자 평균 점수 : 65.0
4.6 andThen()과 compose() 디폴트 메소드
디폴트 및 정적 메소드는 추상 메소드가 아니기 때문에 함수적 인터페이스에 선언되어도 여전히 함수적 인터페이스의 성질을 잃지 않는다.
Consumer, Function, Operator 종류의 함수적 인터페이스는 andThen() 과 compose() 디폴트 메소드를 가지고 있다.
두 디폴트 메소드는 두 개의 함수적 인터페이스를 순차적으로 연결하고, 첫 번째 처리 결과를 두 번째 매개값으로 제공해서 최종 결과값을 얻을 때 사용한다.
andThen()과 compose()의 차이점은 어떤 함수적 인터페이스를 먼저 처리하느냐이다.
1. andThen()
인터페이스AB = 인터페이스A.andThen(인터페이스B)
최종결과 = 인터페이스AB.method();
인터페이스 A 처리 후 결과값을 인터페이스 B에 매개값으로 전달
2. compose()
인터페이스AB = 인터페이스A.compose(인터페이스B)
최종결과 = 인터페이스AB.method();
인터페이스 B 처리 후 결과값을 인터페이스 A에 매개값으로 전달
public class FunctionAndThenComposeExample {
public static void main(String[] args) {
Function<Member, Address> functionA;
Function<Address, String> functionB;
Function<Member, String> functionAB;
String city;
functionA = (member -> member.getAddress());
functionB = (address -> address.getCity());
functionAB = functionA.andThen(functionB);
city = functionAB.apply(new Member("홍길동", "hong", new Address("한국", "서울")));
System.out.println(city);
functionAB = functionB.compose(functionA);
city = functionAB.apply(new Member("홍길동", "hong", new Address("한국", "서울")));
System.out.println(city);
}
}
=>
서울
서울
4.7 minBy(), maxBy() 정적 메소드
BinaryOperator<T> 함수적 인터페이스는 minBy(), maxBy() 정적 메소드를 제공한다. 이 두 메소드는 매개값으로 제공되는 Comparator 를 이용해서 최대 T와 최소T를 얻는 BinaryOperator<T>를 리턴한다.
public static void main(String[] args) {
BinaryOperator<Student> binaryOperator;
Student student;
binaryOperator = BinaryOperator.minBy((f1, f2) -> {
return Integer.compare(f1.getMathScore(), f2.getMathScore());
});
student = binaryOperator.apply(new Student("길동", 30, 40), new Student("길은", 40, 50));
System.out.println(student.getName());
}
'java > java' 카테고리의 다른 글
람다식(2) - 클래스 멤버와 로컬 변수 사용 (0) | 2023.12.08 |
---|---|
람다식 (1) - 기본 문법, 함수적 인터페이스 (1) | 2023.12.07 |
Generic (1) | 2023.12.04 |
중첩 클래스와 중첩 인터페이스란? (0) | 2023.11.29 |
어노테이션 (2) | 2023.11.26 |