Java

[Java][14-1] 람다식

김yejin 2022. 8. 10. 22:07

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;