bdfgdfg

람다 본문

웹프로그래밍/Java

람다

marmelo12 2023. 7. 30. 11:44
반응형

람다식(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 패키지

일반적으로 자주 쓰이는 형식의 메소드를 함수형 인터페이스로 미리 정의해놓은 패키지.

주로 쓰이는 함수형 인터페이스(https://velog.io/@oyeon/14-78-java.util.function-%ED%8C%A8%ED%82%A4%EC%A7%80)

제네릭스 타입 변수 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는 기본형 타입의 값을 래퍼클래스로 감싸기 때문에 당연히 기본형타입으로 처리하는거에 비하면 비효율적이다.

그렇기에 기본형을 사용하는 함수형 인터페이스들도 존재한다

https://velog.io/@cocodori/Lambda

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
Comments