람다식(Lambda Expression)

함수(메소드)를 간단한 (expression)’으로 표현하는 방법

 

익명 함수(이름이 없는 함수, anonymous function)

 

함수와 메소드의 차이

- 근본적으로 동일. 함수는 일반적 용어, 메소드는 객체지향개념 용어

- 함수는 클래스에 독립적, 메소드는 클래스에 종속적

 

 

람다식 작성하기

1. 메소드의 이름과 반환타입을 제거하고 ‘->’를 블록{} 앞에 추가한다.

int max(int a, int b) {
	return a > b ? a : b;
}

바꾸면 다음과 같다.

(int a, int b) -> {
	return a > b ? a : b;
}

 

2. 반환값이 있는 경우, 식이나 값만 적고 return문 생략 가능( 끝에 ‘;’ 안 붙임)

(int a, int b) -> {
	return a > b ? a : b;
}

바꾸면 다음과 같다.

(int a, int b) -> a > b ? a : b

 

3. 매개변수의 타입이 추론 가능하면 생략가능(대부분의 경우 생략가능)

(int a, int b) -> a > b ? a : b

바꾸면 다음과 같다.

(a, b) -> a > b ? a : b

 

 

람다식 작성할 때 주의사항

1. 매개변수가 하나인 경우, 괄호() 생략 가능(타입이 없을때만)

a -> a * a // OK
int a -> a * a // 에러

2. 블록 안의 문장이 하나뿐일 때, 괄호{} 생략가능(끝에 ‘;’ 안 붙임)

(int i) -> System.out.println(i)
 

람다식은 익명 함수? 익명 객체!

람다식은 익명 함수가 아니라 익명 객체이다.

(a,b) -> a > b ? a : b

위 식은 사실 다음과 같다 (익명클래스의 익명 객체)

new Object() {
	int max(int a, int b) {
		return a > b ? a : b;
	}
}

 

람다식(익명 객체)을 다루기 위한 참조변수가 필요하다. 참조변수의 타입은 무엇으로 해야할까?

 

먼저 모든 클래스의 조상인 Object 타입으로 받아보았다.

Object obj = new Object() {
	int max(int a, int b) {
		return a > b ? a : b;
	}
};

그랬더니 에러가 발생한다. Object 클래스에는 max 메소드가 없기 때문이다.

int value = obj.max(3,5); // 에러, Object 클래스에 max()가 없음

 

그래서 필요한게 함수형 인터페이스이다.

 

 

 

함수형 인터페이스

함수형 인터페이스 단 하나의 추상메소드만 선언된 인터페이스

 

함수형 인터페이스에 max라는 추상메소드를 선언한다. 

@FunctionalInterface
interface MyFunction {
	public abstract int max(int a, int b);
}

 

익명클래스를 이용해서 객체를 생성하고 인터페이스인 MyFunction 타입의 참조변수에 담는다.

MyFunction f = new MyFunction() { // 익명클래스, 클래스의 선언과 객체생성 동시에.
			public int max(int a, int b) {
				return a>b? a:b;
			}
		};

MyFunction을 구현한 익명클래스로 생성한 객체는 max 메소드를 가지고 있으므로,

max 메소드를 사용해도 에러가 발생하지 않는다.

int value = f.max(3,5);  // OK. MyFunction에 max()가 있음

 

 

함수형 인터페이스 타입의 참조변수로 람다식을 참조할 수 있다.

(, 함수형 인터페이스의 메소드와 람다식의 매개변수 개수와 반환타입이 일치해야 함)

MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3,5); // 실제로는 람다식(익명 함수)이 호출됨

 

 

ex)

@FunctionalInterface // 단 하나의 추상메소드만 가져야 함.
interface MyFunction2 {
	int max (int a, int b);
}

public class Ex14_0 {

	public static void main(String[] args) {

//		MyFunction2 f = new MyFunction2() {
//			public int max(int a, int b) {
//				return a>b? a:b;
//			}
//		};
		
		// 람다식을 다루기 위한 참조변수의 타입은 함수형 인터페이스로 한다.
		MyFunction2 f = (a,b) -> a>b? a:b; // 람다식. 익명객체
		
		int value = f.max(3,5); // 함수형 인터페이스 필요
		System.out.println("value="+value);
		
	}

}

 

ex) 함수형 인터페이스 Comparator

import java.util.*;

public class Practice {
	
	public static void main(String[] args)  {
		List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd", "aaa");
		
//		Collections.sort(list, new Comparator<String>() {
//			public int compare(String s1, String s2) {
//				return s2.compareTo(s1);
//			}
//		});
		
		Collections.sort(list, (s1,s2) -> s2.compareTo(s1));
		// Comparator<String> c = (s1,s2) -> s2.compareTo(s1);
		
	}

}

 

 

 

