1. 람다식이란
자바 8부터 지원하는 기능으로 함수형 프로그래밍을 뜻하며, 함수를 정의하고, 함수를 데이터 처리부로 보내서 데이터를 처리하는 기법을 말한다.
쉽게 말해서 함수(자바에서는 메서드)를 간단한 식으로 표현하는 방법으로, 익명 함수라고도 한다.
2. 람다식의 형태
(매개변수) -> { 처리 내용}
3. 메서드로부터 람다식을 만드는 방법
- 메서드의 리턴 타입과 이름을 지우고 매개변수와 구현부 사이에 화살표를 넣어주면 된다.
- 간단한 예
// 메서드 사용
public int max(int a , int b){
return a > b ? a : b;
}
// 접근제어자, 리턴 타입, 메서드 명을 지우고 매개변수와 구현부 사이에 화살표를 넣어준다.
// 람다식 사용
(a, b) -> { a > b ? a : b}
1) 기본
(int a, int b) -> {
return a > b ? a : b;
}
2) 리턴 값이 있는 경우
- 식이나 값만 적고 생략 가능하다.(대신 마지막에 ;을 붙이지 않는다.)
(int a, int b) -> a > b ? a : b
3) 매개 변수의 타입이 추론 가능한 경우
- 매개변수의 타입이 없어도 짐작이 가능한 경우에는 매개변수의 타입 또한 생략이 가능하다.(대부분의 경우 생략 가능)
(a, b) -> a > b ? a : b
4. 주의점
1) 괄호의 생략
- 매개변수가 하나밖에 없는 경우에는 괄호를 생략할 수 있다.
- 단, 매개변수의 타입이 없는 경우에만 생략할 수 있다.
a -> a++
int a -> a++ // 불가
2) 중괄호의 생략
- if문과 같이 중괄호 블럭 안의 문장이 하나일 경우 {}를 생략할 수 있다.(마지막에 ; 를 붙이지 않는다.)
(int i) -> {
System.out.println("i : " + i);
}
// 생략하면
(int i) -> System.out.println("i : " + i);
5. 람다와 함수형 인터페이스
- 함수형 인터페이스 : 단 하나의 추상 메서드만 가지고 있는 인터페이스(@FunctionalInterface를 붙여서 쓴다.)
- 람다식은 익명 객체다. 따라서 람다식을 다룰 때는 참조변수가 필요하며, 그 타입은 함수형 인터페이스이다.
public class LambdaEx {
public static void main(String[] args) {
// 함수형 인터페이스 구현할 때, 익명 객체를 사용하여 구현
CustomFunction customFunction1 = new CustomFunction() {
@Override
public int max(int a, int b) {
return a > b ? a : b;
}
};
int max = customFunction1.max(10, 20);
// 위의 코드를 람다식으로 바꾸면 아래와 같다.
// 또한 람다식은 익명 객체이므로 람다식을 다룰 때는 customFunction라는 참조변수가 필요하다
CustomFunction customFunction2 = (a, b) -> a > b ? a : b;
}
}
1) 함수형 인터페이스의 예
- Comparator
public class LambdaEx {
public static void main(String[] args) {
List<String> list = Arrays.asList("abc", "def", "ghi");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
// 람다를 사용하면 아래와 같아진다.
// public int를 제거하고 매개변수의 타입을 제거한 후 매개변수와 구현부 사이에 화살표를 추가
Collections.sort(list, (o1, o2) -> o2.compareTo(o1));
// 또한 위의 코드는 아래와 같다.
Collections.sort(list, Comparator.reverseOrder());
}
}
2) 함수형 인터페이스 타입 매개변수와 리턴타입
- 매개변수가 함수형 인터페이스 타입인 메서드
static void exec(MyFunction myFunction) {
myFunction.run();
}
- 리턴 타입이 함수형 인터페이스 타입인 메서
static MyFunction getMyFunction() {
MyFunction myFunction = () -> System.out.println("getMyFunction().run()");
return myFunction;
}
- 예제 전체 코드
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class LambdaEx {
@FunctionalInterface
interface MyFunction {
void run();
}
public static void main(String[] args) {
// 람다식으로 run() 구현
MyFunction f1 = () -> System.out.println("f1.run()");
// 익명 클래스로 run()구현
MyFunction f2 = new MyFunction() {
@Override
public void run() {
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction();
f1.run();
f2.run();
f3.run();
exec(f1);
exec(() -> System.out.println("exec().run()"));
}
// 매개변수가 MyFunction인 메서드
static void exec(MyFunction myFunction) {
myFunction.run();
}
// 리턴 타입이 MyFunction인 메서드
static MyFunction getMyFunction() {
MyFunction myFunction = () -> System.out.println("getMyFunction().run()");
return myFunction;
}
}
6. java.util.function
자주 사용되는 함수형 인터페이스를 제공하는 자바 패키지
함수형 인터페이스 | 메서드 | 설명 |
java.lang.Runnable | void run() | 매개변수도 없고, 반환값도 없다 |
Supplier<T> | T get() | 매개변수는 없고, 반환값만 있다. |
Consumer<T> | void accept(T t) | 매개변수는 있고, 반환값은 없다(Supplier와 반대) |
Function<T. R> | R apply(T t) | 일반적인 함수로 하나의 매개변수를 아 결과를 리턴한다. |
Predicate<T> | boolean test(T t) | 조건식을 표현하는데 사용된다. 하나의 매개변수를 가지고 리턴 타입은 boolean이다. |
BiConsumer<T, U> | void accept(T t, U u) | Consumer와 같음(단, 매개변수가 2개) |
BiFunction<T.U.R> | R apply(T t, U u) | Function과 같음(단, 매개변수가 2개) |
BiPredicate<T, U> | boolean test(T t, U u) | Predicate와 같음(단, 매개변수가 2개이며 하나의 결과를 리턴한다.) |
UnaryOperator<T> | T apply(T t) | Function의 자손 매개변수와 리턴 타입이 같다. |
BinaryOperator<T> | T apply(T t, T t) | BiFunction의 자손 매개변수와 리턴 타입이 같다. |
- 사용 예
import java.util.function.Predicate;
public class FunctionEx {
public static void main(String[] args) {
Predicate<String> strIsEmpty = s -> s.length() == 0;
String s = "";
if(strIsEmpty.test(s)) System.out.println("empty string");
}
}
7. 함수형 인터페이스를 사용하는 컬렉션 프레임워크
인터페이스 | 메서드 | 설명 |
Collection | boolean removeIf(Predicate<E> filter) | 조건에 맞는 요소를 삭제한다. |
List | void replaceAll(UnaryOperator<E> operator) | 모든 요소를 변환하여 대체한다. |
Iterable | void forEach(Consumer<T> action) | 모든 요소에 작업 action을 수행한다. |
Map | V compute(K key, BiFunction(K.V.V) f) | 지정된 키의 값에 작업 f를 수행한다. |
V computeIfAbsent(K key, Function<K.V> f) | 키가 없으면 작업 f를 수행 후 추가한다. | |
V computeIfPresent(K key, BiFunction<K.V.V> f) | 지정된 키가 있을 때, 작업 f를 수행한다. | |
V merge(K key, V value, BiFunction<V.V.V> f) | 모든 요소에 병합 작업(f)를 수행한다. | |
void forEach(BiConsumer<K.V> action) | 모든 요소에 작업 action을 수행한다. | |
void replaceAll(BiFunction<K.V.V> f) | 모든 요소에 치환 작업 f를 수행한다. |
8. 메서드 참조
하나의 메서드만 호출하는 람다식은 '메서드 참조'를 통해서 더 간단하게 바꿀 수 있다.
1) 메서드의 메서드 참조
- 메서드 참조의 형
클래스 이름 :: 메서드 이름
종류 | 람다식 | 메서드 참조 |
static 메서드 참조 | (x) -> ClassName.method(x) | ClassName::method |
인스턴스 메서드 참조 | (obj.x) -> obj.method(x) | ClassName::method |
특정 객체 인스턴스 메서드 참조 | (x) -> obj.method(x) | obj::method |
- 메서드 참조 예
import java.util.function.Function;
public class MethodReferenceEx {
public static void main(String[] args) {
Function<String, Integer> f1 = (String s) -> Integer.parseInt(s);
// 메서드 참조로 바꾸기
// 매개변수로 주어진 정보를 가지고 입력정보를 유추해낼 수 있다
Function<String, Integer> f2 = Integer::parseInt;
System.out.println(f2.apply("100") + 100);
}
}
2) 생성자의 메서드 참조
- 생성자와 메서드 참조
Supplier<MyClass> s1 = () -> new MyClass();
Function<Integer, MyClass> s2 = (i) -> new MyClass(i);
// 메서드 참조로 바꾸면
Supplier<MyClass> s1 = MyClass::new;
Function<Integer, MyClass> s2 = MyClass::new;
- 배열의 매서드 참조
Function<Integer, int[]> f1 = (x) -> new int[x];
// 람다식을 메서드 참조로 바꾸면
Function<Integer, int[]> f2 = int[]::new;
import java.util.function.Function;
import java.util.function.Supplier;
public class MethodReferenceEx {
public static void main(String[] args) {
Supplier<MyClass> s1 = () -> new MyClass();
MyClass myClass1 = s1.get();
System.out.println(myClass1);
System.out.println(s1.get());
// 메서드 참조로 바꾸면
Supplier<MyClass> s2 = MyClass::new;
MyClass myClass2 = s2.get();
System.out.println(myClass2);
System.out.println(s2.get());
/////////// 생성자 ///////////
Function<Integer, MyClass> f1 = (i) -> new MyClass(i);
MyClass myClass3 = f1.apply(100);
System.out.println(myClass3.i);
System.out.println(f1.apply(200).i);
// 메서드 참조로 바꾸면
Function<Integer, MyClass> f2 = MyClass::new;
MyClass myClass4 = f2.apply(300);
System.out.println(myClass4.i);
System.out.println(f2.apply(400).i);
/////////// 배열 ///////////
Function<Integer, int[]> f3 = (i) -> new int[i];
int[] arr1 = f3.apply(10);
System.out.println("arr1 length : " + arr1.length);
// 메서드 참조로 바꾸면
Function<Integer, int[]> f4 = int[]::new;
int[] arr2 = f4.apply(20);
System.out.println("arr2 length : " + arr2.length);
}
}
class MyClass{
int i;
public MyClass() {
}
public MyClass(int i) {
this.i = i;
}
}
'Java' 카테고리의 다른 글
[Java] 스트림(Stream)(2) (0) | 2023.02.13 |
---|---|
[Java] 스트림(Stream)(1) (0) | 2023.02.09 |
[Java] 어노테이션(annotation) (0) | 2023.02.06 |
[Java] 쓰레드(Thread) (3) | 2023.02.02 |
[Java] 열거형(Enum) (0) | 2023.01.27 |