int[] arr = new int[5];
Arrays.setAll(arr, (i) -> (int) (Math.random() * 5)+1);
💡 method와 function 차이
객체지향개념에서는 함수 대신 객체의 행위나 동작을 의미하는 메서드라는 용어를 사용한다. 메서드는 함수와 같은 의미지만, 특정 클래스에 반드시 속해야 한다는 제약이 있기 때문에 메서드 라는 용어를 사용한다. 그러나 이제 람다식을 통해 메서드가 하나의 독립적인 기능을 하기 때문에 함수라는 용어를 사용하게 되었다.
int max(int a, int b) {
return a > b ? a : b;
}
// 람다식으로 변환
(int a, int b) -> {
return a > b ? a : b;
}
// return 문에 '식(expression)'으로 대신 할 수 있음
// 식의 연산 결과가 자동적으로 반환값이 됨
// 문장이 아닌 식이므로 ; 붙이지 않음
(int a, int b) -> a > b ? a : b
// int 생략 가능
(a, b) -> a > b ? a : b
@FuctionalInterface // 컴파일러가 함수형 인터페이스를 올바르게 정의 했는지 확인
interface MyFunction {
public abstract int max(int a, int b);
}
MyFunction f = new MyFunction() {
public int max(int a, int b) {
return a > b ? a : b?
}
}
MyFunction f = (int a, int b) -> a > b ? a : b;
📌 대체 가능한 이유는?
람다식도 실제로는 익명 객체이고, MyFunction 인터페이스를 구현한 익명 객체의 메서드 max()와 람다식의 매개변수의 타입과 개수 그리고 반환값이 일치하기 때문이다.
Collections.sort 람다식 예
List<String> list = Arrays.asList("abc", "aaa", "bbb", "ddd","aaa");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
Intellij 에서는 Lambda 로 변경하라는 가이드가 나온다.
Replace with lambda 를 눌러주면 아래와 같이 변환된다.
Collections.sort(list, (o1, o2) -> o2.compareTo(o1));
// Intellij는 위의 코드를 아래와 같이 변경 추천
// Collections.sort(list, Comparator.reverseOrder());
@FuctionalInterface // 컴파일러가 함수형 인터페이스를 올바르게 정의 했는지 확인
interface MyFunction {
void run();
}
class LambdaEx1 {
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 f1 = () -> System.out.println("f1.run()");
// 익명클래스로 구현
MyFunction f2 = new MyFunction() {
pubic void run() { // 반드시 public
System.out.println("f2.run()");
}
}
MyFunction f3 = getMyFunction();
f1.run();
f2.run();
f3.run();
execute(f1);
execute( () -> System.out.println("run()") );
}
}
실행결과
f1.run()
f2.run()
f3.run()
f1.run()
run()
함수형 인터페이스 | 메서드 | 설명 |
java.lang.Runnable | void run() | 매개변수 없고, 반환값도 없음 |
Supplier<T> | T get() → | 매개변수 없고, 반환값만 있음 |
Consumer<T> | → void accept(T t) | Supplier와 반대로 매개변수만 있고 반환값 없음 |
Function<T,R> | → R apply(T t) → | 일반적인 함수. 하나의 매개변수를 받아서 결과를 반환 |
Predicate<T> | → boolean test(T t) → | 조건식 표현에 사용. 매개변수는 하나. 반환타입 boolean |
💡 수학에서 결과로 true/false를 반환하는 함수를 predicate 라고 한다.
함수형 인터페이스 | 메서드 | 설명 |
BiConsumer<T,U> | → R accept(T t, U u) | 두 개의 매개변수만 있고 반환값 없음 |
BiPredicate<T,U> | → R test(T t, U u) → | 조건식 표현에 사용. 매개변수는 둘. 반환값 boolean |
BiFunction<T,U,R> | → R apply(T t, U u) → | 두 개의 매개변수만 있고 반환값 없음 |
@FunctionalInterface
interface TriFunction<T,U,V,R> {
R apply(T t, U u, V v);
}
함수형 인터페이스 | 메서드 | 설명 |
Collection | boolean removeIf(Predicate filter) | 조건에 맞는 요소를 삭제 |
List | void replaceAll(UnaryOperator operator) | 모든 요소를 변환하여 대체 |
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> actioin) | 모든 요소에 작업 action 수행 | |
void replaceAll(BiFunction<K,V,V> f) | 모든 요소에 치환 작업 f 수행 |
사용 예
list.forEach(i->System.out.println(i));
list.removeIf(x-> x%2 ==0);
list.replaceAll(i->i*10);
map.forEach((k, v) -> System.out.println(k, v));
함수형 인터페이스 | 메서드 | 설명 |
DoubleToIntFunction | → int applyAsInt(double d) → | AToBFunction은 입력=A타입, 출력=B타입 |
DoubleToIntFunction | → int applyAsInt(T value) → | ToBFunction은 출력=B타입, 입력=Generic 타입 |
intFunction | → R apply(T t, U u) → | AFunction은 입력=A타입, 출력=Generci타입 |
ObjIntConsumer<T> | → void accept(T t, U u) → | ObjAFunction은 입력=T,A타입, 출력은 없음 |
class LambdaEx7 {
public static void main(String[] args) {
Function<String, Integer> f = (s -> Integer.parseInt(s , 16));
Function<Integer, String > g = (i -> Integer.toBinaryString(i));
Function<String, String> h = f.andThen(g);
Function<Integer, Integer> h2 = f.compose(g);
System.out.println(h.apply("FF")); // "FF" -> 255 -> "11111111"
System.out.println(h2.apply(2)); // 2 -> "10" -> 16
Function<String, String> f2 = x -> x;
System.out.println(f2.apply("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 = "abc";
String str2 = "abc";
// str1과 str2가 같은지 비교한 결과를 변환
Predicate<String> p2 = Predicate.isEqual(str1);
boolean result = p2.test(str2);
System.out.println(result);
}
}
람다식이 하나의 메서드만 호출하는 경우에 '메서드 참조(mehotd reference)'라는 방법으로 람다식을 더욱 간결하게 표현할 수 있다.
Function<String, Integer> f = (String s) -> Integer.parseIn(s);
↓
Function<String, Integer> f = Integer::parseInt; // 메서드 참조
BiFunction<String, String, Boolean> f2 = (s1, s2) -> sq.equals(s2);
↓
BiFunction<String, String, Boolean> f2 = String::equals; // 메서드 참조
참조변수 f2의 타입만 봐도 람다식이 두 개의 String 타입의 파라미터를 받는다는 것을 알 수 있으므로 람다식의 파라미터들은 없어도 된다.