본문 바로가기
개발/JAVA

JAVA8 IN Action Chapter3 요점정리

by 밥버검 2018. 9. 16.
반응형

람다 표현식


메서드로 전달할 수 있는 익명 함수를 단순화한것

람다 표현식에는 이름은 없지만,파라미터 리스트,바디,반환 형식,발생할 수 있는 예외 리스트 가질 수 있음



람다(lambda) -> 람다 미적분학 학계에서 개발한 시스템에서 유래


람다 이용해서 간결한 방식으로 코드를 전달함


1
2
3
4
5
6
7
8
9
Comparator byWeight = new Comparator() {
   public int compare(Apple a1,Apple a2) {
         return a1.getWeight().compareTo(a2.getWeight());
   }
}  
 
Comparator byWeight = 
     (Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
      파라미터 리스트      -> 화살표      -> 람다의 바디
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(String s) -> s.length()
(Apple a) -> a.getWeight() > 150
(int x, int y) -> {
    System.out.println("Result:");
    System.out.println(x+y);
}
() -> 42 //파라미터 없으며 int 반환
(Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
() -> {} //파라미터가 없으며 void를 반환하는 람다 표현식
         //public void run() {} 처럼 바디가 없는 메서드
() -> "Reoul" //파라미터가 없으며 문자열을 반환하는 표현식
() -> {return "Mario";} //파라미터가 없으며 (명시적으로 return문을 이용해서) 문자열을 반환하는 표현식
(Integer i) -> return "Alan" + i;
//return은 흐름 제어문이다. (Integer i) -> {return "Alan" + i;}처럼 되어야 올바른 
//람다 표현식이다.
(String s) -> {"Iron Man";}
//"Iron Man"은 구문statement이 아니라 표현식 expression
//(String s) -> "Iron Man"처럼 되어야 올바른 람다 표현식
//(String s) -> {return "Iron Man";} 처럼 명시적 return문 사용
cs

어디에 어떻게 람다 사용


@FunctionalInterface 함수형 인터페이스를 가리키는 어노테이션


실행 어라운드 패턴 excute around patterm (실제 자원을 처리하는 코드를 설정과 정리 두과정이 둘러싸는 형태)


함수형 인터페이스,형식 추론


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

자바 API의 함수형 인터페이스 Comparator,Runnable


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//java.util.Comparator
public interface Comparator {
     int compare(T o1,T o2);
}
 
//java.lang.Runnable
public interface Runnable {
    void run();
}
 
//java.awt.event.ActionListener
public interface ActionListener extends EventListener {
    void actionPerformed(ActionEvent e);
}
 
//java.util.concurrent.Callable
public interface Callable {
    V call();
}
 
//java.security.PrivilegedAction
public interface PrivilegedAction {
    T run();
}
cs


Predicate

java.util.function.Predicate<T> 인터페이스는 test라는 추상 메서드를 정의 

test는 제네릭 형식 T의 객체를 인수로 받아 불린을 반환



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
 
 
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
    List<T> results = new ArrayList<>();
    for(T s: list) {
        if(p.test(s)) {
            results.add(s);
        }
    }
    return results;
}
 
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
cs


Consumer

java.util.function.Consumer<T> 인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의

T 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용함 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
 
public static <T> void forEach(List<T> list, Consumer<T> c) {
    for(T i: list) {
        c.accept(i);
    }
}
 
//Consumer의 accept 메서드 구현 람다
forEach(
    Arrays.asList(1,2,3,4,5),
    (Integer i) -> System.out.println(i)
);
cs


Function

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

입력을 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스를 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
public static <T,R> List<R> map(List<T> list,
                                    Function<T, R> f) {
    List<R> result = new ArrayList<>();
    for (T s : list) {
        result.add(f.apply(s));
    }
    return result;
}
// [7, 2, 6]
List<Integer> l = map(
                    Arrays.asList("lambdas","in","action"),
                    (String s) -> s.length());   //Function의 apply메서드를 구현하는 람다
cs



자바에서는 기본형을 참조형으로 변환할 수 있는기능 제공 이기능은 박싱(boxing)

참조형을 기본형으로 변환하는 반대 동작을 언박싱(unboxing)


람다와 함수형 인터페이스 


 사용 예시

람다 예시 

대응하는 함수형 인터페이스 

 불린 표현

(List<String> list) -> list.isEmpty() 

Predicate<List<String>> 

 객체 생성

() -> new Apple(10) 

Supplier<Apple> 

 객체에서 소비

(Apple a) -> System.out.println(a.getWeight()) 

Consumer<Apple> 

 객체에서 선택/추출

(String s) -> s.length() 

Function<String,Integer> 또는 ToIntFunction<String> 

 두 값 조합

(int a, int b) -> a * b

IntBinaryOperator 

 두 객체 비교
 

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

Comparator<Apple> 또는 BiFunction<Apple,Apple,Integer> 또는 ToIntBiFunction<Apple,Apple> 


대상형식 : 람다가 전달될 메서드 파라미터나 람다가 할당되는 변수 등)에서 기대되는 람다 표현식의 형식



지역 변수 사용 

자유 변수 (free variable) 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수 활용 가능

람다 캡처링 (capturing lambda)


//에러 : 람다에서 참고하는 지역 변수는 final로 선언 


