2022. 12. 10. 00:44ㆍ자바
<람다 표현식>
자바를 함수형 언어처럼 사용할 수 있게 하는 문법
함수형 언어? 데이터 처리를 수학적 함수의 계산으로 취급 가능
1. 표현 방법
람다 연산자 '->'를 기준으로 선언부와 구현부로 나뉨
(타입 매개변수) -> {실행문}
선언부 구현부
예) () -> 123.45 //double myMeth() { return 123.45; }와 같은 뜻
(n) -> (n%2)==0 //bool myMeth(n) {return n%2==0 } 와 같은 뜻
인자가 위의 예처럼 하나만 있는 경우 괄호 안쓰고 n -> (n%2)==0 이렇게 써도 됨
2. 특징
- 람다 표현식은 리턴 타입을 명시할 필요 없음
- 메서드와 달리 이름도 없음 (익명 함수라고도 함)
- 메서드와 달리 특정 클래스에 종속되지 않는다.
- 예외처리도 가능
- 메서드의 인자로 전달될 수도 있고 변수에 대입될 수도 있음
<함수형 인터페이스>
오직 하나의 추상 메서드를 가지는 인터페이스
예) interface MyNumber {
double getValue();
}
Runnable 인터페이스도 함수형 인터페이스라고 할 수 있다.
why? run() 메서드 하나만을 가지기 때문
1. 쓰임새
람다 표현식은 함수형 인터페이스를 구현한 것( 람다 표현식의 타입을 정의)
예) MyNumber myNum;
myNum = () -> 123.45; //MyNumber 인터페이스의 getValue() 메서드를 구현한 것
* 람다 표현식과 함수형 인터페이스의 추상 메서드는 매개변수와 리턴 값이 일치 해야한다.
* myNum.getValue();를 호출하면 : getValue()콜->lambda 표현식 실행->myNum에 저장
-예제-
interface NumericTest {
boolean test(int n, int d);
}
class LambdaDemo {
public static void main(String[] args) {
NumericTest isFactor = (n, d) -> (n % d) == 0;
if (isFactor.test(10, 2))
System.out.println("2 is a factor of 10");
if (!isFactor.test(10, 3))
System.out.println("3 is not a factor of 10");
}
}
<블록 람다 표현식>
구현부에 여러 실행문이 존재할 경우 중괄호로 블록화하여 여러 실행문 정의 가능
interface StringFunc {
String func(String n);
}
class BlockLambdaDemo2 {
public static void main(String[] args) {
StringFunc reverse = (str) -> {
String result = "";
int i;
for (i = str.length() - 1; i >= 0; i--)
result += str.charAt(i); //charAt()는 해당 인덱스에 있는 문자를 반환
return result;
};
System.out.println(reverse.func("Lambda"));
System.out.println(reverse.func("Expression"));
}
}
<제네릭 함수형 인터페이스>
함수형 인터페이스에 제네릭 타입 매개변수 지정 가능
(람다 표현식엔 제네릭 타입 불가)
interface SomeFunc<T> {
T func(T t);
}
class GenericFunctionalInterfaceDemo {
public static void main(String[] args) {
SomeFunc<String> reverse = (str) -> {
...
};
SomeFunc<Integer> factorial = (n) -> {
...
};
}
}
<람다 표현식을 인자로 사용하기>
메서드의 매개변수가 함수형 인터페이스 일 때 인자로 람다표현식 사용 가능.
마치 실행 가능한 코드를 넘겨주는 것( c의 함수형 포인터랑 비슷)
interface StringFunc {
String func(String n);
}
class LambdasAsArgumentsDemo {
static String stringOp(StringFunc sf, String s) {
return sf.func(s); //인터페이스에 있는 메서드가 실행(인자는 s)
}
public static void main(String[] args) {
String outStr;
String inStr = "Lambdas add power to Java";
outStr = stringOp((str) -> str.toUpperCase(), inStr);
// toUpperCase(): 대문자로 변환
// 단일 람다식을 인자로 사용
outStr = stringOp((str) -> {
String result = "";
int i;
for (i = 0; i < str.length(); i++)
if (str.charAt(i) != ' ')
result += str.charAt(i);
return result;
}, inStr); //블록 람다식을 인자로 사용
StringFunc reverse = (str) -> {
String result = "";
int i;
for (i = str.length() - 1; i >= 0; i--)
result += str.charAt(i);
return result;
};
System.out.println("The string reversed: " + stringOp(reverse, inStr));
// 람다식을 변수에 저장하여 사용
}
}
<람다 표현식과 예외처리>
람다 표현식도 예외를 던질 수 있음
(단, 함수형 인터페이스의 추상 메서드가 던지는 예외타입들과 일치 해야함)
interface DoubleNumericArrayFunc {
double func(double[] n) throws EmptyArrayException;
// throws문: 메서드를 호출한 곳에서 예외처리를 하도록 할 수 있다.
}
class EmptyArrayException extends Exception {// 사용자 정의 예외로, Exception을 상속받는다.
EmptyArrayException() {
super("Array Empty");
}
}
class LambdaExceptionDemo {
public static void main(String[] args) throws EmptyArrayException {
double[] values = { 1.0, 2.0, 3.0, 4.0 };
DoubleNumericArrayFunc average = (n) -> {
double sum = 0;
if (n.length == 0)
throw new EmptyArrayException();// 사용자 정의 예외로 던지기
for (int i = 0; i < n.length; i++)
sum += n[i];
return sum / n.length;
};//인터페이스의 메서드 구현한 것
System.out.println(average.func(values));
System.out.println(average.func(new double[0])); // 빈 배열을 인자로 넘겨서 예외 발생
}
}
<람다 표현식과 변수 캡처>
람다표현식 구현부에서 외부의 변수를 접근 가능
외부의 변수? 인스턴스 변수, static 변수 등
또한, this 키워드를 사용할 수 있음
변수캡처(vaiable capture)
: 구현부에서 외부의 지역 변수를 참조하는 것
이 때 변수는 effectively final이 됨(final 변수와 같이 값을 변경할 수 없는 상수값이 된다.)
interface MyFunc {
int func(int n);
}
class VarCapture {
public static void main(String[] args) {
int num = 10;
MyFunc myLambda = (n) -> {
int v = num + n;
num++; // 컴파일에러-> num은 effectively final이 됨.
return v;
};
num = 9; // 컴파일 에러-> 참조한 이후부터 num은 effectively final이 됨.
}
}
<메서드 참조>
메서드를 실행하지 않고 참조 변수로 넘겨 줄때 사용
함수형 인터페이스를 람다식이 아닌 일반 메서드를 참조시켜 선언하는 방법
메서드 참조를 사용하면 람다식과 마찬가지로 함수형 인터페이스로 반환된다.
static 메서드를 참조할 때
-문법: classname::methodname
오브젝트 메서드를 참조할 때
-문법: objname::methodname
public class Main {
interface LambdaFunction{
int min(int x, int y);
}
public static void main(String[] args) {
// 1. 람다식 사용
LambdaFunction lambdaFunction = (x, y) -> Math.min(x, y);
System.err.println(lambdaFunction.min(3, 4));
// 2.메서드 참조 사용
LambdaFunction lambdaFunction = Math::min;
System.err.println(lambdaFunction.min(3, 8));
}
}
위 예제를 보면 알겠지만 람다식은 단순히 두개의 값을 Math.min() 메서드의 매개값으로 전달하는 역할만 하는것뿐 별다른 의미는 없다.
이럴때 함수형 인터페이스의( 매개변수 타입, 개수, 반환형)과 사용하려는 메서드의 (매개변수 타입, 개수, 반환형)이 일치한다면 2번 처럼 메서드 참조를 사용하여 이를 간결하게 표현할 수 있다.
interface StringFunc {
String func(String n);
}
class MyStringOps {
static String strReverse(String str) {//StringFunc의 func()와 호환(리턴타입과 매개변수가 같아야함)
String result = "";
int i;
for (i = str.length() - 1; i >= 0; i--)
result += str.charAt(i);
return result;
}
}
class MethodRefDemo {
static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "Lambdas add power to Java";
String outStr;
outStr = stringOp(MyStringOps::strReverse, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reversed: " + outStr);
}
}

