[윤성우의 열혈 Java 프로그래밍] Chapter 27 - 람다와 함수형 인터페이스
카테고리: Java lang
태그: java
27-1. 람다와 함수형 인터페이스
- 인스턴스보다 기능 하나가 필요한 상황을 위한 람다
기능 하나를 정의해서 전달해야 하는 상황
자바는 객체지향언어이기 때문에 인스턴스를 전달하는 형태이지만 내용을 보면 메소드, 즉 기능을 전달하는 상황이 생기곤 한다.
이러한 상황에서 람다가 유용하게 쓰일 수 있다.
- 매개변수가 있고 반환하지 않는 람다식
interface LambdaExample {
void print(String s);
}
public class OneParamNoReturn {
public static void main(String[] args) {
LambdaExample l;
// 줄임 없는 표현
l = (String s) -> { System.out.println(s); };
// 중괄호 생략
l = (String s) -> System.out.println(s);
// 매개변수 형 생략
l = (s) -> System.out.println(s);
// 매개변수 소괄호 생략
l = s -> System.out.println(s);
l.print("Lambda example!");
}
}
interface Calculate {
void cal(int a, int b);
}
public class TwoParamNoReturn {
public static void main(String[] args) {
Calculate c; // 매개변수가 둘 이상인 경우, 괄호를 생략하는 것은 불가능하다.
// 덧셈 진행
c = (a, b) -> System.out.println(a + b);
c.cal(4, 3);
// 뺄셈 진행
c = (a, b) -> System.out.println(a - b);
c.cal(4,3);
// 곱셈 진행
c = (a, b) -> System.out.println(a * b);
c.cal(4, 3);
}
}
- 매개변수가 있고 반환하는 람다식
interface RCalculate {
int cal(int a, int b);
}
public class TwoParamAndReturn {
public static void main(String[] args) {
RCalculate c;
// 몸체에 return이 들어가면 중괄호 생략 불가
c = (a, b) -> { return a + b; }; // c = (a, b) -> { a + b; }; 불가능
System.out.println(c.cal(4,3));
// return 문이 메소드의 몸체를 이루는 유일한 경우
c = (a, b) -> a + b;
System.out.println(c.cal(4,3));
}
}
- 매개변수가 없는 람다식
import java.util.Random;
interface Generator {
int rand(); // 매개변수 없는 메소드
}
public class NoParamAndReturn {
public static void main(String[] args) {
Generator g;
g = () -> {
Random random = new Random();
return random.nextInt(50);
};
System.out.println(g.rand());
}
}
- 함수형 인터페이스와 어노테이션
함수형 인터페이스: 추상 메소드가 딱 하나만 존재하는 인터페이스
@FunctionsalInterface 어노테이션 타입을 통해 함수형 인터페이스에 부합하는지를 확인할 수 있다.
인터페이스에 둘 이상의 추상 메소드가 존재하면, 컴파일 오류로 이어진다.
그러나 static, default 선언이 붙은 메소드의 정의는 함수형 인터페이스의 정의에 아무런 영향을 미치지 않는다.
@FunctionalInterface
interface Calculate {
int cal(int a, int b);
default int add(int a, int b) { return a + b; }
static int sub(int a, int b) { return a - b; }
}
- 람다식과 제네릭
인터페이스는 제네릭으로 정의하는 것이 가능하다.
따라서 제네릭으로 정의된 함수형 인터페이스를 대상으로 람다식을 작성할 수도 있다.
@FunctionalInterface
interface Calculator <T> {
T cal(T a, T b);
}
public class LambdaGeneric {
public static void main(String[] args) {
// 참조변수의 형을 지정해서 문장을 구성하면 된다.
Calculator<Integer> intCalculator = (a, b) -> a + b;
System.out.println(intCalculator.cal(3, 4));
Calculator<Double> doubleCalculator = (a, b) -> a + b;
System.out.println(doubleCalculator.cal(4.32, 3.45));
}
}
27-2. 정의되어 있는 함수형 인터페이스
표준 함수형 인터페이스의 추상 메소드 정의에 해당하는 람다식을 작성해서 전달해야한다.
- 미리 정의되어 있는 함수형 인터페이스
‘미리 정의해 놓은’ 함수형 인터페이스들이 있다.
메소드의 반환형과 매개변수 선언에 차이를 둔 다양한 함수형 인터페이스들을 표준으로 정의하고 있다.
위의 표준 인터페이스들을 알아야 removeIf 같은 메소드도 사용할 수 있다.
- Predicate<T>
Predicate<T> 인터페이스에는 다음 추상 메소드가 존재한다.
boolean test(T t); // 전달된 인자를 대상으로 true, false 판단할 때
import java.util.List;
import java.util.Arrays;
import java.util.function.Predicate;
class PredicateDemo {
public static int sum(Predicate<Integer> p, List<Integer> lst) {
int s = 0;
for(int n : lst) {
if(p.test(n))
s += n;
}
return s;
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 5, 7, 9, 11, 12);
int s;
s = sum(n -> n%2 == 0, list);
System.out.println("짝수 합: " + s);
s = sum(n -> n%2 != 0, list);
System.out.println("홀수 합: " + s);
}
}
- Supplier<T>
Supplier<T> 인터페이스에는 다음 추상 메소드가 존재한다.
T get(); // 단순히 무엇인가 반환할 때
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.function.Supplier;
class SupplierDemo {
public static List<Integer> makeIntList(Supplier<Integer> s, int n) {
List<Integer> list = new ArrayList<>();
for(int i = 0; i < n; i++)
list.add(s.get()); // 난수를 생성해 담는다.
return list;
}
public static void main(String[] args) {
Supplier<Integer> spr = () -> {
Random rand = new Random();
return rand.nextInt(50);
};
List<Integer> list = makeIntList(spr, 5);
System.out.println(list);
list = makeIntList(spr, 10);
System.out.println(list);
}
}
- Consumer<T>
Consumer<T>에는 다음 추상 메소드가 존재한다.
void accept(T t); // 전달된 인자 기반으로 '반환' 이외의 다른 결과를 보일 때
import java.util.function.Consumer;
class ConsumerDemo {
public static void main(String[] args) {
Consumer<String> c = s -> System.out.println(s);
c.accept("Pineapple"); // 출력이라는 결과를 보임
c.accept("Strawberry");
}
}
- Function<T, R>
Consumer<T>에는 다음 추상 메소드가 존재한다.
// T: 매개변수형, R: 반환형
R apply(T t); // 전달 인자와 반환 값이 모두 존재할 때
import java.util.function.Function;
class FunctionDemo {
public static void main(String[] args) {
Function<String, Integer> f = s -> s.length();
System.out.println(f.apply("Robot"));
System.out.println(f.apply("System"));
}
}
import java.util.function.Function;
class FunctionDemo2 {
public static void main(String[] args) {
Function<Double, Double> cti = d -> d * 0.393701;
Function<Double, Double> itc = d -> d * 2.54;
System.out.println("1cm = " + cti.apply(1.0) + "inch");
System.out.println("1inch = " + itc.apply(1.0) + "cm");
}
}
- Predicate<T>, Supplier<T>, Consumer<T>, Function<T, R>을 구체화하고 다양화 한 인터페이스들
T를 기본 자료형으로 결정하여 정의한 인터페이스들도 존재한다.
이렇게 할 경우 박싱, 언박싱 과정이 필요없어진다.
다양하게 있으니 필요한 순간에 찾아서 써보도록 하자.
- removeIf 메소드를 사용해 보자.
Collection<E> 인터페이스에 정의되어 있는 다음 디폴트 메소드를 살펴보자.
default boolean removeIf(Predicate<? super E> filter)
위 메소드의 기능에 대한 자바 문서의 설명은 다음과 같다.
“Removes all of the elements of this collection that satisfy the given predicate”
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
class RemoveIfDemo {
public static void main(String[] args) {
List<Integer> ls1 = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
List<Double> ls2 = new ArrayList<>(Arrays.asList(-1.1, 2.2, 3.3, -4.4, 5.5));
// Predicate의 정의에는 와일드카드를 이용한 하한제한이 걸려있기 때문에 Double과 Integer 모두 적용하기 위해 Number로 E를 결정함
Predicate<Number> p = n -> n.doubleValue() < 0.0;
ls1.removeIf(p);
ls2.removeIf(p);
System.out.println(ls1);
System.out.println(ls2);
}
}
댓글 남기기