#chapter 3. 람다 표현식

 

-. 람다 표현식 : 메서드로 전달할 수 있는 익명함수를 단순화한 것

[example]

Comparator<Apple> byWeight = new Comparator<Apple>() {

    public int compare(Apple a1, Apple a2) {

        return a1.getWeight().compareTo(a2.getWeight());

    }

};

 

[람다 이용]

Comparator<Apple> byWeight = 

    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

        람다 파라미터   화살표              람다 바디

 

[람다 포멧]

-. (parameters) -> expression

-. (parameters) -> {expression;}

 

-. 함수형 인터페이스  : 정확히 하나의 추상메서드를 지정하는 인터페이스

public interface Comparator<T> {

    boolean test(T t);

}

 

?) 많은 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스다.

   디포트 메서드 : 인터페이스의 메서드를 구현하지 않은 클래스를

 

@FunctionalInterface 

해당 어노테이션을 쓰면 

 

???) 함수형 인터페이스로 뭘 할 수 있을까?

       람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터         페이스의 인스턴스로 취급

 

자바8 설계자들은 java.util.function.* 패키지로 여러가지 새로운 함수형 인터페이스를 제공. 

 

-. Predicate

test 라는 추상 메서드를 정의하며, test는 제네릭형식의 T의 객체를 인수로 받아 불리언을 반환한다.

[java.util.function.Predicate]

       @FunctionalInterface

       public interface Predicate<T> {

             boolean test(T t);

       }

[Predicate 예제]       

       public <T> List<T> filter(List<T> list, Predicate<T> p) {

             List<T> results = new ArrayList<>();

             for (T t:list) {

                    if(p.test(t)) {

                           results.add(t);

                    }

             }

             return results;

       }

       

       Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();

       List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

 

-. Comsumer

accept 라는 추상 메서드를 정의하며, accept는 제네릭형식의 T의 객체를 인수로 받아 void를 반환한다.

[java.util.function.Consumer]

       @FunctionalInterface

       public interface Consumer<T> {

             void accept(T t);

       }

[Comsumer 예제]

       public <T> void foreach(List<T> list, Consumer<T> c) {

             for (T t: list) {

                    c.accept(t);

             }

       }

       

       private void forEach() {

             Arrays.asList(1, 2, 3, 4, 5),

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

       }

 

-. Function

java.util.function.Function<T, R> 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R객체를 반환하는 추상메서드 apply를 정의한다.

[java.util.function.Function]

       @FunctionalInterface

       public interface Function<T, R> {

             R apply(T t);

       }

[Function 예제]

       public <T,R> List<R> map(List<T> list, Function<T, R> f) {

             List<R> result = new ArrayList<>();

             for (T t: list) {

                    result.add(f.apply(t));

             }      

             return result;

       }

       

       List<Integer> l = map(

                    Arrays.asList("lamdas", "in", "action"),

                    (String s) -> s.length()

       );

 

cf> java의 형식

 

     참조형 : Byte, Integer, Object, List

     기본형 : int, double, byte, char

 

-. 박싱         : 기본형 -> 참조형 으로 형 변환

-. 언박싱     : 참조형 -> 기본형 으로 형 변환

-. 오토박싱 : 박싱과 언박싱이 자유롭게 이뤄짐

 

람다로 함수형 인터페이스의 인스턴스를 만들수 있다.

 

[형식검사 문제] 다음 코드를 컴파일할 수 없는 이유는?

 

Object o = () -> { System.out.println("Eun example"); };

 

(solution)

람다 표현식의 콘텍스트 = Object(대상형식) 이다. 

하지만 Object는 함수형 인터페이스가 아니다.

따라서 ()-> void 형식의 함수 디스크립터를 갖는 Runnable로 대상 형식을 바꿔서 문제를 해결할 수 있다.

 

    sol) Object o = (Runnable) () -> { System.out.println("Eun example"); };

 

(추가) 예를 들어 execute (() -> {})라는 람다 표현식이 있다면 Runnable과 Action의 함수 디스크립터가 같으므로 누구를 가리키는지가 명확하지 않다.

 

public void excute(Runnable runnable) {

    runnable.run();

}

public void excute(Action<T> action) {

    action.act();

}

 

@FunctionlInterface

interface Action {

        void act();

}

 

    sol) execute ( (Action) () -> {})

 

-. 형식추론

자바 컴파일러는 람다표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.

[Comparator 객체 예제]

Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());    // 형식을 추론하지 않음.

Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());                // 형식을 추론함

=> 개발자 스스로 어떤 코드가 가독성을 향상 시킬 수 있는지 결정해야 한다.

 

-. 지역변수 사용

 

람다 캡쳐링 :  람다 표현식에서는 익명 함수가 하는 것처럼 자유변수를 활용할 수 있다.

    cf> 자유변수란) 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수

 

[Question]

int portNumber = 1337;

Runnable r = () -> System.out.println(portNumber);        // 컴파일 ERROR 발생

portNumber = 31337;

 

(solution)

2번째 줄에서 컴파일에러 발생,

    why?) 자유변수의 사용에도 약간의 제약이 있다.

 

지역변수는 final로 선언되어 있어야 한다.

 

final int portNumber = 1337;

Runnable r = () -> System.out.println(portNumber);

portNumber = 31337;                                        // 컴파일 ERROR 발생(final 변수를 수정했으므로)

 

 

[자유변수 사용에 제약이 생긴 이유?]

 

(저장공간)

인스턴스변수 => 힙

지역 변수    => 스택

 

람다에서 지역변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는

해당 변수에 접근하려 할 수 있다. 따라서 자바 구현에서는 원래 변수에 접근을 허용하는것이 아닌 자유 지역 변수의 복사본을 제공한다. 

따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에서는 한 번만 값을 할당해야 한다는 제약이 생긴것이다.

 

-. 메서드 참조

메서드 참조를 사용함으로써 가독성을 높일 수 있고, 간결하게 구현할 수 있다.

[예제]

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

inventory.sort(comparing(Apple::getWeight));

 => Apple클래스에 정의된 getWeight의 메서드 참조.

 

생성자, 배열 생성자, super 호출 등에 사용할 수 있는 특별한 형식의 메서드 참조도 있다.

 

1) 기존의 메서드 구현을 재활용해서 메서드 참조를 만드는 방법

[예제] List에 포함된 문자열을 대,소문자를 구분하지 않고 정렬하는 프로그램.

 

List<String> str = Arrays.asList("a", "b", "A", "B");

str.sort((s1, s2) -> s1.compareToIgnoreCase(s2));

str.sort(String::compareToIgnoreCase);

 

2) 생성자 참조 (ClassName::new)

Supplier<Apple> c1 = Apple::new;

Apple a1 = c1.get();                                                      // Supplier의 get메서드를 호출해서 새로운 Apple 객체를 만들 수 있다.

 

Function<Integer, Apple> c2 = Apple::new;              // (weight) -> new Apple(weight)

Apple a2 = c2.apply(110);                                            // Function의 get메서드를 호출해서 새로운 Apple 객체를 만들 수 있다. 

 

 

119 ~ 131 페이지 

생성자 참조~ 부터는 다음에 한번 더 보기! (이해가 잘 안감)

 

 

 

 

 

 

 

 

 

 

+ Recent posts