'이것이 자바다 - 신용권' 14장 학습
1절. 람다식이란
2절. 람다식 기본 문법
3절. 타겟 타입과 함수적 인터페이스
4절. 외부 로컬 변수 접근
5절. 기본적으로 제공되는 함수적 인터페이스
6절. 메소드 참조
- y = f(x) 형태의 함수로 구성된 프로그래밍 기법
- 데이터를 매개값으로 전달하고 결과를 받는 코드들로 구성된다.
- 객체 지향 프로그래밍 보다 효율적인 경우
- 대용량 데이터의 처리시에 유리
- 데이터 포장 객체를 생성 후 처리하는 것 보다, 데이터를 바로 처리하는 것이 속도에 유리
- 멀티 코어 CPU에서 데이터를 병렬 처리하고 취합할 때 객체보다는 함수가 유리
- 이벤트 지향 프로그래밍(이벤트가 발생하면 핸들러 함수 실행)에 적합
- 반복적인 이벤트 처리는 핸들러 객체보다는 핸들러 함수가 적합
- 대용량 데이터의 처리시에 유리
- 현대적 프로그래밍 기법
- 객체 지향 프로그래밍 + 함수적 프로그래밍
-
람다식(Lambda Expressions)을 언어 차원에서 제공
- 람다 계산법에서 사용된 식을 프로그래밍 언어에 접목
- 익명 함수(anonymous function)을 생성하기 위한 식
(타입 매개변수, ...) -> { 실행문; ...}
-
자바에서 람다식을 수용한 이유
- 코드가 매우 간결해진다.
- 컬렉션 요소(대용량 데이터)를 필터링 또는 매핑해서 쉽게 집계할 수 있다.
-
자바는 람다식을 함수적 인터페이스의 익명 구현 객체로 취급한다.
람다식 -> 매개변수를 가진 코드 블록 -> 익명 구현 객체
- 어떤 인터페이스를 구현할지는 대입되는 인터페이스에 달려있다.
Runnable runnable = () -> { ... }; // 람다식
Runnable runnable = new Runnable() { public void run() { ... } };
- 함수적 스타일의 람다식을 작성하는 방법
- (타입 매개변수, …) -> { 실행문; ...}
- ex) (int a) -> { System.out.println(a); }
- (타입 매개변수, …) -> { 실행문; ...}
- 람다식의 6가지 약식 표현
- 매개 타입은 런타임시에 대입값에 따라 자동으로 인식하기 때문에 생략 가능하다.
- 람다식은 함수적 인터페이스의 메소드를 구현한 것이기 때문에, 아래에서 매개변수만 지정을 해도 함수적 인터페이스의 메소드의 타입을 보고 a의 타입을 결정 할 수 있다. 따라서 a의 타입을 생략 할 수 있다.
(a) -> { System.out.println(a); }
- 하나의 매개변수만 있을 경우에는 괄호 () 생략 가능
a -> { System.out.println(a); }
- 하나의 실행문만 있다면 중괄호 {} 생략 가능
a -> System.out.println(a)
- 매개변수가 없다면 괄호 ()를 생략할 수 없음
() -> { 실행문; ... }
- 리턴값이 있는 경우, return 문을 사용
(x,y) -> { return x + y; };
- 중괄호 {}에 return 문만 있을 경우, 중괄호를 생략 가능
(x,y) -> x + y
- 매개 타입은 런타임시에 대입값에 따라 자동으로 인식하기 때문에 생략 가능하다.
-
타겟 타입(target type)
- 람다식이 대입되는 인터페이스를 말한다.
- 인터페이스 변수 = 람다식;
- 익명 구현 객체를 만들 때 사용할 인터페이스이다.
- 람다식이 대입되는 인터페이스를 말한다.
-
함수적 인터페이스(functional interface)
- 모든 인터페이스는 람다식의 타겟 타입이 될 수 없다.
- 람다식은 하나의 메소드를 정의하기 떄문에
- 하나의 추상 메소드만 선언된 인터페이스만 타겟 타입이 될 수 있다.
- 함수적 인터페이스
- 하나의 추상 메소드만 선언된 인터페이스를 말한다.
- @FunctionalInterface 어노테이션
- 하나의 추상 메소드만을 가지는지 컴파일러가 체크하도록 함
- 두 개 이상의 추상 메소드가 선언되어 있으면 컴파일 오류 발생
- 모든 인터페이스는 람다식의 타겟 타입이 될 수 없다.
-
매개변수와 리턴값이 없는 람다식
@FunctionalInterface public interface MyFunctionalInterface { public void method(); }
...
- 람다식을 인터페이스 변수에 대입하게 되면 익명 구현 객체가 만들어지게 되고,
- 실행은 인터페이스 변수를 통해 메소드를 호출해 주면 된다.
Myfunctionalnterface fi = () -> { ... } fi.method();
-
매개변수가 있는 람다식
@FunctionalInterface public interface MyFunctionalInterface { public void method(int x); }
...
Myfunctionalnterface fi = (x) -> { ... } 또는 Myfunctionalnterface fi = x -> { ... } fi.method(5);
-
리턴값이 있는 람다식
@FunctionalInterface public interface MyFunctionalInterface { public int method(int x); }
...
Myfunctionalnterface fi = (x, y) -> { ...; return 값; } int result = fi.method(5, 2);
-
약식 표현
- 1
Myfunctionalnterface fi = (x, y) -> { return x + y; }
=
Myfunctionalnterface fi = (x, y) -> x + y;
- 2
Myfunctionalnterface fi = (x, y) -> { return sum(x, y); }
=
Myfunctionalnterface fi = (x, y) -> sum(x, y);
-
-
클래스의 멤버 사용
- 람다식 실행 블록에는 클래스의 멤버인 필드와 메소드를 제약 없이 사용할 수 있다.
- 람다식 실행 블록내에서 this는 람다식을 실행한 객체의 참조이다.
public class ThisExample { public int outerField = 10; class Inner { int innerField = 20; void method() { MyfunctionalInterface fi = () -> { System.out.println("outerField: " + outerField); System.out.println("outerField: " + ThisExample.this.outerField); System.out.println("innerField: " + innerField); System.out.println("innerField: " + this.innerField); } fi.method(); } } }
-
로컬 변수의 사용
- 람다식은 함수적 인터페이스의 익명 구현 객체를 생성한다.
- 람다식에서 사용하는 외부 로컬 변수는 final 특성을 갖는다.
- 원래 자바 익명 구현 객체에서 로컬 변수를 사용하게 되면, 그 로컬 변수는 final 특성을 가지게 된다. 람다도 마찬가지이다. 람다식이 익명 구현 객체를 생성하기 때문이다.
- 자바 8 이후부터는 로컬 변수에 final 키워드를 붙이지 않아도 자동으로 처리해준다.
public class UsingLocalVariable { void method(int arg) { //arg는 final 특성을 가짐 int localVar = 40 //localVar는 final 특성을 가짐 ------------------ //arg = 31; //final 특성 때문에 수정 불가 //localVar = 41; //final 특성 때문에 수정 불가 ------------------ MyFunctionalInterface fi = () -> { //로컬변수 사용 System.out.println("arg: " + arg); System.out.println("localVar: " + localVar); } fi.method(); } }
-
한 개의 추상 메소드를 가지는 인터페이스들은 모두 람다식 사용 가능
- ex: Runnable 인터페이스를 구현해서 new Thread(Runnable 구현 객체) 형태로 쓰지 않고
- new Thread(() -> { …;}) 이런 식으로 람다식으로 작성해서 사용하는 경우가 대부분이다.
Thread thread = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.print(i + " "); } System.out.println(); }); thread.start();
-
자바 8부터 표준 API로 제공되는 함수적 인터페이스
- java.util.function 패키지에 포함되어 있다.
- 매개타입으로 사용되어 람다식을 매개값으로 대입할 수 있도록 해준다.
- 종류
- Consumer 함수적 인터페이스 류(소비)
- 매개값만 있고 리턴값이 없는 추상 메소드를 가지고 있다.
- Supplier 함수적 인터페이스 류(공급)
- 매개값은 없고 리턴값만 있는 추상 메소드를 가지고 있다.
- Function 함수적 인터페이스 류
- 매개값과 리턴값이 모두 있는 추상 메소드를 가지고 있다.
- 주로 매개값을 리턴값으로 매핑(타입변환)할 경우에 사용
- 예를 들면, String 10을 Integer 10으로 변환할 경우
- Operator 함수적 인터페이스 류
- 매개값과 리턴값이 모두 있는 추상메소드를 가지고 있다.
- 주로 매개값을 연산하고 그 결과를 리턴할 경우에 사용
- Predicate 함수적 인터페이스 류
- 매개값을 조사해서 true 또는 false를 리턴할 때 사용
- Consumer 함수적 인터페이스 류(소비)
- Consumer 함수적 인터페이스의 특징은 리턴값이 없는 accept() 메소드를 가지고 있다. accept() 메소드는 단지 매개값을 소비하는 역할만 한다. 여기서 소비한다는 말은 사용만 할 뿐 리턴값이 없다는 뜻이다.
인터페이스명 | 추상 메소드 | 설명 |
---|---|---|
Consumer<T> | void accept(T t) | 객체 T를 받아 소비 |
BiConsumer<T,U> | void accept(T t, U u) | 객체 T와 U를 받아 소비 |
DoubleConsumer | void accept(double value) | double 값을 받아 소비 |
IntConsumer | void accept(int value) | int 값을 받아 소비 |
LongConsumer | void accept(long value) | long 값을 받아 소비 |
ObjDoubleConsumer<T> | void accept(T t, double value) | 객체 T와 double 값을 받아 소비 |
ObjIntConsumer<T> | void accept(T t, int value) | 객체 T와 int 값을 받아 소비 |
ObjLongConsumer<T> | void accept(T t, long value) | 객체 T와 long 값을 받아 소비 |
- Consumer<T>
Consumer<String> consumer = t -> {t를 소비하는 실행문;};
- <String>이므로 매개값 t는 String 타입
- BiConsumer<T, U>
Consumer<String, String> consumer = (t, u) -> {t를 소비하는 실행문;};
- <String, String>이므로 매개값 t, u는 String 타입
- DoubleConsumer
DoubleConsumer consumer = d -> {d를 소비하는 실행문;};
- 매개값 d는 double 타입(고정)
- ObjIntConsumer<T>
ObjIntConsumer<String> consumer = (t, i) -> { t와 i를 소비하는 실행문; }
- <String> 이므로 매개값 t는 String 타입이고, 매개값 i는 int 타입(고정)
- Consumer 함수적 인터페이스 예제 코드
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.ObjIntConsumer;
public class ConsumerEx {
public static void main(String[] args) {
Consumer<String> consumer = t -> System.out.println(t + "8");
consumer.accept("Java");
BiConsumer<String, String> biConsumer = (t, u) -> System.out.println(t + u);
biConsumer.accept("Java", "8");
DoubleConsumer doubleConsumer = d -> System.out.println("Java" + d);
doubleConsumer.accept(8.0);
ObjIntConsumer<String> objIntConsumer = (t, i) -> System.out.println(t + i);
objIntConsumer.accept("Java", 8);
}
}
Java8
Java8
Java8.0
Java8
- Supplier 함수적 인터페이스의 특징은 매개변수가 없고 리턴값이 있는 getXXX() 메소드를 가지고 있다. 이들 메소드는 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 한다.
- 리턴 타입에 따라서 아래와 같은 Supplier 함수적 인터페이스들이 있다.
인터페이스명 | 추상 메소드 | 설명 |
---|---|---|
Supplier<T> | T get() | 객체를 리턴 |
BooleanSupplier | boolean getAsBoolean() | boolean 값을 리턴 |
DoubleSupplier | double getAsDouble() | double 값을 리턴 |
IntSupplier | int getAsInt() | int 값을 리턴 |
LongSupplier | long getAsLong() | long 값을 리턴 |
- Supplier<T>
Supplier<String> supplier = () -> {...; return "문자열";}
- IntSupplier
IntSupplier supplier = () -> {...; return int값;}
- Supplier 함수적 인터페이스 예제 코드
import java.util.function.IntSupplier;
public class SupplierEx {
public static void main(String[] args) {
IntSupplier intSupplier = () -> {
int num = (int) (Math.random() * 6) +1;
return num;
};
int num = intSupplier.getAsInt();
System.out.println("랜덤 주사위 눈의 수 : " + num);
}
}
5
- Function 함수적 인터페이스의 특징은 매개변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다. 이들 메소드는 매개값을 리턴값으로 매핑(타입 변환) 하는 역할을 한다.
- Ex) String 10 -> Integer 10
인터페이스명 | 추상 메소드 | 설명 |
---|---|---|
Function<T, R> | R apply(T t) | 객체 T를 객체 R로 매핑 |
BiFunction<T, U, R> | R apply(T, U) | 객체 T와 U를 받아서 R로 매핑 |
DoubleFunction<R> | R apply(double value) | double을 객체 R로 매핑 |
IntToDoubleFunction | double applyAsDouble(int value) | int를 double로 매핑 |
ToDoubleBiFunction | double applyAsDouble(T t, U u) | 객체 T와 U를 double로 매핑 |
ToDoubleFunction | double applyAsDouble(T value) | 객체 T를 double로 매핑 |
… | ... | ... |
-
Function<T, R>
Function<Student, String> function = t -> return t.getName(); 또는 Function<Student, String> function = t -> t.getName();
- <Student, String> 이므로 매개값 t는 Student 타입이고 리턴값은 String 타입이다.
- Student 객체를 String으로 매핑한 예제이다.
-
ToIntFunction<T>
ToIntFunction<Student> function = t - > return t.getScore(); 또는 ToIntFunction<Student> function = t - > t.getScore();
- <Student> 이므로 매개값 t는 Student 타입이고 리턴값은 int 타입이다.(고정)
- Student 객체를 int으로 매핑한 예제이다.
-
Function 함수적 인터페이스 예제 코드
- Student.java
public class Student { private String name; private int englishScore; private int mathScore; public Student(String name, int englishScore, int mathScore) { this.name = name; this.englishScore = englishScore; this.mathScore = mathScore; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getEnglishScore() { return englishScore; } public void setEnglishScore(int englishScore) { this.englishScore = englishScore; } public int getMathScore() { return mathScore; } public void setMathScore(int mathScore) { this.mathScore = mathScore; } }
- FuntionEx1
import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.function.ToIntFunction; public class FunctionEx1 { private static List<Student> list = Arrays.asList( new Student("namjune1", 100, 100), new Student("namjune2", 99, 99) ); public static void printString(Function<Student, String> function) { list.forEach(student -> System.out.print(function.apply(student) + " ")); } public static void printInt(ToIntFunction<Student> function) { list.forEach(student -> System.out.print(function.applyAsInt(student) + " ")); } public static void main(String[] args) { System.out.println("[학생 이름]"); printString(t -> t.getName()); System.out.println(); System.out.println("[영어 점수]"); printInt(t -> t.getEnglishScore()); System.out.println(); System.out.println("[수학 점수]"); printInt(t -> t.getMathScore()); } }
[학생 이름] namjune1 namjune2 [영어 점수] 100 99 [수학 점수] 100 99
- FuntionEx2
import java.util.Arrays; import java.util.List; import java.util.function.ToIntFunction; public class FunctionEx2 { private static List<Student> list = Arrays.asList( new Student("namjune1", 100, 100), new Student("namjune2", 99, 99) ); public static double avg(ToIntFunction<Student> function) { int sum = 0; for (Student student : list) { sum += function.applyAsInt(student); } double avg = (double) sum / list.size(); return avg; } public static void main(String[] args) { double englishAvg = avg(t -> t.getEnglishScore()); System.out.println("[영어 평균 점수]"); System.out.println(englishAvg); System.out.println(); double mathAvg = avg(t -> t.getMathScore()); System.out.println("[수학 평균 점수]"); System.out.println(mathAvg); } }
[영어 평균 점수] 99.5 [수학 평균 점수] 99.5
- Operator 함수적 인터페이스의 특징은 Function과 동일하게 매개변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있다. 하지만 이들 메소드는 매개값을 리턴값으로 매핑(타입변환)하는 역할보다는 매개값을 이용해서 연산을 수행한 후 동일한 타입으로 리턴값을 제공하는 역할을 한다.
- ex) 10+5 -> 15
인터페이스명 | 추상메소드 | 설명 |
---|---|---|
BinaryOperator<T> | BiFunction<T,U,R>의 하위 인터페이스 | T와 U를 연산한 후 R 리턴 |
UnaryOperator<T> | Function<T,R>의 하위 인터페이스 | T를 연산한 후 R 리턴 |
DoubleBinaryOperator | double applyAsDouble(double, double) | 두 개의 double 연산 |
DoubleUnaryOperator | 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 연산 |
-
IntBinaryOperator
IntBinaryOperator operator = (a,b) -> { ...; return int 값;}
- 매개값 a, b는 모두 int 타입이고, 연산 후 리턴값도 int타입
-
IntUnaryOperator
IntUnaryOperator operator = a -> { ...; return int값; }
- 매개값 a는 int 타입이고, 연산 후 리턴값도 int 타입
-
Operator 함수적 인터페이스 예제 코드
import java.util.function.IntBinaryOperator; public class OperatorEx { private static int[] scores = {92, 34, 87}; private static int maxOrMin(IntBinaryOperator intBinaryOperator) { int result = scores[0]; for (int score : scores) { result = intBinaryOperator.applyAsInt(result, score); } return result; } public static void main(String[] args) { int max = maxOrMin( (result, score) -> { if (result >= score) return result; else return score; } ); System.out.println(max); int min = maxOrMin( (result, score) -> { if (result <= score) return result; else return score; } ); System.out.println(min); } }
92 34
- Predicate 함수적 인터페이스의 특징은 매개변수와 boolean 리턴값이 있는 testXXX() 메소드를 가지고 있다. 이들 메소드는 매개값을 조사해서 true 또는 false를 리턴하는 역할을 한다.
인터페이스명 | 추상 메소드 | 설명 |
---|---|---|
Predicate<T> | boolean test(T t) | 객체 T를 조사 |
BiPredicate<T, U> | 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>
Predicate<Student> predicate = t -> { return t.getSex().equals("남자")}; 또는 Preducate<Sturent> predicate = t -> { t.getSex().equals("남자")};
- <Student> 이므로 매개값 t는 Student 타입이고, 리턴값은 boolean 타입(고정)
-
Predicate 함수적 인터페이스 예제 코드
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateEx {
private static List<Student> list = Arrays.asList(
new Student("김남준", "남", 100),
new Student("김남순", "여", 100),
new Student("김남준1", "남", 90),
new Student("김남순2", "여", 80)
);
public static double avg(Predicate<Student> predicate) {
int count = 0;
int sum = 0;
for (Student student : list) {
if (predicate.test(student)) {
sum += student.getScore();
count++;
}
}
return sum / 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);
}
}
남자 점수 평균: 95.0
여자 점수 평균: 90.0
-
함수적 인터페이스가 가지고 있는 디폴트 메소드이다.
-
두 개의 함수적 인터페이스를 순차적으로 연결해서 실행한다.
-
첫번째 리턴값을 두번째 매개값으로 제공해서 최종 결과값을 리턴한다.
-
andThen()과 compose()의 차이점은 어떤 함수적 인터페이스부터 처리하느냐이다.
-
andThen() 디폴트 메소드
- 인터페이스 AB의 method를 실행하면
- 인터페이스 A의 람다식의 결과값을 받아서
- 인터페이스 B의 매개값으로 전달하고
- 최종결과를 리턴을 한다.
인터페이스 AB = 인터페이스 A.andThen(인터페이스 B);
최종결과 = 인터페이스 AB.method();
- compose() 디폴트 메소드
- 인터페이스 AB의 method를 실행하면
- 인터페이스 B의 람다식의 결과값을 받아서
- 인터페이스 A의 매개값으로 전달하고
- 최종결과를 리턴한다.
인터페이스 AB = 인터페이스 A.compose(인터페이스 B);
최종결과 = 인터페이스 AB.method();
- 함수적 인터페이스가 andThen()과 compose()를 제공하는지를 알아보고 사용하는 것이 좋다.
- andThen()메소드는 거의 제공을하지만 compose()의 경우 아래의 메소드들만 제공한다.
- compose() 디폴트 메소드를 제공하는 함수적 인터페이스
- Function<T, R>
- DoubleUnaryOperator
- IntUnaryOperator
- LongUnaryOperator
- compose() 디폴트 메소드를 제공하는 함수적 인터페이스
Consumer의 순차적 연결
- Consumer 종류의 함수적 인터페이스는 처리 결과를 리턴하지 않기 떄문에 andThen()과 compose() 디폴트 메소드는 함수적 인터페이스의 호출순서만 정한다.
- 사용 예시
Consumer<Member> consumerA = (m) -> {
System.out.println("consumerA: " + m.getName());
};
Consumer<Member> consumerB = (m) -> {
System.out.println("consumerB: " + m.getId());
};
Consumer<Member> consumerAB = consumerA.andThen(consumerB);
consumerAB.accept(new Member("홍길동", "hong", null));
실행 결과:
consumerA: 홍길동
consumerB: hong
-
예제코드
- Member.java
public class Member { private String name; private String id; private Address address; public Member(String name, String id, Address address) { this.name = name; this.id = id; this.address = address; } public String getName() { return name; } public String getId() { return id; } public Address getAddress() { return address; } }
- Address.java
public class Address { private String country; private String city; public Address(String country, String city) { this.country = country; this.city = city; } public String getCountry() { return country; } public String getCity() { return city; } }
- ConsumerAndThenEx.java
public class ConsumerAndThenEx { public static void main(String[] args) { Consumer<Member> consumerA = (member) -> { System.out.println(member.getName()); }; Consumer<Member> consumerB = (member) -> { System.out.println(member.getId()); }; Consumer<Member> consumerAB = consumerA.andThen(consumerB); consumerAB.accept( new Member("김남준", "njkim", new Address("korea", "seoul")) ); } }
김남준 njkim
Function의 순차적 연결
- Function과 Operator 종류의 함수적 인터페이스는 먼저 실행한 함수적 인터페이스의 결과를 다음 함수적 인터페이스의 매개값으로 넘겨주고, 최종 처리결과를 리턴한다.
- functionA의 두번째 매개변수 A는 functionB로 전달되는 매개값의 타입이고, functionB의 첫번째 매개변수로 정의되어 있다.
- Address는 functionA에서 functionB로 전달되는 타입이고, 최종적으로 functionAB에서는 Member객체를 이용하여 String 타입의 결과값을 얻는다.
- 사용 예시
Function<Member, Address> functionA;
Function<Address, String> functionB;
Function<Member, String> functionAB;
String city;
functionA = m -> m.getAddress();
functionB = a -> a.getCity;
functionAB = functionA.andThen(functionB);
city = functionAB.apply(
new Member("홍길동","hong",new Address("한국", "서울"))
);
functionAB = functionB.compose(functionA);
city = functionAB.apply(
new Member("홍길동","hong",new Address("한국", "서울"))
);
-
예제 코드
- FunctionAndThenComposeEx
public class FunctionAndThenComposeEx { public static void main(String[] args) { Function<Member, Address> functionA; Function<Address, String> functionB; Function<Member, String> functionAB; functionA = member -> member.getAddress(); functionB = address -> address.getCity(); functionAB = functionA.andThen(functionB); String city = functionAB.apply( new Member("김남준", "njkim", new Address("한국", "서울")) ); System.out.println("andThen 거주도시: " + city); functionAB = functionB.compose(functionA); city = functionAB.apply( new Member("김남준", "njkim", new Address("한국", "서울")) ); System.out.println("compose 거주도시: " + city); } }
andThen 거주도시: 서울 compose 거주도시: 서울
- and(), or(), negate() 디폴트 메소드
- Predicate 함수적 인터페이스의 디폴트 메소드
- and() : &&과 대응 - 두 Predicate가 모두 true를 리턴하면 최종적으로 true를 리턴
predicateAB = predicateA.and(predicateB);
- or() : ||과 대응 - 두 Predicate중 하나만 true를 리턴하면 최종적으로 true를 리턴
predicateAB = predicate.or(predicateB);
- negate() : !과 대응 - Predicate의 결과가 true리면 false, false이면 true를 리턴
predicateAB = predicateA.negate();
- and(), or(), negate() 예제 코드
import java.util.function.IntPredicate;
public class PredicateAndOrNegateEx {
public static void main(String[] args) {
IntPredicate predicateA = a -> a % 2 == 0;
IntPredicate predicateB = b -> b % 3 == 0;
IntPredicate predicateAB = predicateA.and(predicateB);
int input = 6;
boolean result = predicateAB.test(input);
System.out.println(input + "은/는 2의 배수이면서 3의 배수인가? " + result);
input = 4;
predicateAB = predicateA.or(predicateB);
result = predicateAB.test(input);
System.out.println(input + "은/는 2의 배수 또는 3의 배수 인가? " + result);
input = 4;
predicateAB = predicateA.negate();
result = predicateAB.test(input);
System.out.println(input + "은/는 2의 배수가 아닌가? " + result);
}
}
6은/는 2의 배수이면서 3의 배수인가? true
4은/는 2의 배수 또는 3의 배수 인가? true
4은/는 2의 배수가 아닌가? false
-
isEqual() 정적 메소드
- Predicate<T>의 정적 메소드
Predicate<Object> predicate = Predicate.isEqual(targetObject); boolean result = predicate.test(sourceObject);
- Objects.equals(sourceObject, targetObject)는 다음과 같은 리턴값을 제공한다.
sourceObject targetObject 리턴값 null null true(주의 해야 한다.) not null null false null not null false not null not null sourceObject.equals(targetObject)의 리턴값 -
isEqual() 예제 코드
import java.util.function.Predicate;
public class PredicateIsEqualEx {
public static void main(String[] args) {
Predicate<String> predicate;
predicate = Predicate.isEqual(null);
System.out.println("null, null: " + predicate.test(null));
predicate = Predicate.isEqual("java8");
System.out.println("null, java8: " + predicate.test(null));
predicate = Predicate.isEqual(null);
System.out.println("java8, null: " + predicate.test("java8"));
predicate = Predicate.isEqual("java8");
System.out.println("java8, java8: " + predicate.test("java8"));
}
}
null, null: true
null, java8: false
java8, null: false
java8, java8: true
- BinaryOperator<T> 함수적 인터페이스의 정적 메소드
- Comparator를 이용해서 최대 T와 최소 T를 얻는 BinaryOperator<T>를 리턴한다.
리턴타입 | 정적 메소드 |
---|---|
BinaryOperator<T> | minBy(Comparator<? super T> comperator) |
BinaryOperator<T> | maxBy(Comparator<? super T> comparator) |
- Comparator<T>는 다음과 같이 선언된 함수적 인터페이스이다. o1과 o2를 비교해서 o1이 작으면 음수를, 동일하면 0, o1이 크면 양수를 리턴해야하는 compare() 메소드가 선언되어 있다.
@FunctionalInterface
public interface Comparator<T> {
public int compare(T o1, T o2);
}
- Comparator<T>를 타겟 타입으로하는 람다식은 다음과 같이 작성할 수 있다.
(o1, o2) -> { ...; return int 값; }
- 만약 o1과 o2가 int 타입이라면 다음과 같이 Integer.compare(int, int) 메소드를 이용할 수 있다.
- 만약 int 타입이 아니라면 직접 비교해서 return 하도록 작성을 해야한다.
(o1. o2) -> Integer.compare(o1, o2);
- 예제 코드
import java.util.function.BinaryOperator;
public class OperatorMinByMaxByEx {
public static void main(String[] args) {
BinaryOperator<Fruit> binaryOperator;
Fruit fruit;
binaryOperator = BinaryOperator.minBy(
(f1, f2) -> Integer.compare(f1.getPrice(), f2.getPrice()));
fruit = binaryOperator.apply(
new Fruit("수박", 20000), new Fruit("오렌지", 10000));
System.out.println("minBy: " + fruit.getName());
binaryOperator = BinaryOperator.maxBy(
(f1, f2) -> Integer.compare(f1.getPrice(), f2.getPrice()));
fruit = binaryOperator.apply(
new Fruit("수박", 20000), new Fruit("오렌지", 10000));
System.out.println("maxBy: " + fruit.getName());
}
minBy: 오렌지
maxBy: 수박
- 메소드를 참조해서 매개변수의 정보 및 리턴타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이 목적이다.
- 종종 람다식은 기존 메소드를 단순하게 호출만 하는 경우가 있다.
(left, right) -> Math.max(left, right);
- 위의 형태에서 left와 right의 중복 사용을 줄여서 아래와 같이 메소드 참조의 형태로 사용할 수 있다.
Math::max;
- 메소드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성됨
- 타겟 타입에서 추상 메소드의 매개변수 및 리턴 타입에 따라 메소드 참조도 달라진다.
- ex) IntBinaryOperator 인터페이스는 두개의 int 매개값을 받아 int 값을 리턴하므로 동일한 매개값과 리턴타입을 갖는 Math 클래스의 max() 메소드를 참조할 수 있다.
IntBinaryOperator operator = Math::max;
- 정적메소드 참조
클래스::메소드
- 인스턴스 메소드 참조
참조변수::메소드
- 사용 예시
public class Calculator {
public static int staticMethod(int x, int y) {
return x + y;
}
public int instanceMethod(int x, int y) {
return x + y;
}
}
IntBinaryOperator operator;
//정적 메소드 참조
operator = (x, y) -> Calculator.staticMethod(x, y);
operator = Calculator::staticMethod;
//인스턴스 메소드 참조
Calculator obj = new Calculator();
operator = (x, y) -> obj.instanceMethod(x, y);
operator = obj::instanceMethod;
- 예제 코드
import java.util.function.IntBinaryOperator;
public class MethodReferencesEx {
public static void main(String[] args) {
IntBinaryOperator operator;
operator = (x, y) -> Calculator.staticMethod(x, y);
System.out.println("결과1: " + operator.applyAsInt(1, 2));
operator = Calculator::staticMethod;
System.out.println("결과2: " + operator.applyAsInt(3, 4));
Calculator obj = new Calculator();
operator = (x, y) -> obj.instanceMethod(x, y);
System.out.println("결과3: " + operator.applyAsInt(5, 6));
operator = obj::instanceMethod;
System.out.println("결과4: " + operator.applyAsInt(7, 8));
}
}
결과1: 3
결과2: 7
결과3: 11
결과4: 15
-
(a, b) -> { a.instanceMethod(b); }
->클래스::instanceMethod
-
왼쪽의 람다식을 메소드 참조로 구현하면 오른쪽과 같다.
-
위에서 클래스는 a의 타입이다. a의 타입이 String 이면 클래스는 String이다.
-
메소드 참조로 구현한 것을 보면, 클래스가 가지고 있는 static 메소드를 참조한 것 처럼 보이지만
-
사실은 클래스의 인스턴스 메소드를 호출한 것이다.
-
그렇다면, 어떻게 static메소드를 참조한 것인지, 인스턴스 메소드를 참조한 것인지 알 수 있는가?
- 함수적 인터페이스의 추상메소드의 매개변수와 리턴타입을 보고 결정을 한다.
ToIntBiFunction<String, String> function; //String이 가지고 있는 인스턴스 메소드 호출 //compareToIgnoreCase()는 a가 b보다 사전순으로 먼저이면 음수를, 같다면 0을, b가 먼저라면 양수를 리턴한다. function = (a, b) -> a.compareToIgnoreCase(b); print(function.applyAsInt("Java8", "JAVA8")); function = String::compareToIgnoreCase; print(function.applyAsInt("Java8", "JAVA8"));
-
-
예제 코드
- 아래의
String::compareToIgnoreCase;
을 보면 String 클래스의 정적 메소드를 참조하는 것 처럼 보이지만, - 문맥상으로 볼때 a변수의 인스턴스 메소드를 참조되는 상황이기 때문에, 정적 메소드가 아니라 인스턴스 메소드가 된다.
- 아래의
import java.util.function.ToIntBiFunction;
public class ArgumentMethodReferencesEx {
public static void main(String[] args) {
ToIntBiFunction<String, String> function;
function = (a, b) -> a.compareToIgnoreCase(b);
print(function.applyAsInt("java8", "JAVA8"));
function = String::compareToIgnoreCase;
print(function.applyAsInt("java8", "JAVA8"));
}
public static void print(int order) {
if (order < 0) {
System.out.println("사전순으로 먼저 옵니다.");
} else if (order == 0) {
System.out.println("동일한 문자열 입니다.");
} else {
System.out.println("사전순으로 나중에 옵니다.");
}
}
}
동일한 문자열 입니다.
동일한 문자열 입니다.
-
(a, b) -> { return new 클래스(a, b);}
->클래스::new
- 람다식의 실행부에 객체를 생성하고 리턴하는 코드만 있다면 오른쪽과 같이 생성자 참조를 사용할 수 있다.
- 생성자의 매개값의 타입과 수는 생성자 참조가 대입되는 함수적 인터페이스의 추상 메소드가 어떻게 선언되어 있느냐에 따라서 결정 된다.
- 예시 1
Function<String, Member> function1 = Member::new; Member member1 = function1.apply("angel");
//아래의 생성자 사용 public Member(String id) { System.out.println("Member(String id) 실행"); this.id = id; }
- 예시 2
BiFunction<String, String, Member> function2 = Member::new; Member member2 = function2.apply("김남준", "njkim");
//아래의 생성자 사용 public Member(String name, String id) { System.out.println("Member(String name, String id) 실행"); this.name = name; this.id = id; }
-
예제 코드
- Member.java
public class Member { private String name; private String id; public Member() { System.out.println("Member() 실행"); } public Member(String id) { System.out.println("Member(String id) 실행"); this.id = id; } public Member(String name, String id) { System.out.println("Member(String name, String id) 실행"); this.name = name; this.id = id; } }
- ConstructorReferencesEx.java
import java.util.function.BiFunction; import java.util.function.Function; public class ConstructorReferencesEx { public static void main(String[] args) { Function<String, Member> function1 = Member::new; Member member1 = function1.apply("angel"); BiFunction<String, String, Member> function2 = Member::new; Member member2 = function2.apply("namjun", "njkim"); } }
Member(String id) 실행 Member(String name, String id) 실행