bdfgdfg
람다 본문
람다식(Lambda expression)
람다식은 메소드를 하나의 식으로 표현한 것, 즉 함수를 간략하면서 명확한 식으로 표현할 수 있게 해준다.
-> 메소드를 람다식으로 표현할 시 메소드명과 반환값이 없으므로 람다식을 익명함수라고도 한다.
public class Hello {
public static void main(String[] args)
{
int[] array = new int[10];
Arrays.setAll(array, (i)->(int)(Math.random() * 10));
}
}
C/C++언어와 달리 전역함수 사용도 못하고, 클래스를 만들고 메소드를 작성할 필요 없이 위처럼 간단하게 람다를 통해 메소드를 넘겨줄 수 있다. ( (i)->(int)(Math.random * 10) )
이 람다식을 메소드로 바꿔본다면 밑과 같다.
public int Lambda()
{
return (int)(Math.random() *10);
}
람다식은 메소드에서 이름과 반환타입을 제거 후 매개변수 선언부와 몸통사이에 ->를 추가한다.
(매개변수 선언) -> { 문장 }
즉 MAX값을 구하는 메소드를 람다로 바꾸면
int GetMax(int a, int b)
{
return a > b ? a : b;
}
//
(int a, int b) -> { return a > b ? a : b }
// 반환값이 있는 메소드의 경우 return문 대신 식으로 대신할 수 있음.
// 문장이 아닌 식이므로 끝에 세미콜론을 붙이지 않는다.
(int a, int b) -> { a > b ? a : b }
// 또한 문장(식)이 하나일 때는 괄호 {}를 생략할 수 있다.
(int a, int b) -> a > b ? a : b
위와 같이 사용이 가능하다.
함수형 인터페이스(Functional Interface)
람다식이 가능한 이유는 익명 클래스의 객체와 동등하기 때문.
import java.io.FileInputStream;
import java.util.*;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
interface MyFunction
{
public abstract int max(int a, int b);
}
public class Hello {
public static void main(String[] args)
{
MyFunction f = new MyFunction()
{
@Override
public int max(int a, int b ) { return a > b ? a : b ;}
};
int big = f.max(10, 20);
// 람다식이 익명객체라는것을 알 수 있음.
MyFunction f2 = (int a, int b) -> a > b ? a : b;
int big2 = f2.max(20, 30);
}
}
위와 같이 익명객체를 통해 만든 메소드를 실제 람다식 문법으로 바꾸어서 적용해도 문제없이 컴파일이 잘 되는것을 알 수 있음.
-> 익명객체란 생성타입은 부모 클래스이지만, 실제로는 익명 클래스(이름이 없는)가 부모 클래스의 자손이 되어 객체로 생성되는것을 의미.
-> 인터페이스는 상속을 받기에 그 자식 클래스의 인스턴스화가 되어야한다는 것을 생각하면 쉽다.
-> 단독으로 생성 불가, 클래스 상속(즉 자식) or 인터페이스 구현해서 사용 가능
-> 재사용 목적이 아닌 1번만 사용 하려고 할 때 쓴다.
이 의미는 결국 람다식도 실제로는 익명객체라는 의미.
이렇게 람다식을 다루기 위한 인터페이스를 함수형 인터페이스라고 한다.
@FunctionalInterface
interface MyFunction
{
public abstract int max(int a, int b);
}
함수형 인터페이스는 오직 하나의 추상 메소드만 정의되어 있어야 한다. (static이나 default메소드는 상관x)
그렇기에 @FunctionalInterface 어노테이션을 붙여 이 함수형 인터페이스에는 하나의 추상 메소드만 정의된다는걸 컴파일러에게 알려주는게 좋다.
이제는 밑과 같이 인터페이스를 넘겨줘야하는 코드도 람다식을 통해서 코드를 간편하게 작성 가능하다.
public class Hello {
public static void main(String[] args)
{
// 일반배열을 리스트로 변환
List<String> list = Arrays.asList("aaa","bbb","ccc");
// 밑의 new Comparator도 익명객체(인터페이스를 상속받은 익명 클래스)
Collections.sort(list, new Comparator<String>() {
@Override // 깔끔하게 Override도 붙이는게 좋다고 생각함
public int compare(String s1, String s2) {
return s2.compareTo(s1);
}
});
// 훨씬 간단해짐
Collections.sort(list, (String s1, String s2) -> s2.compareTo(s1) );
}
}
이렇게 람다식이 익명객체라는것을 잊지말자.
-> 이 말은 즉 람다식을 메소드의 매개변수, 반환도 가능해진단 의미.
java.util.function 패키지
일반적으로 자주 쓰이는 형식의 메소드를 함수형 인터페이스로 미리 정의해놓은 패키지.
제네릭스 타입 변수 T는 Type을 R은 Return Type을 의미.
Predicate는 Function의 변형으로 R(Return Type)이 boolean이라는 것만 다르다.
public class Hello {
public static void main(String[] args)
{
Predicate<String> isEmptyStr = (String s1) -> s1.length() <= 0;
String s = "";
if(isEmptyStr.test(s))
System.out.println("OK");
}
}
즉 굳이 우리가 매개변수를 하나 넘기고, 그 매개변수와 비교를 해서 bool값을 만드는 함수형 인터페이스를 직접 작성하지 않아도 위의 이미 만들어진 함수형 인터페이스를 가져다 쓰면 가독성 면에서나 유지보수측면에서도 좋다.
-> 매개변수가 2개인 함수형 인터페이스는 이름앞에 접두사 Bi가 붙음 ex) biPredicate
컬렉션 인터페이스에도 Predicate 함수형 인터페이스를 넘겨줘야하는 메소드가 존재한다.
public class Hello {
public static void main(String[] args)
{
List<Integer> list = new ArrayList();
for(int i = 0; i < 10; ++i)
list.add(i + 1);
// 람다를 배우지 않았더라면.
list.removeIf( new Predicate<Integer>(){
@Override
public boolean test(Integer i1)
{
return (i1 % 2 == 0);
}
});
// 람다식을 배웠따면
list.removeIf( (Integer i1 ) -> i1 % 3 == 0 );
System.out.println(list);
}
}
이제는 람다식을 통해 간단하게 해결가능하고, 위 예제를 통해 미리 정의된 함수형 인터페이스가 자바 내부 컬렉션에도 여럿 쓰인다는걸 알 수 있다.
다만 Integer는 기본형 타입의 값을 래퍼클래스로 감싸기 때문에 당연히 기본형타입으로 처리하는거에 비하면 비효율적이다.
그렇기에 기본형을 사용하는 함수형 인터페이스들도 존재한다
IntFunction, IntPredicate등등.. 찾아보면 더욱많으니 있나? 하면 있다.
메소드 참조
람다식을 더욱 간결하게 표현할 수 있는 방법인 메소드 참조가 있다.
기본적인 람다식은 밑과 같다.
public class Hello {
public static void main(String[] args)
{
Function<String,Integer> fn = (String s1) -> Integer.parseInt( s1 );
}
}
여기서 좀 더 라인을 줄일 수 있는 방법이 존재하는데 바로 더블콜론(::)을 이용해 메소드를 바로 호출하는 것.
Function<String,Integer> fn = (String s1) -> Integer.parseInt( s1 );
Function<String,Integer> fn = Integer::parseInt;
람다식이 단 하나의 메소드만을 호출하는경우 위와 같이 간결하게 작성이 가능하다.
클래스명::메소드명으로 호출이 가능하고, 만약 인스턴스화 된 객체의 메소드를 람다식에서 사용하는 경우는
public class Hello {
public void Test(int a)
{
System.out.println(a);
}
public static void main(String[] args)
{
List<Integer> list = new ArrayList();
for(int i = 0; i < 10; ++i)
list.add( i + 1);
Hello h = new Hello();
list.forEach( h::Test ); // foreach의 매개변수 함수형 인터페이스는 Consumer다.(반환형이 void이고 매개변수가 1개)
//list.forEach( System.out::println ); 바로 이렇게 호출하는법도 있음.
}
}
위와 같이 참조변수::메소드명으로 접근해야한다.
'웹프로그래밍 > Java' 카테고리의 다른 글
자바의 직렬화 (0) | 2023.08.01 |
---|---|
스트림(Stream) (0) | 2023.07.30 |
쓰레드 (0) | 2023.07.29 |
제네릭스 & 어노테이션 (0) | 2023.07.29 |
자바의 컬렉션(Collection) (0) | 2023.07.27 |