728x90
반응형
람다 표현식(Lambda Expression)
- 람다 표현식이란 간단히 말해 메소드를 하나의 식으로 표현한 것
- 메소드를 람다 표현식으로 표현하면, 클래스를 작성하고 객체를 생성하지 않아도 메소드를 사용할 수 있음.
- 익명 클래스: 단 하나의 객체만을 생성할 수 있는 클래스
- 자바라는 언어 특성상 모든 메소드는 객체 안에 있어야 함
- ⇒ 자바에서 람다 표현식은 익명 클래스의 객체와 같다고 할 수 있음.
// 람다 표현식 = 사실상 객
(x, y) -> x < y ? x : y;
// 익명 클래스
new Object() {
int min(int x, int y) {
return x < y ? x : y;
}
}
- 이러한 람다 표현식은 메소드의 매개변수로 전달될 수도 있으며, 메소드의 결괏값으로 반환될 수도 있음 (결국은 객체니까)
- 람다 표현식을 사용하면, 기존의 불필요한 코드를 줄여주고, 작성된 코드의 가독성을 높여줌
- → 함수형 프로그래밍 가능
람다 표현식 작성
화살표(->) 기호를 사용하여 람다 표현식을 작성
(매개변수목록) -> { 함수몸체 }
람다 표현식을 작성 팁
- 매개변수의 타입을 추론할 수 있는 경우에는 타입을 생략할 수 있음.
- 매개변수가 하나인 경우에는 괄호(())를 생략할 수 있음.
- 함수의 몸체가 하나의 명령문만으로 이루어진 경우에는 중괄호({})를 생략할 수 있음. (이때 세미콜론(
;
)은 붙이지 않음) - 함수의 몸체가 하나의 return 문으로만 이루어진 경우에는 중괄호({})를 생략할 수 없음.
- return 문 대신 표현식을 사용할 수 있으며, 이때 반환값은 표현식의 결괏값이 됨. (이때 세미콜론(
;
)은 붙이지 않음)
함수형 인터페이스(functional interface)
- 람다 표현식을 변수나 매개변수에 할당하기 위해서는 람다 표현식을 저장하기 위한 참조 변수의 타입을 결정해야 함
- 람다식은 사실상 익명 객체이므로 이러한 객체를 다루기 위해서는 참조 변수가 필요
Object obj = new Object() {
int min(int x, int y) {
return x < y ? x : y;
}
}
// 그렇다면?
타입 obj = (x, y) -> x < y ? x : y;
// 여기서 참조변수의 타입이 필요함.
// Object로 하나? 에러.
int value = obj.min(3,5); // 에러: Object 클래스에는 min이 없음.
=> 참조 변수의 타입을 지정해줄 필요가 있으며, 이 타입을 함수형 인터페이스라고 함.
- 함수형 인터페이스: 위의 문법처럼 람다 표현식을 하나의 변수에 대입할 때 사용하는 참조 변수의 타입
- 함수형 인터페이스는 추상 클래스와는 달리 단 하나의 추상 메소드만을 가져야 함
- 즉, 함수형 인터페이스 = 단 하나의 추상 메소드만 선언된 인터페이스
@**FunctionalInterface**
을 사용하여 함수형 인터페이스임을 명시할 수 있음. 이 어노테이션을 인터페이스의 선언 앞에 붙이면, 컴파일러는 해당 인터페이스를 함수형 인터페이스로 인식- 자바 컴파일러는 이렇게 명시된 함수형 인터페이스에 두 개 이상의 메소드가 선언되면 오류를 발생
@FunctionalInterface
interface Calc { // 함수형 인터페이스의 선언
// 인터페이스의 모든 메소드는 public이면서 abstract이기에 생략 가능.
(public abstract) int min(int x, int y);
}
public class Lambda02 {
public static void main(String[] args){
Calc minNum = (x, y) -> x < y ? x : y; // 추상 메소드의 구현
System.out.println(minNum.min(3, 4)); // 함수형 인터페이스의 사용
}
}
함수형 인터페이스 표준 API
이 표준 API들은 사실상 알고보면 별거 아닌 놈들이다. 단어만 그럴싸하게 함수형 인터페이스 API 라고 하지, 그냥 람다식을 이용해 프로그래밍할때 자주 사용될것 같은 함수 모양, 형태를 미리 만들어놓고 인터페이스 이름만 아주 그럴싸하게 전문가 처럼 지어놓은 것일 뿐
Runnable
- 메서드 형태:
void run()
- 활용: 매개 변수를 사용하지 않고, 리턴을 하지 않는 함수 형태로 이용
- 대표적으로 쓰레드의 매개 변수로 이용
- 매개변수 X
- 반환값 X
- 메서드 형태:
Consumer<T>
- 메서드 형태:
void accept(T t)
- 활용: 매개 변수를 사용만 하고, 리턴을 하지 않는 함수 형태로 이용
- 매개변수 O
- 반환값 X
- 외에도,
BiConsumer<T, U>
,XXXConsumer
,ObjXXXConsumer<T>
가 있다
- 메서드 형태:
public static void main(String[] args) {
// 객체 T를 받아 출력하는 함수 정의
Consumer<String> c1 = t -> System.out.println("입력값 : "+ t);
c1.accept("홍길동");
// 객체 T와 U를 받아 출력하는 함수 정의
BiConsumer<String, Integer> c2 = (a, b) -> System.out.println("입력값1 : "+ a+ ", 입력값2 : "+ b);
c2.accept("홍길동", 100);
// int 값을 받아 출력하는 함수 정의
IntConsumer c3 = a -> System.out.println("입력값 : "+ a);
c3.accept(100);
// double 값을 받아 출력하는 함수 정의
DoubleConsumer c4 = a -> System.out.println("입력값 : "+ a);
c4.accept(100.01);
// long 값을 받아 출력하는 함수 정의
LongConsumer c5 = a -> System.out.println("입력값 : "+ a);
c5.accept(2100000000);
}
Supplier<T>
- 메서드 형태:
T getXXX()
- 활용: 매개 변수를 사용하지 않고, 리턴만 하는 함수 형태로 이용
- 대표적으로 쓰레드의 매개 변수로 이용
- 매개변수 X
- 반환값 O
- 외에도
XXXSupplier
가 있다
- 메서드 형태:
public static void main(String[] args) {
// T 객체를 리턴하는 함수 정의
Supplier<Object> supplier = () -> new Object();
System.out.println(supplier.get());
// Boolean 값을 리턴하는 함수 정의
BooleanSupplier booleanSup = () -> true;
System.out.println(booleanSup.getAsBoolean());
// int 값을 리턴하는 함수 정의
IntSupplier intSup = () -> {
int num = (int) (Math.random() * 6) + 1;
return num;
};
System.out.println("주사위 랜덤 숫자 : " + intSup.getAsInt());
// double 값을 리턴하는 함수 정의
DoubleSupplier doubleSup = () -> 1.0;
System.out.println(doubleSup.getAsDouble());
// long 값을 리턴하는 함수 정의
LongSupplier longSup = () -> 1L;
System.out.println(longSup.getAsLong());
}
Function<T, R>
- 메서드 형태:
R applyXXX(T t)
- 활용: 매개값을 매핑(=타입변환)해서 리턴하기
- 매개변수 O
- 반환값 O
- 외에도
BiFunction<T, U, R>
,XXXFunction<T>
,XXXtoYYYFunction
,toXXXFunction<T>
,toXXXBiFunction<T>
가 있다- T와 U는 매개변수 타입, R은 리턴 타입
- 메서드 형태:
public static void main(String[] args) {
// int형을 String으로 타입 변환하는 함수 정의
Function<Integer, String> intToStr = t -> String.valueOf(t);
String str = intToStr.apply(100);
// String을 int형으로 타입 변환하는 함수 정의
ToIntFunction<String> strToInt = t -> Integer.parseInt(t);
int num = strToInt.applyAsInt("100");
// int형을 double형으로 타입 변환하는 함수 정의
IntToDoubleFunction intToDouble = t -> (double) t;
double d = intToDouble.applyAsDouble(100);
}
Predicate<T>
- 메서드 형태:
boolean test(T t)
- 활용: 매개값이 조건에 맞는지 단정해서 boolean 리턴
- 매개변수 O
- 반환값 O
- 형태:
Predicate<T>
,BiPredicate<T, U>
,XXXPredicate
- 메서드 형태:
class Student {
String name;
int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public static void main(String[] args) {
List<Student> list = List.of(
new Student("홍길동", 99),
new Student("임꺽정", 76),
new Student("고담덕", 36),
new Student("김좌진", 77)
);
// int형 매개값을 받아 특정 점수 이상이면 true 아니면 false 를 반환하는 함수 정의
IntPredicate scoring = (t) -> {
return t >= 60;
};
for (Student student : list) {
String name = student.name;
int score = student.score;
// 함수 실행하여 참 / 거짓 값 얻기
boolean pass = scoring.test(score);
if(pass) {
System.out.println(name + "님 " + score + "점은 국어 합격입니다.");
} else {
System.out.println(name + "님 " + score + "점은 국어 불합격입니다.");
}
}
}
}
Operator
- 메서드 형태:
R applyAsXXX(T t)
- 활용: 매개값을 연산해서 결과 리턴
- Function과 빗스하지만, 매개값을 매핑하는 역할보다는 매개값을 이용해서 연산을 수행한 후 동일한 타입으로 리턴값을 제공하는 역할에 초점
- 매개변수 O
- 반환값 O
- 형태:
UnaryOperator<T>
,BinaryOperator<T>
,XXXUnaryOperator
,XXXBinaryOperator
- Unary는 단항, Binary는 이항
- 메서드 형태:
class Operation {
static int calculate(int[] arr, IntBinaryOperator o) {
int result = arr[0];
for (int i = 1; i < arr.length; i++) {
result = o.applyAsInt(result, arr[i]);
}
return result;
}
public static void main(String[] args) {
int[] numbers = {3, 1, 7, 6, 5};
// 배열 요소의 모든 합 구하기
int sum = Operation.calculate(numbers, (x, y) -> {
return x + y;
});
System.out.println(sum);
// 배열 요소의 모든 곱 구하기
int mul = Operation.calculate(numbers, (x, y) -> {
return x * y;
});
System.out.println(mul);
// 배열 요소중 가장 큰 수 구하기
int max = Operation.calculate(numbers, (x, y) -> {
int tmp;
if(x > y)
tmp = x;
else
tmp = y;
return tmp;
});
System.out.println(max);
// 배열 요소중 가장 작은 수 구하기
int min = Operation.calculate(numbers, (x, y) -> {
int tmp;
if(x < y)
tmp = x;
else
tmp = y;
return tmp;
});
System.out.println(min);
}
}
Function 합성
- 두 람다 함수를 연결하여 합성시킬 수 있는데, 이 합성 시키는 메서드를 자바에서 함수형 인터페이스의 디폴트(
default
) 메서드로서 제공함 Function
인터페이스 뿐만 아니라Consumer
나Operator
인터페이스도 존재- 종류
andThen
: f(g(x)) 합성함수compose
: g(f(x)) 합성함수 (andThen의 반대)x.andThen(y)
는y.compose(x)
와 동일
identity
: 항등함수(자기 자신 반환)
public static void main(String[] args) {
Function<Integer, Integer> f = num -> (num - 4); // f(x)
Function<Integer, Integer> g = num -> (num * 2); // g(x)
// f(g(x))
int a = f.andThen(g).apply(10);
System.out.println(a); // (10 - 4) * 2 = 12
// g(f(x)) - andThen을 반대로 해석하면 된다
int b = f.compose(g).apply(10);
System.out.println(b); // 10 * 2 - 4 = 16
}
Predicate 결합
- Predicate을 결합한다는 것은 true / false 조건식에 대하여 이들을 결합하여 and 연산, or 연산을 행할 수 있다는 것이다
- 종류
and
: and 연산or
: or 연산negate
: 역 부정isEqual
: 객체 비교
public static void main(String[] args) {
Predicate<Integer> greater = x -> x > 10;
Predicate<Integer> less = x -> x < 20;
// x > 10 && x < 20
Predicate<Integer> between = greater.and(less);
System.out.println(between.test(15)); // true
// x > 10 || x < 20
Predicate<Integer> all = greater.or(less);
System.out.println(all.test(5)); // true
// x <= 10
Predicate<Integer> negate = greater.negate();
System.out.println(negate.test(50)); // false
}
메소드 참조(Method Reference)
메소드 참조
- 메소드 참조(method reference): 람다 표현식이 단 하나의 메소드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 해줌. 즉, 람다를 더 간단하게 해준것.
- 메소드 참조를 사용하면 불필요한 매개변수를 제거하고 다음과 같이 '
::
' 기호를 사용하여 표현할 수 있음. - 메소드 참조의 3가지 종류
- static 메소드 참조
- 인스턴스 메소드 참조
- 특정 개체 인스턴스 메소드 참조 → 거의 안 씀
클래스이름::메소드이름
또는
참조변수이름::메소드이름
static 메소드 참조
// 기본
Integer method(string s) {
return Integer.parseInt(s) // static 메서드
}
// 람다식
Function<Stirng, Integer> f = (String s) -> Integer.parseInt(s);
// 메소드 참조
Function<String, Integer> f = Integer::parseInt;
// 함수형 인터페이스에 이미 정보가 다 있으니까 가능한 것.
double method(double base, double exponent) {
return Math.pow(base, exponent); // static 메서드
}
Function<Double, Double> f = (double base, double exponent) -> Math.pow(base, exponent);
-> 이는 단순히 Math 클래스의 pow() 메소드를 인수로 전달하는 역할만 하는 람다.
Function<Double, Double> f = Math::pow;
인스턴스 메서드 참조
MyClass obj = new MyClass;
Function<String, Boolean> func = (a) -> obj.equals(a); // 람다 표현식
Function<String, Boolean> func = obj::equals(a); // 메소드 참조
생성자 참조
생성자를 호출하는 람다 표현식도 앞서 살펴본 메소드 참조를 이용할 수 있음. 즉, 단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 참조로 변환할 수 있음
// 생성자에 매개변수가 없는 경우
Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
// 생성자에 매개변수가 있는 경우
Function<Integer, MyClass> s = (i) -> new MyClass(i);
Function<Integer, MyClass> s = MyClass::new;
// 이때 해당 생성자가 존재하지 않으면 컴파일 시 오류 발생
// 배열과 메소드 참
Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식
Function<Integer, double[]> func2 = double[]::new; // 생성자 참조
728x90
반응형
'Java' 카테고리의 다른 글
[Java] Optional (0) | 2025.01.05 |
---|---|
[Java] Stream API (0) | 2025.01.05 |
[Java] 자바 컬렉션 프레임워크 (0) | 2025.01.04 |
[Java] 자바 API 클래스 (6) | 2025.01.03 |
[Java] 예외 처리 (2) | 2025.01.03 |