[Java][14-1] 람다식
1.1 람다식이란?
람다식(Lambda expression) : 메서드를 하나의 식(expression)으로 표현한 것
== 익명함수(anonymous fuction) : 메서드의 이름값과 반환값이 없어진다
Arrays.setAll(arr, (i) → (int)(Math.random()*5)+1);
1.2 람다식 작성하기
메서드에서 메서드 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 ->
를 추가한다.
// 메서드 max
int max (int a, int b) {
return a>b ? a : b;
}
// 람다식
(a,b) → a>b ? a : b // {}안의 문장이 1개인 경우 {} 생략 가능
반환 값이 있을 경우, return 문 대신 expression(식) 으로 대신 할 수 있다.
식으로 대신할때는 ; 을 붙이지 않는다.
또한, 매개변수의 타입은 추론이 가능한 경우 생략할 수 있다.
// 여러 매개변수 중 한개만 생략 불가능
(int a, int b) -> a+b
// (int a, b) -> a+b // 불가능
(a,b) -> a+b // 가능
// 매개변수 1개, 괄호 생략 가능
(int f) -> System.out.pritln(f)
f -> System.out.println(f) // 괄호 생략 가능
// int f -> System.out.println(f) // 타입이 있을시 생략 불가능
하지만 괄호안에 문장이 return 문일 경우 {} 는 생략할 수 없다.
// 익명클래스
int sumArr(int[] arr) {
int sum = 0;
for (int i : arr)
sum+=i;
return sum;
}
// 람다식
(int[] arr) → {
int sum =0;
for(int i : arr)
sum+=i;
return sum; // return 문은 생략 불가능
}
1.3 함수형 인터페이스 (Functional Interface)
🤔 함수형 인터페이스란?
함수형 인터페이스 : 람다식을 다루기 위한 인터페이스
(단, 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야 함)
(static 메서드, default 메서드 제약 x)
ex) Comparator 비교 클래스를 람다식으로 구현 시 더 간단히 구현할 수 있다.
메서드로 표현
List<String> list = Arrays.asList(”abc”, “aaa”, “bbb”, “ddd”, “aaa”);
Collections.sort(list, new Comparator<String>() {
public int compere(String s1, String s2) {
return s2.compareTo(s1);
}
});
람다식으로 구현
List<String> list = Arrays.asList(”abc”, “aaa”, “bbb”, “ddd”, “aaa”);
Collections.sort(list, (s1,s2) → s2.compareTo(s1));
1) 함수형 인터페이스 타입의 매개변수와 반환타입
@FunctionalInterface
interface MyFunction {
void myMethod(); //추상 메서드
}
1-1) 매개변수
aMethod : 매개변수 MyFunction을 받는 메서드
방법 (1) 참조변수 o
void aMethod(MyFunction f) {
f.myMethod();
}
MyFunction f = () → System.out.println(”use reference variable”);
aMethod(f);
방법(2) 참조변수 x
void aMethod(MyFunction f) {
f.myMethod();
}
aMethod( () → System.out.println(”no use reference variable”));
1-2) 반환타입
MyFunction myMethod() {
MyFunction f = () → {}; // return () → {};
return f;
}
예제
@FunctionalInterface
interface MyFunction {
void run(); // public abstract 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의 run()을 구현
MyFunction f1 = ()-> System.out.println("f1.run()");
f1.run();
// 익명클래스로 run()을 구현
MyFunction f2 = new MyFunction() {
public void run() { // public을 반드시 붙여야 함
System.out.println("f2.run()");
}
};
f2.run();
// 참조변수 유무
execute(f1); // 참조변수 f1으로 구현
execute( ()-> System.out.println("run()") ); // 참조변수 없이 구현
// 반환타입이 함수형 인터페이스
MyFunction f3 = getMyFunction();
f3.run();
}
}
2) 람다식의 타입과 형변환
람다식의 타입 : 컴파일러가 임의로 정하기 때문에 알 수 없음
// 함수형 인터페이스(MyFunction)타입으로 형변환
MyFunction f1 = (MyFunction) () -> System.out.println("f1.run()");
// 형변환 생략가능
MyFunction f1 = ()-> System.out.println("f1.run()");
// 함수형 인터페이스로만 형변환가능
//Object obj = (Object)() -> System.out.println("f1.run()");
// 컴파일 에러 : must be a functional interface
예제
package Lambda;
@FunctionalInterface
interface MyFunction2 {
void myMethod(); // public abstract void myMethod();
}
class LambdaEx2 {
public static void main(String[] args) {
// Object 초기화 차이
Object obj1=null; // obj1 변수가 아직 실제로 참조하는 곳이 없다.
Object obj2= new Object(); // obj2 변수가 객체를 만들고, 참조값 저장했다.
System.out.println(obj1);
System.out.println(obj2);
MyFunction2 f = ()->{}; // MyFunction f = (MyFunction)(()->{});
Object obj = (MyFunction2)(()-> {}); // Object타입으로 형변환이 생략됨
String str = ((Object)(MyFunction2)(()-> {})).toString();
System.out.println(f); // 람다식 : 외부클래스이름$$Lambda$번호
System.out.println(obj);
System.out.println(str);
MyFunction2 f2 = new MyFunction2() { public void myMethod(){}};
System.out.println(f2); // 익명의 객체타입 외부클래스이름$번호
// System.out.println(()->{}); // 에러. 람다식은 Object타입으로 형변환 안됨
System.out.println((MyFunction2)(()-> {}));
// System.out.println((MyFunction2)(()-> {}).toString()); // 에러
System.out.println(((Object)(MyFunction2)(()-> {})).toString());
}
}
3) 외부 변수를 참조하는 람다식
package Lambda;
@FunctionalInterface
interface MyFunction3 {
void myMethod();
}
class Outer {
int value=10; // Outer.this.value
class Inner {
int value=20; //this.value
void method(int i) { // final int i jdk 1.8 부터 final 생략
//i = 20;
int value = 30;
MyFunction3 f = () -> {
System.out.println(" value :" + value);
System.out.println(" this.value :" + this.value);
System.out.println("Outer.this.value :" + Outer.this.value);
};
f.myMethod();
}
}
} // Outer클래스의 끝
public class LambdaEx3 {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.method(100);
}
}
1.4 java.util.function 패키지
일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 놓은 패키지
자주쓰이는 기본적인 함수형 인터페이스
1) 조건식의 표현에 사용되는 Predicate
Function의 변형
반환타입 : boolean
사용 : 조건식을 표현할때
Predicate<String> isEmptyStr = s -> s.length() == 0;
String s = "";
if(isEmptyStr.test(s))
System.out.println("This is an empty String.");
2) 매개변수가 두 개인 함수형 인터페이스
접두사 Bi 붙임
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
BiConsumer<T,U> | T,U → void accept(T t, U u) | 매개변수 2개,반환값 X |
BiPredicate<T,U> | T,U → boolean test(T t, U u) → boolean | |
BiFunction<T,U,R> | T,U → R apply(T t, U u) → R |
3개 이상의 매개변수 → 직접 만들어 써야함.
3) UnaryOperator와 BinaryOperator
매개변수와 반환값의 타입 일치
4) 컬렉션 프레임웍과 함수형 인터페이스
ex)
compute : Map의 value 변환
merge : Map 병합
`java.util.ArrayList.forEach`
/**
* @throws NullPointerException {@inheritDoc}
*/
@Override
public void forEach(Consumer<? super E> action) { // 함수형 인터페이스(Consumer) action 을 매개변수로 받음
Objects.requireNonNull(action); // null 이면 npe 반환
final int expectedModCount = modCount;
final Object[] es = elementData;
final int size = this.size;
for (int i = 0; modCount == expectedModCount && i < size; i++)
action.accept(elementAt(es, i)); // action을 index마다 반복
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
// list의 모든 요소를 출력
// 람다식
list.forEach(i->System.out.print(i+","));
System.out.println();
// 익명 객체로 표현
...
MyFunction4 f = new MyFunction4() {
public void run(Integer i) {
System.out.print(i+",");
}
};
forEach2(list,f);
System.out.println();
...
public static void forEach2(ArrayList<Integer> list, MyFunction4 action) {
for (int i = 0; i < list.size(); i++)
action.run(list.get(i));
}
`java.util.ArrayList.removeIf`
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
int removeCount = 0;
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
// shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
final int newSize = size - removeCount;
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
return anyToRemove;
}
// A tiny bit set implementation
private static long[] nBits(int n) {
return new long[((n - 1) >> 6) + 1];
}
private static void setBit(long[] bits, int i) {
bits[i >> 6] |= 1L << i;
}
private static boolean isClear(long[] bits, int i) {
return (bits[i >> 6] & (1L << i)) == 0;
}
/**
* @throws NullPointerException {@inheritDoc}
*/
@Override
public boolean removeIf(Predicate<? super E> filter) {
return removeIf(filter, 0, size);
}
/**
* Removes all elements satisfying the given predicate, from index
* i (inclusive) to index end (exclusive).
*/
boolean removeIf(Predicate<? super E> filter, int i, final int end) {
Objects.requireNonNull(filter);
int expectedModCount = modCount;
final Object[] es = elementData;
// Optimize for initial run of survivors
for (; i < end && !filter.test(elementAt(es, i)); i++)
;
// Tolerate predicates that reentrantly access the collection for
// read (but writers still get CME), so traverse once to find
// elements to delete, a second pass to physically expunge.
if (i < end) {
final int beg = i;
final long[] deathRow = nBits(end - beg);
deathRow[0] = 1L; // set bit 0
for (i = beg + 1; i < end; i++)
if (filter.test(elementAt(es, i)))
setBit(deathRow, i - beg);
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount++;
int w = beg;
for (i = beg; i < end; i++)
if (isClear(deathRow, i - beg))
es[w++] = es[i];
shiftTailOverGap(es, w, end);
return true;
} else {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return false;
}
}
`java.util.ArrayList.replaceAll`
@Override
public void replaceAll(UnaryOperator<E> operator) {
replaceAllRange(operator, 0, size);
modCount++;
}
private void replaceAllRange(UnaryOperator<E> operator, int i, int end) {
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final Object[] es = elementData;
for (; modCount == expectedModCount && i < end; i++)
es[i] = operator.apply(elementAt(es, i));
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
`java.util.Map.forEach`
/**
* Performs the given action for each entry in this map until all entries
* have been processed or the action throws an exception. Unless
* otherwise specified by the implementing class, actions are performed in
* the order of entry set iteration (if an iteration order is specified.)
* Exceptions thrown by the action are relayed to the caller.
*
* @implSpec
* The default implementation is equivalent to, for this {@code map}:
* <pre> {@code
* for (Map.Entry<K, V> entry : map.entrySet())
* action.accept(entry.getKey(), entry.getValue());
* }</pre>
*
* The default implementation makes no guarantees about synchronization
* or atomicity properties of this method. Any implementation providing
* atomicity guarantees must override this method and document its
* concurrency properties.
*
* @param action The action to be performed for each entry
* @throws NullPointerException if the specified action is null
* @throws ConcurrentModificationException if an entry is found to be
* removed during iteration
* @since 1.8
*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
5) 기본형을 사용하는 함수형 인터페이스
//DoubleToIntFunction<Double , Int, boolean> →
UnaryOperator : 매개변수,반환값 같은 함수형 인터페이스
IntUnaryOperator : 매개변수,반환값이 Int 인 함수형 인터페이스
IntToIntFunction 은 없음, IntUnaryOperator 로 가능
1.5 Fuction의 합성과 Predicate의 결합
1) Function의 합성
1-1) addThen
f h
f.addThen(h)
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
1-2) compose
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
1-3) identity
x→x
잘 사용하지 않음, map()
으로 변환작업할 때 변환없이 그대로 처리하고자 할때 사용됨
2) Predicate의 결합
2-1) and
f.and(x>5)
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
2-2) or()
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
2-3) negate() not(!)
default Predicate<T> negate() {
return (t) -> !test(t);
}
2-4) isEqual
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull //메서드 참조
: object -> targetRef.equals(object);
}
예제)
package Lambda;
import java.util.function.*;
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; // 항등 함수(identity function)
Function<String, String> f3 = Function.identity();
System.out.println(f2.apply("AAA")); // AAA가 그대로 출력됨
System.out.println(f3.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 = "abc";
String str2 = "abc";
// str1과 str2가 같은지 비교한 결과를 반환
Predicate<String> p2 = Predicate.isEqual(str1);
boolean result = p2.test(str2);
System.out.println(result);
}
}
1.6 메서드 참조
클래스이름::메서드이름
or 참조변수::메서드이름
1) static 메서드 참조
// 문자열 -> 정수
Function<String, Integer> f = (String s) -> Integer.parseInt(s);
// 메서드 참조
Function<String, Integer> f = Integer::parseInt;
2) 인스턴스메서드 참조
// 문자열 비교
BiFunction<String, String, Boolean> f = (s1, s2) -> s1.equals(s2);
// 메서드 참조
BiFunction<String, String, Boolean> f = String::equals;
3) 특정 객체 인스턴스 메서드 참조
// 이미 생성된 객체의 메서드
MyClass obj = new MyClass();
Function<String, Boolean> f = (x) -> obj.equals(x);
//메서드 참조
Function<String, Boolean> f = obj::equals;
생성자의 메서드 참조
// 매개변수 x 생성자
Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
// 매개변수 O 생성자
Function<Integer, MyClass> f = (i) -> new MyClass(i);
Function<Integer, MyClass> f2 = MyClass::new;
BiFunction<Integer, String, MyClass> bf = (i, s) -> new MyClass(i, s);
BiFunction<Integer, String, MyClass> bf2 = MyClass::new;
class Card {
int num;
String shape;
MyClass(int num, String shape){
this.num=num;
this.shape=shape;
}
}
...
MyClass f = new Myclass(10,"spade");
Function<Integer, String, Card> f = Card::new;
f(10,"spade");
int[] makeArray(int x){
int[] arr = new int[x];
return arr;
}
...
makeArray f = new makeArray(x);
//int[] a = new int[x];
Function<Integer, int[]> f = x -> new int[x];
Function<Integer, int[]> f = int[]::new;