함수형 인터페이스 타입의 매개변수, 반환타입

 

함수형 인터페이스 타입의 매개변수

@FunctionalInterface
interface MyFunction {
	void myMethod();
}
void aMethod(MyFunction f) {
	f.myMethod(); // MyFunction에 정의된 메소드 호출
}
MyFunction f = () -> System.out.println("myMethod()");
aMethod(f);

 

 

함수형 인터페이스 타입의 반환타입

MyFunction myMethod() {
	MyFunction f = () -> {};
	return f;
}

 

 

ex)

@FunctionalInterface
interface MyFunction {
	void run();	// public abstract void run();
}
public class Ex14_1 {
	static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메소드
		f.run();
	}
	
	static MyFunction getMyFunction() { // 반환타입이 MyFunction인 메소드
		MyFunction f = () -> System.out.println("f3.run()");
		return f;
	}
	public static void main(String[] args) {
		// 람다식으로 MyFunction의 run()을 구현
		MyFunction f1 = () -> System.out.println("f1.run()");
		
		MyFunction f2 = new MyFunction() {
			public void run() {
				System.out.println("f2.run()");
			}
		};
		
		MyFunction f3 = getMyFunction();
		
		f1.run();
		f2.run();
		f3.run();
		
		execute(f1);
		execute( ()-> System.out.println("run()"));
		

	}

}

 

 

 

java.util.function 패키지

자주 사용되는 다양한 함수형 인터페이스를 제공

* Predicate<T> 사용하는 방법

Predicate<String> isEmptyStr = s -> s.length()==0;
String a = "";

if(isEmptyStr.test(a)) // if(a.length()==0)
	System.out.println("This is an empty String.");

 

매개변수가 2개인 함수형 인터페이스

BiSupplier는 없음. 2개의 값을 반환할 수 없기 때문에.

* 매개변수 3개짜리 함수를 사용하려면 직접 만들어야 함

@FunctionalInterface
interface TriFunction<T,U,V,R> {
	R apply(T t, U u, V v);
}

 

 

매개변수의 타입과 반환타입이 일치하는 함수형 인터페이스

 

 

ex)

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class Ex14_2 {
	static <T> List<T> doSomething(Function<T, T> f, List<T> list) {
		List<T> newList = new ArrayList<T>(list.size());
		
		for(T i : list) {
			newList.add(f.apply(i));
		}
		
		return newList;
	}
	
	static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
		System.out.print("[");
		for(T i : list) {
			if(p.test(i)) // 짝수인지 검사
				c.accept(i); // i를 출력
		}
		System.out.println("]");
	}
	
	static <T> void makeRandomList(Supplier<T> s, List<T> list) {
		for (int i=0; i<10; i++) {
			list.add(s.get());
		}
	}
	public static void main(String[] args) {
		Supplier<Integer> s = ()->(int)(Math.random()*100)+1; // 1~100 정수를 얻는다.
		Consumer<Integer> c = i -> System.out.print(i+", "); // i를 출력
		Predicate<Integer> p = i -> i%2==0;	// 짝수인지 검사
		Function<Integer, Integer> f = i -> i/10 * 10; // i의 일의자리를 없앤다.
		
		List<Integer> list = new ArrayList<>();
		makeRandomList(s, list);	// list를 랜덤값으로 채운다.
		System.out.println(list);
		printEvenNum(p, c, list);	// list중 짝수만 출력
		List<Integer> newList = doSomething(f, list);
		System.out.println(newList);	
		
	}

}

 

 

Predicate의 결합

and(), or(), negate()로 두 Predicate를 하나로 결합(default메소드)

( and - && / or - || / negate - ! )

 
Predicate<Integer> p = i -> i<100;
Predicate<Integer> q = i -> i<200;
Predicate<Integer> r = i -> i%2 == 0;

Predicate<Integer> notP = p.negate();	// i >= 100
Predicate<Integer> all = notP.and(q).or(r); // 100 <= i && i <200 || i%2==0
Predicate<Integer> all2 = notP.and(q.or(r)); // 100 <= I && (i < 200 || i%2==0)

System.out.println(all.test(2));  // true
System.out.println(all2.test(2)); // false

 

 

등가비교를 위한 Predicate의 작성에는 isEqual()를 사용(static메소드)

Predicate<String> p = Predicate.isEqual(str1); // isEqual()은 static 메소드
Boolean result = p.test(str2); // str1과 str2가 같은지 비교한 결과를 반환


Boolean result = Predicate.isEqual(str1).test(str2); // 위의 두 줄을 한 줄로 작성

 

 

ex)

import java.util.function.*;

public class Ex14_3 {

