: 함수(메서드)를 간단한 식(expression)으로 표현하는 방법
람다식은 익명 함수(이름이 없는 함수)
int max(int a, int b) {
return a > b ? a: b;
}
람다식으로 표현🔽
(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
매개변수의 타입이 추론 가능하면 생략 가능(대부분 생략 가능)
(a, b) -> a > b ? a : b
매개변수가 하나인 경우 괄호 생략 가능
a -> a * a
블록 안의 문장이 하나 뿐일 때 중괄호 생략 가능(;
도 생략)
i -> System.out.println(i)
(int a, int b) -> {
return a > b ? a : b;
}
람다식🔽
(a, b) -> a > b ? a : b
int square(int x) {
return x * x;
}
람다식🔽
x -> x * x
int roll() {
return (int)(Math.random() * 6);
}
람다식🔽
() -> (int)(Math.random() * 6)
매개변수 없을 때 괄호만 써준다. 괄호 생략 불가
람다식은 익명 함수가 아니라 익명 객체이다.
람다식은 메서드를 간단하게 표현한 식이다. 그런데 자바는 메서드만 단독으로 쓰일 수 없고 항상 클래스 안에 있어야 하기 때문에 익명 객체로 람다식을 감싼 것이고 따라서 람다식을 익명 개체라고 하는 것이다.
(a, b) -> a > b ? a : b
// 익명 클래스
new Object() {
int max(int a, int b) {
return a > b ? a : b ;
}
}
// 참조변수에 대입
Object obj = new Object() {
int max(int a, int b) {
return a > b ? a : b ;
}
};
...
int value = obj.max(3,5); // 에러
단 하나의 추상 메서드만 선언된 인터페이스
@FunctionalInterface
interface MyFunction {
int max(int a, int b);
}
public class Ex14_0 {
public static void main(String[] args) {
MyFunction f = (a, b) -> a > b ? a : b;
int value = f.max(3, 5);
System.out.println(value);
}
}
5
@FunctionalInterface
: 함수형 인터페이스라는 것을 알리는 애너테이션. 이 애너테이션이 붙은 인터페이스에 2개 이상의 추상 메서드를 작성하면 에러가 난다. -> 하나의 추상 메서드만 작성 가능익명 객체를 람다식으로 대체
List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
람다식으로 대체🔽
Collections.sort(list,(s1, s2) -> s2.compareTo(s1));
Comparator<T>
도 compare(T o1, T o2)
추상 메서드 하나만을 가진 함수형 인터페이스이다.@FunctionalInterface
interface MyFunction {
void myMethod() ;
}
...
void aMethod(MyFunction f) {
f.myMethod();
}
...
Myfunction f = () -> System.out.println("myMethod()");
aMethod(f);
함수형 인터페이스 타입의 매개변수는 매개변수로 람다식을 넣는다. 그럼 이 메서드가 람다식을 호출하는 것이다.
aMethod(() -> System.out.println("myMEthod()"));
한 줄로 줄일 수 있다.
MyFunction getMyFunction() {
return () -> System.out.println("myMethod");
}
@FunctionalInterface
interface MyFunction {
void run(); // public abstract void run();
}
public class Ex14_1 {
static void execute(MyFunction f) {
f.run();
}
static MyFunction getMyFunction() {
return () -> System.out.println("f3.run()");
}
public static void main(String[] args) {
MyFunction f1 = () -> System.out.println("f1.run()");
MyFunction f2 = getMyFunction(); // 함수형 인터페이스 타입의 반환타입
f1.run();
f2.run();
execute(f1); // 함수형 인터페이스 타입의 매개변수
execute(() -> System.out.println("run()"));
}
}
f1.run()
f2.run()
f1.run()
run()
execute(f1);
: 매개변수가 함수형 인터페이스 타입인 메서드 excute()
는 매개변수로 람다식이 들어간다.: 자주 사용되는 다양한 함수형 인터페이스를 제공하는 패키지이다.
-> 람다식을 쓸 때마다 새로운 함수형 인터페이스를 정의하는 것보다 이 패키지 안에 있는 이미 만들어진 함수형 인터페이스를 사용하는 것이 재사용성 / 유지보수 측면에서 좋고 표준화가 된다는 장점이 있다.
빈칸에 들어갈 알맞은 함수형 인터페이스는?
( ① ) f = () -> (int)(Math.random()*100) + 1;
A. ① : Supplier<Integer>
( ② ) f = i -> System.out.print(i + ", ");
A. ② : Consumer<Integer>
( ③ ) f = i -> i % 2 == 0;
A. ③ : Predicate<Integer>
i % 2 == 0
이라는 조건식을 반환하기 때문에( ④ ) f = i -> i / 10 * 10;
A. ④ : Function<Integer, Integer>
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
identity()
: 항등함수라고 한다. 입력값 t를 입력하면 그대로 t가 반환되는 함수이다.import java.util.function.*;
import java.util.*;
class Ex14_2 {
public static void main(String[] args) {
Supplier<Integer> s = () -> (int) (Math.random() * 100) + 1; // 난수 생성
Consumer<Integer> c = i -> System.out.print(i + ", "); // 입력값 출력
Predicate<Integer> p = i -> i % 2 == 0; // 짝수인지 검사
Function<Integer, Integer> f = i -> i / 10 * 10; // 일의 자리 버림
List<Integer> list = new ArrayList<>();
makeRandomList(s, list);
System.out.println(list);
printEvenNum(p, c, list);
List<Integer> newList = doSomething(f, list);
System.out.println(newList);
}
static <T> List<T> doSomething(Function<T, T> f, List<T> list) {
// list와 크기가 같은 newList를 새로 생성
List<T> newList = new ArrayList<T>(list.size());
// Function으로 일의 자리 버림한 수를 newList에 add
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) {
// Predicate로 짝수인지 검사하고 맞으면
if (p.test(i))
// Consumer로 짝수값 출력
c.accept(i);
}
System.out.println("]");
}
static <T> void makeRandomList(Supplier<T> s, List<T> list) {
// Supplier를 통해 생성된 난수 10개를 list에 저장
for (int i = 0; i < 10; i++) {
list.add(s.get());
}
}
}
makeRandomList(s, list);
: Supplier를 통해 생성된 난수 10개를 list에 저장
printEvenNum(p, c, list);
: Predicate로 list의 요소들을 짝수인지 검사하고 짝수가 맞으면 Consumer를 통해 출력
List<Integer> newList = doSomething(f, list);
: Function을 통해 list의 값들을 일의 자리 버림한 수들을 newList에 저장
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 0;
Predicate 결합🔽
Predicate<Integer> notP = p.ngeate();
Predicate<Integer> all = notP.and(q).or(r);
Predicate<Integer> all2 = notP.and(q.or(r));
notP = i >= 100
all = i >= 100 && i < 200 || i % 2 == 0
all2 = i >= 100 && (i < 200 || i % 2 == 0)
System.out.println(all.test(2));
System.out.println(all2.test(2));
true
false
all.test(2)
all2.test(2)
boolean result = Predicate.isEqual(str1).test(str2);
2개 이상의 함수를 하나로 연결할 때 사용
// 입력값 : String / 반환값 : Integer
Function<String, Integer> f = (s) -> Integer.parseInt(s, 16)
// 입력값 : Integer / 반환값 : String
Function<Integer, String> g = (i) -> Integer.toBinaryString(i)
Function<String, String> h = f.andThen(g); // 두 함수를 하나의 함수로 결합
System.out.println(h.apply("FF"));
Function<String, String> h = f.andThen(g)
h의 입력타입은 f의 입력타입과 같고 h의 반환타입은 g의 반환타입과 같다.
f의 반환타입과 g의 입력타입이 같아야 한다.
h.apply("FF")
-> Integer.parseInt(FF, 16)
: "FF"를 16진수로 변환 = 255
-> Integer.toBinaryString(255)
: 255를 2진수로 변환 = 11111111 출력
함수형 인터페이스를 사용하는 컬렉션 프레임워크의 메서드(와일드 카드 생략)
Interface | Method | 설명 |
---|---|---|
Collection | boolean removeIf(Predicate<E> filter) | 조건에 맞는 요소 삭제 |
List | void replaceAll(UnaryOperator<E> operator) | List 내 모든 요소 변환하여 대체 |
Iterable | void forEach(Consumer<T> action) | 모든 요소에 작업(action) 수행 |
Map | V compute(K key, BiFunction<K,V,V> f) | 지정된 키의 값에 작업(f) 수행 |
V computeIfAbsent(K key, Function<K,V> f) | 키가 없으면, 값에 작업(f) 수행 & 추가 | |
V computeIfPresent(K key, BiFunction<K,V,V> f) | 지정 키가 있으면, 값에 작업(f) 수행 | |
V merge(K key, V value, BiFunction<V,V,V> f) | 모든 요소에 병합 작업(f) 수행 | |
void forEach(BiConsumer<K,V> action) | 모든 요소에 작업(action) 수행 | |
void replaceAll(BiFunction<K,V,V> f) | 모든 요소에 치환작업(f) 수행 |
import java.util.*;
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의 모든 요소에 x10
list.replaceAll(i -> i * 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();
}
}
map.forEach((k, v) -> System.out.print("{" + k + "," + v + "},"));
: iterator() 반복자를 써서 컬렉션을 출력하는 대신 forEach() 메서드를 통해 코드를 간결하게 바꿀 수 있다.: 하나의 메서드만 호출하는 람다식은 "메서드 참조"로 더욱 간단히 할 수 있다.
종류 | 람다식 | 메서드 참조 |
---|---|---|
static 메서드 참조 | x -> ClassName.method(x) | CalssName::method |
인스턴스 메서드 참조 | (obj, x) -> obj.method(x) | CalssName::method |
Integer method(string s) {
return Integer.parseInt(s);
}
람다식🔽
Function<String, Integer> f = s -> Integer.parseInt(s);
메서드 참조🔽
Function<String, Integer> f = Integer::parseInt;
매개변수가 없는 생성자
// 생성자 람다식
Supplier<MyClass> s = () -> new MyClass();
메서드 참조🔽
Supplier<MyClass> s = MyClass::new;
매개변수가 있는 생성자
Function<Integer, MyClass> f = (i) -> new MyClass(i); //람다식
Function<Integer, MyClass> f = MyClass::new; // 메서드 참조
매개변수가 2개 이상인 생성자
// 매개변수가 2개 이상인 생성자
BiFunction<Integer, String, MyClass> bf = (i, s) -> new MyClass(i, s); // 람다식
BiFunction<Integer, String, MyClass> bf = MyClass::new; // 메서드 참조
Function<Integer, int[]> arrF = x -> new int[x]; // 람다식
Function<Integer, int[]> arrF = int[]::new; // 메서드 참조
// String 배열
Function<Integer, String[]> arrF2 = String[]::new ;
어떤 함수형 인터페이스를 사용할지 모를땐 입력값과 반환값의 유무를 잘 생각해보면 된다.