함수형 인터페이스
람다를 이용한 프로그래밍
- 람다를 받는다(함수형인터페이스를 찾던가 만들던가)
- 람다를 호출해야하는지 검사
- 필요할때 람다를 호출
- 모든 람다의 핵심은 지연실행이다. 코드를 나중에 실행할 이유는 다음과 같다.
- 별도의 스레드에서 코드실행
- 코드를 여러번 실행
- 알고리즘에서 코드를 적절한 시점에서 실행(정렬 비교연산 등)
- 어떤일이 발생했을 때 코드 실행(이벤트 등..)
- 필요할때만 코드 실행
각 픽셀에 Color → Color 함수를 적용하여 이미지를 변환한다고 가정.
public static Image transform(Image image, UnaryOperator<Color> f){ // 고정된 값만큼 모든 픽셀의 밝기를 올리는 메서드
// .....
f.apply(image.getColor);
}
Image brightenedImage = transform(image, Color::brighter);
여기서 람다를 받을 방법은 2가지인데,
// 1. 알맞는 함수형인터페이스를 찾던가
T타입을 받아서 T타입을 리턴하므로 UnaryOperator<T>를 사용
// 2. 내가 직접 만들던가
@FunctionalInterface
public interface ColorTransformer{
Color apply(Color color); // 명칭을 apply로 하는 이유는
}
함수 리턴
함수형프로그램에서는 함수를 인자로 받을 수 있을뿐만아니라, 함수자체를 리턴할 수 도 있다.
Image brightenedImage = transform(image, Color::brighter); // 고정된 값으로 이미지를 밝게하지만 내가 원하는 밝기만큼 밝게해주고싶다면?
// 1. 함수 오버로딩
Image brightenedImage = transform(image, (c, factor) -> c.deriveColor(0,1,factor,1), 1.2); // 이렇게 변수를 하나 더받도록 오버로딩해야함
public static <T> Image transform(Image in, BiFunction<Color, T> f, T val){ ... }
// 2. 함수 자체를 리턴하도록함
public static UnaryOperator<Color> setBright(double factor){ // 새롭게 하나 만들고
return c -> c.deriveColor(0,1,factor,1);
}
Image brightenedImage = transform(image, setBright(1.2)); // setBright(1.2)가 이미 UnaryOperator<Color>함수를 리턴하기때문에 setBright의 결과값을 transform의 인자로 사용할 수 있다.
여기서 setBright메서드의 결과가 UnaryOperator<Color>함수를 리턴하기때문에 setBright의 결과값을 transform의 인자로 사용가능한 것은 맞는데, setBright를 미리 계산하여 그 결과값을 transform에 넣을생각하지말고(어차피 c를 받아야해서 계산도못함) 그 리턴값(함수)를 그대로 들고와서 대입하면 쉽다.
리턴값을 계산하여 가져오지말고, 리턴 함수 그자체를 통째로 가져와서 대입하도록 패러다임을 바꿔야함.
위에서는 c -> c.deriveColor(0,1,factor,1); 를 들고와서 transform의 f.apply()가 실행될때 c -> c.deriveColor(0,1,factor,1);를 실행하면된다. c값은 image.getColor가 됨.
합성
이미지 조작을 한다. 먼저 밝게한다음 흑백을 적용한다.
Image image1 = new Image("abc.jps");
Image image2 = transform(image, Color::brighter);
Image image3 = transform(image2, Color::gray); // 이렇게하면되긴하는데, image2라는 쓸때없는 중간변수가 생겨남
합성을 적용하여 중간변수를 만들지않고 한번에 결과를 뽑는다.
public static <T> UnaryOperator<T> compose(UnaryOperator<T> op1, UnaryOperator<T> op2){
return t -> op2.apply(op1.apply(t));
}
Image image3 = transform(image, compose(Color::brighter, Color::gray);
예외처리
람다 표현식에서 예외를 던지면, 해당 예외는 호출자에 전파된다.
public static void do(Runnable first, Runnable second){
first.run(); // <- 여기서 에러가 발생하면 do메서드는 종료되며 이 메서드를 호출한 곳으로 예외를 전달
second.run(); // 실행x
}
// 비동기 실행
public static void do(Runnable first, Runnable second){
Thread t = new Thread(){
public void run(){
first.run(); // <- 여기서 에러가 발생하면 do메서드는 종료되지만, 예외를 전달하지않고 종료됨
second.run(); // 실행x
}
};
t.start();
}
// 아래처럼 예외처리기에 전달하는 것이 좋음
public static void do(Runnable first, Runnable second, **Consumer<Throwable> handler**){
Thread t = new Thread(){
public void run(){
try{
first.run(); // <- 여기서 에러가 발생하면 do메서드는 종료
second.run(); // 실행x
} catch(Throwable t){
**handler.accept(t); // 예외처리**
}
}
};
t.start();
}
// 이런식으로 first가 생성하는 값을 second가 소비한다고해도 처리기 사용가능
public static <T> void do3(**Supplier<T>** first, **Consumer<T>** second, Consumer<Throwable> handler) {
Thread t = new Thread() {
public void run() {
try {
**T result = first.get(); // first가 만든값을 second가 소비, 예외처리도 가능
second.accept(result);**
} catch (Throwable t) {
handler.accept(t);
}
}
};
t.start();
}
위의 예제는 first.get()을 진행하는도중 예외가 발생했을시 예외를 처리하기위해서 try / catch로 잡았다**.**
Supplier의 get()은 예외검사를 안하기때문에 try/catch를 안넣어줘도 컴파일 에러는 없었다.
(다만 예외처리는 못하겠지만)
미연의 방지를 위하여 예외 처리를 강제하고 싶다면 Callable의 call() 메서드를 사용한다. call()메서드는 throws exception이 붙어있어서 반드시 예외처리를 해줘야한다.
public static <T> void do4(Callable<T> first, Consumer<T> second, Consumer<Throwable> handler) {
Runnable r = () -> {
try {
T result = first.call(); // Supplier였을때는 예외처리를 안해줘도 됐지만, Callable은 예외처리가 필수임(throws Exception이 붙어있기때문에)
second.accept(result);
} catch (Throwable t) {
handler.accept(t);
}
};
}
public static void repeatMessage(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i++) {
// Thread.sleep(20); 예외 처리를 해줘야함. try catch로 잡던가 / throw할수있는 함수형인터페이스로 받아주던가(근데 Runnable의 run은 throws Exception이 없음)
}
};
Callable<String> a = () -> {
Thread.sleep(20); // Callable의 call메서드가 exception을 받아주기때문에 try catch로 예외를 잡을필요가없음
return text;
};
new Thread(r).start();
}
제네릭과 람다
List<Employee> staff = ...;
List<Person> tenants = staff; ← 불가능 Person의 후손이 Employee이긴하지만 List<Person>의 후손이 Employee<Person>은 아니므로..
- 인자타입 : <? super T>
- 리턴타입 : <? extends T>
- BinaryOperator<T> 같은 인터페이스는 인자도 T, 리턴도 T 이므로 상쇄되므로 그냥 <T>만 입력
public static <T> void do3(Supplier<? extends T> first, **Consumer<? super T>** second, Consumer<**? super** Throwable> handler) { // 이런식으로 변환
Thread t = new Thread() {
public void run() {
try {
T result = first.get();
second.accept(result);
} catch (Throwable t) {
handler.accept(t);
}
}
};
t.start();
}
'IT > 자바' 카테고리의 다른 글
classpath (0) | 2023.03.26 |
---|---|
ExecutorService (0) | 2023.03.26 |
Java8 스트림(Stream)연산을 사용해보자 (0) | 2022.01.23 |
Java8 람다(Lambda)를 사용해보자 (0) | 2022.01.23 |
Java8 옵셔널(Optional<T>)객체 올바르게 사용하기 (0) | 2022.01.23 |
댓글