람다식(Lambda Expressions)
- 자바 8에 추가된 가장 큰 특징
- "함수형" 프로그래밍 형태를 받아들인 결과
함수형 프로그램이란? (함수형 vs 객체지향)
- 함수형 : 1950년대 , 객체지향 : 1990년대 (역사가 더 오래되었음)
- 기능 위주의 프로그래밍 기법
- 매개변수만을 사용하여 만드는 함수(순수 함수, pure function) 를 구현하고 호출함으로써 외부 자료에 부수적인 영향을 주지 않도록 구현하는 방식
- 객체 지향 : 객체를 기반으로 구현, 클래스에 속성과 기능을 정의
함수형 : 함수를 기반으로 자료를 입력받아 구현, 기능 즉 함수가 따로 존재
- 함수 이름이 없는 익명 함수를 만드는 것 메서드의 이름과 리턴타입을 빼고 '->' 를 활용하여 람다식을 구현
ex) public int add(int x, int y) {
return x + y;
}
의 메서드를 람다식으로 표현하면
(int x, int y) -> { return x + y; }
< 람다식의 기본 문법 >
(데이터타입 매개변수, ...) -> { 실행문, ... }
1. 기본형
(int x) -> { System.out.println(x); }
2. 매개변수의 타입을 추론할 수 있는 경우에는 타입 생략 가능
(x) -> { System.out.println(x); }
3. 매개변수나 실행문이 하나라면 소괄호() 와 중괄호 {} 를 생략할 수 있음.
(이 때, 세미콜론(;)은 생략!)
x -> System.out.println(x)
주의! 매개변수가 두 개 이상인 경우 소괄호() 를 생략할 수 없다!
4. 매개변수가 없을 경우 소괄호() 를 사용한다. (생략 불가!)
() -> System.out.println(x)
5. 리턴이 필요한 경우 return 키워드를 사용
(x, y) -> { return x + y; }
주의! return 문은 중괄호를 생략할 수 없다!
6. 실행문이 단순히 return 문 하나로 표현되는 경우
표현식만 사용할 수 있으며, 이 때 리턴 값은 표현식의 결과값이 됨
(주의! 이 때, 세미콜론을 붙이지 않는다!)
(x, y) -> x + y
// 두 수를 입력받아 큰 수를 출력하는 메서드를 가진 클래스 정의
class MaxPrintNumber {
public int maxNum(int num1, int num2) {
if(num1 >= num2) {
return num1;
} else {
return num2;
}
}
}
// MyNumber 인터페이스를 구현한 MyNumberClass 정의
//interface MyNumber {
// int getMax(int num1, int num2); // 추상 메서드 선언
//}
class MyNumberClass implements MyNumber {
// 두 수를 입력받아 둘 중 큰 숫자를 리턴하는 getMax() 메서드 정의
@Override
public int getMax(int num1, int num2) {
if(num1 >= num2) {
return num1;
} else {
return num2;
}
}
}
위의 getMax() 메서드를 람다식으로 표현하기 위해 함수형 인터페이스 선언한다
=> 자바에서는 참조 변수 없이 메서드를 호출할 수 없음
=> 람다식을 구현하기 위해 함수형 인터페이스를 만들고, 인터페이스에 람다식으로 구현할 메서드를 선언하는 형태로 사용
=> 주의! 람다식은 하나의 메서드를 구현하여 인터페이스형 변수에 대입하므로 인터페이스가 두 개 이상의 메서드를 가지면 안된다!
@FunctionalInterface // 함수형 인터페이스 용도로 사용됨을 알림
interface MyNumber {
int getMax(int num1, int num2); // 추상 메서드 선언
// int add(int num1, int num2);
}
// MaxPrintNumber 클래스 활용
MaxPrintNumber mpn = new MaxPrintNumber();
System.out.println(mpn.maxNum(10, 20));
System.out.println("----------------------------");
// 구현클래스를 활용한 getMax 메서드 호출
MyNumber max = new MyNumberClass(); // 업캐스트 적용
System.out.println(max.getMax(10, 20));
System.out.println("-----------------------------");
// 람다식 구현과 호출
MyNumber max2 = (x, y) -> (x >= y) ? x : y ;
// => 람다식을 인터페이스형 max2 변수에 대입
System.out.println(max2.getMax(10, 20)); // 인터페이스형 변수로 메서드 호출
익명 객체를 생성하는 람다식
- 자바는 객체 지향 언어로 객체 없이 메서드를 호출할 수 없다
- 람다식은 객체 없이 인터페이스의 구현만으로 메서드를 호출할 수 있는데 그 이유는 익명 내부 클래스에 있다
- 익명 내부 클래스는 클래스 이름 없이 인터페이스 자료형 변수에 바로 메서드 구현부를 생성하여 대입할 수 있음
- 즉, 람다식으로 메서드를 구현해서 호출하면 컴퓨터 내부에서는 익명클래스가 생성되고, 이를 통해 익명 객체가 생성되는 것
람다식에서 사용하는 지역 변수
- 람다식 코드에서는 외부 메서드의 지역 변수를 수정할 수 없음 그 이유는 지역 변수는 메서드 호출이 끝나면 메모리에서 사라지기 때문에 익명 내부 클래스에서 사용하는 경우에는 지역 변수가 상수로 변하기 때문
int i = 100; // main() 메서드의 지역변수
StringConcat concat2 = (s, v) -> {
// i = 200; // main() 메서드의 지역변수 i는 상수(final) 이므로 값 변경 불가!
// => Local variable i defined in an enclosing scope
// must be final or effectively final
System.out.println(i);
System.out.println(s + "," + v);
};
concat2.makeString("itwill", "busan");
StringConcat concat3 = new StringConcat() {
@Override
public void makeString(String s1, String s2) {
System.out.println(s1 + "," + s2);
}
};
// => 내부적으로 익명 구현 객체가 생성되어 메서드를 호출할 수 있게 됨
concat3.makeString("Hello", "World");
함수형 인터페이스(functional interface) 또는 타겟타입(target type)
- 람다식은 결과적으로 "인터페이스의 클래스를 손쉽게 구현하는 방법"
- 반드시 하나의 abstract 메서드만 존재
- 만약 abstract 메서드가 없거나 두 개 이상 존재한다면 람다식으로 대체할 수 없음
- 함수형 인터페이스 @FunctionalInterface 어노테이션 선언
// 함수형 인터페이스를 사용하는 람다식 유형
// 1. 파라미터와 리턴타입이 없는 경우 (파라미터 : X, 리턴타입 : X)
@FunctionalInterface
interface MyFunc1 {
// 함수형 인터페이스 어노테이션(@FunctionalInterface) 선언 시 발생!
// => 함수형 인터페이스는 반드시 하나의 추상메서드(abstract method)를 가져야 함
public void methodA();
// public void methodB();
}
// 2. 파라미터가 있는 람다식(파라미터 : O)
@FunctionalInterface
interface MyFunc2 {
public void methodB(String msg);
}
// 3. 리턴타입이 있는 람다식(파라미터 : O, 리턴타입 : O)
@FunctionalInterface
interface MyFunc3 {
public String methodC(String msg);
}
// 1. 파라미터와 리턴타입이 없는 경우 (파라미터 : X, 리턴타입 : X)
public static void useFIMethodA(MyFunc1 fi) {
fi.methodA();
}
// 2. 파라미터가 있는 람다식(파라미터 : O)
public static void useFIMethodB(MyFunc2 fi) {
fi.methodB("홍길동");
}
// 3. 리턴타입이 있는 람다식(파라미터 : O, 리턴타입 : O)
public static String useFIMethodC(MyFunc3 fi) {
return fi.methodC("이순신");
}
useFIMethodA(new MyFunc1() {
@Override
public void methodA() {
System.out.println("익명 내부 클래스 형태!");
}
});
System.out.println("------------------------------------");
// 1. 파라미터와 리턴타입이 없는 경우 (파라미터 : X, 리턴타입 : X)
System.out.println("(파라미터 : X, 리턴타입 : X)");
// 정적 메서드 useFIMethodA의 파라미터가 MyFunc1 의 인터페이스 타입이므로
// 람다식으로 만들어지는 익명 객체가 파라미터로 사용됨
// 표현방식1
useFIMethodA( () -> {
System.out.println("람다식1!");
});
// 표현방식2
useFIMethodA(() -> System.out.println("람다식2!"));
System.out.println("---------------------------------");
// 2. 파라미터가 있는 람다식(파라미터 : O)
System.out.println("(파라미터 : O, 리턴타입 : X)");
// 표현방식1
useFIMethodB((String msg) -> {
System.out.println("람다식1 : " + msg);
}
);
// 표현방식2
useFIMethodB( msg -> System.out.println("람다식2 : " + msg) );
System.out.println("-------------------------------------");
// 3. 리턴타입이 있는 람다식(파라미터 : O, 리턴타입 : O)
System.out.println("(파라미터 : O, 리턴타입 : O)");
// 표현방식1
String result = useFIMethodC((String msg) -> {
return "람다식1 : " + msg;
});
System.out.println(result);
// 표현방식2
System.out.println( useFIMethodC(msg -> "람다식2 : " + msg) );
} // main() 메서드 끝
TEST
public class Test {
public static void main(String[] args) {
/*
* 문자열을 입력받아 두 문자열을 연결하여 출력하는 프로그램 정의
* 람다식을 사용하여 구현
* ex) Hello 와 World 를 매개변수값으로 전달하면
* "Hello,World" 가 출력
*/
// String s1 = "Hello";
// String s2 = "World";
Scanner scan = new Scanner(System.in);
System.out.print("첫 번째 문자열을 입력하세요 : ");
String s1 = scan.nextLine();
System.out.print("두 번째 문자열을 입력하세요 : ");
String s2 = scan.nextLine();
// 객체 지향 프로그래밍 방식
StringConcatImpl sc = new StringConcatImpl();
sc.makeString(s1, s2);
// ---------------------------------------------
// makeString() 메서드를 람다식으로 표현
StringConcat concat = (String s, String v) -> System.out.println(s + ", " + v);
concat.makeString(s1, s2);
}
}
@FunctionalInterface
interface StringConcat {
public void makeString(String s1, String s2);
}
class StringConcatImpl implements StringConcat {
@Override
public void makeString(String s1, String s2) {
System.out.println(s1 + ", " + s2);
}
}
'⛏️ > JAVA' 카테고리의 다른 글
[JAVA] 44. 스레드(thread) (1) | 2023.11.28 |
---|---|
[JAVA] 43. java.util.function 패키지 (0) | 2023.11.24 |
[JAVA] 41. 익명(anonymous) 객체 (0) | 2023.11.24 |
[JAVA] 40. 중첩 클래스, 중첩 인터페이스 (0) | 2023.11.08 |
[JAVA] 39. Scanner (0) | 2023.11.07 |