1
2
3
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;
cs



인스턴스 변수 = 힙에 저장

지역변수 = 스택 



메서드 레퍼런스 : 기존의 메서드 정의를 재활용해서 람다처럼 전달 가능 , 람다 표현식보다 메서드 레퍼런스를 사용하는 것이 더 가독성이 더좋다


메서드 레퍼런스는 메서드명 앞에 구분자 :: 붙이는 방식 


메서드 레퍼런스를 만드는 방법

세가지 유형

1.정적 메서드 레퍼런스

-> ex) Integer의 parseInt 메서드는 Integer::parseInt로 표현


2.다양한 형식의 인스턴스 메서드 레퍼런스

-> ex) String의 length 메서드는 String::length로 표현할수 있다.


3.기존 객체의 인스턴스 메서드 레퍼런스



예제


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//메서드 레퍼런스
//1.
Function<String, Integer> stringToInteger =
    (String s) -> Integer.parseInt(s);
 
//-> 1.결과
Function<String, Integer> stringToInteger = Integer::parseInt;
 
//2.
BiPredicate<List<String>String> contains = 
    (list, element) -> list.contains(element);
 
//-> 2.결과
BiPredicate<List<String>String> contains = List::contains;
 
cs



생성자 레퍼런스

ClassName::new 처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 레퍼런스를 만들 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
Supplier<Applie> c1 = Apple::new //디폴트 생성자 Apple()의 생성자 레퍼런스
Apple a1 = c1.get(); //Supplier의 get 메서드 호출 새로운 Apple 객체를 만들수 있음
 
//이거랑 같음
Supplier<Apple> c1 = () -> new Apple(); //람다 표현식은 디폴트 생성자를 가진 Apple을 만듬
Apple a1 = c1.get(); //Supplier의 get 메서드를 호출 -> 새로운 Apple 객체를 만들 수 있음
 
//이렇게도 구현 가능 Function 인터페이스의 시그너처 비슷하게
Function<Integer, Apple> c2 = Apple::new//Apple(Integer weight)의 생성자 레퍼런스
Apple a2 = c2.apply(110); //Function의 apply 메서드를 무게를 인수로 호출해서 새로운 Apple 객체를 만들수 있음
 
 
Function<Integer, Apple> c2 = (weight) -> new Apple(weight); //특정 무게의 사과를 만드는 람다 표현식
Apple a2 = c2.apply(110); //Function의 apply 메서드를 무게를 인수로 호출해서 새로운 Apple 객체를 만들 수 있다.
cs



Predicate 조합

Predicate 인터페이스는 복잡한 프레디게이트를 만들 수 있도록 negate,and,or 세가지 메서드 제공

1
2
3
4
//'빨간색이면서 무거운(150그램 이상) 사과 또는 그냥 녹색 사과'
Predicate<Apple> redAndHeavyAppleOrGreen =
    redApple.and(a -> a.getWeight() > 150)
        .or(a -> "green".equals(a.getColr())); //프레디게이트 메서드를 연결해서 더 복잡한 프레디게이트 객체를 만듬
cs



Function 조합

Function 인터페이스는 Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메서드를 제공

andThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환


1
2
3
4
5
6
7
8
9
10
11
12
13
//숫자를 증가(x -> x+1)시키는 f함수,숫자에 2를 곱하는 g라는 함수
//f와 g를 조립해서 숫자를 증가시킨 뒤 결과에 2를 곱하는 함수 h
 
Function<Integer,Integer> f = x -> x + 1;
Function<Integer,Integer> g = x -> x * 2;
Function<Integer,Integer> h = f.andThen(g);
int result = h.apply(1); //4를 반환
 
//compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공
Function<Integer,Integer> f = x -> x + 1;
Function<Integer,Integer> g = x -> x * 2;
Function<Integer,Integer> h = f.compose(g); 
int result = h.apply(1); //3
cs



요약

1.람다 표현식은 익명 함수의 일종(이름X,파라미터 리스트,바디,반환 형식을 가짐) 예외를 던질수 있음

2.람다 표현식은 간결한 코드로 구현

3.함수형 인터페이스는 하나의 추상 메서드만을 정의하는 인터페이스

4.함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용함

5.람다 표현식을 이용해서 함수형 인터페이스의 추상 메서드를 즉석으로 제공 및 람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급

6.java.util.function 패키지 Predicate<T>,Function<T,R>,Supplier<T>,Consumer<T>,BinaryOperator<T> 등 제공

7.자바8 Predicate<T>와 Function<T,R> 같은 제네릭 함수형 인터페이스와 관련한 박싱 동작을 피할수 있도록 IntPredicate,IntToLongFunction 

기본형 특화 인터페이스도 제공

8.실행 어라운드 패턴(자원할당,자원정리 등 코드 중간에 실행해야 하는 메서드에 꼭 필요한 코드) 람다와 활용하면 유연성과 재사용성을 추가로 얻음

9.람다 표현식의 기대 형식을 대상 형식

10.메서드 레퍼런스를 이용하면 기존의 메서드 구현을 재사용 직접 전달

11.Comparator,Predicate,Function 같은 함수형 인터페이스는 람다 표현식을 조합 -> 다양한 디폴트 메서드를 제공

반응형

댓글