	public static void main(String[] args) {
		Function<String, Integer> f = (s) -> Integer.parseInt(s,16); // s를 16진수로
		Function<Integer,String> g = (i) -> Integer.toBinaryString(i); // 2진수 문자열로 바꿔줌
		
		Function<String, String> h = f.andThen(g); // f 한 후에 g를 이어서 함
		Function<Integer, Integer> h2 = f.compose(g); // g를 한 후에 f를 이어서 함
		
		System.out.println(h.apply("FF")); // "FF" -> 255 -> "11111111"
		System.out.println(h2.apply(2)); // 2 -> "10" -> 16
		
		Function<String, String> f2 = x -> x; // 항등 함수(identity function)
		System.out.println(f2.apply("AAA")); // AAA가 그대로 출력됨
		
		Predicate<Integer> p = i -> i<100;
		Predicate<Integer> q = i -> i<200;
		Predicate<Integer> r = i -> i%2 == 0;
		Predicate<Integer> notP = p.negate(); // i>=100
		
		Predicate<Integer> all = notP.and(q.or(r));
		System.out.println(all.test(150)); // true
		
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		// str1과 str2가 같은지 비교한 결과를 반환
		Predicate<String> p2 = Predicate.isEqual(str1);
		// boolean result = str1.equals(str2);
		boolean result = p2.test(str2);
		// boolean result = Predicate.isEqual(str1).equals(str2);
		System.out.println(result);
		
	}

}

 

 

 

컬렉션 프레임웍과 함수형 인터페이스

함수형 인터페이스를 사용하는 컬렉션 프레임웍의 메소드(와일드카드 생략)

list.forEach(i->System.out.print(i+","));   // list의 모든 요소를 출력
list.removeIf(x-> x%2==0 || x%3==0);	// 2 또는 3의 배수를 제거
list.replaceAll(i->i*10);	// 모든 요소에 10을 곱한다.

// map의 모든 요소를 {k,v}의 형식으로 출력
map.forEach((k,v)-> System.out.print("{"+k+","+v+"},"));

 

 

ex)

import java.util.*;

public class Ex14_4 {
	
	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<>();
		for(int i=0; i<10; i++)
			list.add(i);
		
		// list의 모든 요소를 출력
		list.forEach(i->System.out.print(i+","));
		System.out.println();
		
		// list에서 2 또는 3의 배수를 제거한다.
		list.removeIf(x-> x%2==0 || x%3==0);
		System.out.println(list);
		
		list.replaceAll(i->i*10); // list의 각 요소에 10을 곱한다.
		System.out.println(list);
		
		Map<String, String> map = new HashMap<>();
		map.put("1", "1");
		map.put("2", "2");
		map.put("3", "3");
		map.put("4", "4");
		
		// map의 모든 요소를 {k,v}의 형식으로 출력한다.
		map.forEach((k,v)->System.out.print("{"+k+","+v+"},"));
		System.out.println();
		
	}
}

 

 

 

메소드 참조(method reference)

하나의 메소드만 호출하는 람다식은 메소드 참조로 간단히 할 수 있다.

(형식은 클래스이름::메소드이름;)

 

static 메소드 참조

Integer method(String s) { // 단지 Integer.parseInt(String s) 하나만 호출함
	return Integer.parseInt(s);
}

 

람다식으로 바꾸면

Function<String, Integer> f = (String s) -> Integer.parseInt(s);

 

제네릭에서 알 수 있듯이 입력값이 String임을 알 수 있으므로 생략이 가능함.

Function<String, Integer> f = Integer::parseInt;	// 메소드 참조

 

생성자와 메소드 참조

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, int[]> f = x -> new int[x]; // 람다식
↕
Function<Integer, int[]> f2 = int[]::new;	// 메소드 참조

 

 

ex)

import java.util.Arrays;
import java.util.function.Function;
import java.util.function.Supplier;

class MyClass {
	int iv;
	
	MyClass(int iv) {
		this.iv=iv;
	}
	
}

class MyClass2 {}

public class Practice {
	
	public static void main(String[] args)  {
		// Supplier는 입력X, 출력O
//		Supplier<MyClass2> s = () -> new MyClass2();
		Supplier<MyClass2> s = MyClass2::new;
		
//		Function<Integer, MyClass> s = i -> new MyClass(i);
		Function<Integer, MyClass> f = MyClass::new;
		
		MyClass mc = f.apply(200);
		System.out.println(f.apply(100).iv);
		
//		Function<Integer, int[]> f2 = i -> new int[i];
		Function<Integer, Integer[]> f2 = Integer[]::new;
		
		Integer[] arr = f2.apply(5);
		System.out.println(arr.length);
	}

}

출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편] 

+ Recent posts