func와 strReverse가 호환되어 strReverse함수 결과값이 리턴
*제네릭 메서드 참조도 가능
interface MyFunc<T> {
int func(T[] vals, T v);
}
class MyArrayOps {
static <T> int countMatching(T[] vals, T v) {
int count = 0;
for (int i = 0; i < vals.length; i++)
if (vals[i] == v)
count++;
return count;
}
}
class GenericMethodRefDemo {
static <T> int myOp(MyFunc<T> f, T[] vals, T v) {
return f.func(vals, v);
}
public static void main(String[] args) {
Integer[] vals = { 1, 2, 3, 4, 2, 3, 4, 4, 5 };
int count;
count = myOp(MyArrayOps::<Integer>countMatching, vals, 4);
System.out.println(count);
}
}
<생성자 참조>
메서드 참조와 유사한 방식으로 생성자도 참조할 수 있음
-문법: classname::new
Supplier<String> supplier = () -> new String(); // 람다식 표현
Supplier<String> supplier = String::new; // 메서드 참조
Supplier<Object> supplier = () -> new Object(); // 람다식 표현
Supplier<Object> supplier = Object::new; // 메서드 참조
interface MyFunc {
MyClass func(int n); // func의 리턴 타입이 MyClass가 되어야 생성자 호환 가능
}
class MyClass {
private int val;
MyClass(int v) {
val = v;
}
MyClass() {
val = 0;
}
int getVal() {
return val;
};
}
class ConstructorRefDemo {
public static void main(String[] args) {
MyFunc myClassCons = MyClass::new; // MyClass의 인자 있는 생성자 참조
MyClass mc = myClassCons.func(100);// 위의 생성자가 호출되어 객체 생성
System.out.println("val in mc is " + mc.getVal()); // 100 출력
}
}
*제네릭 생성자 참조도 가능
<사전 정의된 함수형 인터페이스>
자바에서 기본으로 제공되는 함수형 인터페이스
-java.util.function 패키지를 통해 제공

'자바' 카테고리의 다른 글
| 네트워크 프로그래밍 (0) | 2022.12.10 |
|---|---|
| 네트워크 기본 용어 (0) | 2022.12.10 |
| static 키워드 (1) | 2022.12.09 |
| 제네릭 (0) | 2022.12.09 |
| 기타 자바 키워드 (0) | 2022.12.08 |