java 8 정리1

Java 정리 2021. 5. 12. 21:43

인프런 강의 4일차. 

 - 더 자바, Java 8 (백기선 강사님)

 

1. 함수형 인터페이스

 - 추상 메소드를 딱 하나만 가지고 있는 인터페이스

 - SAM (Single Abstract Method) 인터페이스

 - @FunctionInterface 애노테이션을 가지고 있는 인터페이스

 

2. 람다 표현식

 - 함수형 인터페이스의 인스턴스를 만드는 방법으로 쓰일 수 있다.

 - 코드를 줄일 수 있다.

 - 메소드 매개변수, 리턴타입, 변수로 만들어 사용할 수도 있다.

 

3. 자바에서 함수형 프로그래밍

 - 함수를 First Class Object로 사용할 수 있다.

 - 순수 함수(Pure Function) 

    -> 사이드 이펙트 만들 수 없다. (함수 밖에 있는 값을 변경하지 못한다)

    -> 상태가 없다. (함수 밖에 정의되어 있는)

 - 고차 함수(High-Order Function)

    -> 함수가 함수를 매개변수로 받을 수 있고, 함수를 리턴할 수도 있다.

 - 불변성

 

4. 단축키

 -> Alt + Enter : 익명 내부 클래스를 람다로 바꿔주는 단축키(IntelliJ)

package me.whiteship.java8to11;

@FunctionalInterface
public interface RunSomething {

    //추상 메소드가 1개만 있다면 함수형 인터페이스
    //다른 구현된 메소드가 여러개이더라도 추상화 메소드가 1개라면 함수형 인터페이스
    // @FuncationalInterface 추가 시 함수형 인터페이스로 취급되어 추상 메소드 1개만 선언이 가능하다.
    //void doIt();
    int doIt(int number);

    //static method 정의 가능
    static void printName(){
        System.out.println("dhpark");
    }

    //default method 정의 가능
    default void printAge(){
        System.out.println("32");
    }

}


package me.whiteship.java8to11;

public class Foo {

    public static void main(String[] args) {

        //익명 내부 클래스 anonymous inner class
        /*
        RunSomething runSomething = new RunSomething() {
            @Override
            public void doIt() {
                System.out.println("Hello");
            }
        };

        //Alt + Enter : 익명 내부 클래스를 람다 표현식으로 바꿔주는 자동완성 기능
        RunSomething runSomething = () -> System.out.println("Hello");
        runSomething.doIt();
         */
        /*
          함수형 인터페이스를 인라인으로 구현한 오브젝트라고 볼 수 있다.
          이러한 형태는 java가 객체지향 언어이기 때문에 아래 스텝으로 처리된다.
          1. System.out.println("Hello");를 runSomething 객체에 할당
          2. runSomething 객체를 메소드 파라미터에 전달
          3. doIt() 메소드 실행
          위와 같은 호출을 First Class Object로 사용한 것으로 본다.
         */


        /*
          순수 함수 : 입력받은 값이 동일한 경우 결과 값이 같아야 한다.(수학적인 의미)
          아래와 같은 경우 Pure하다 라고 볼 수 없다. (상태 값에 의존한다 라고 볼 수 있다)
           - Case1. 함수가 함수 바깥의 변수를 가져다 사용하는 경우
           - Case2. 외부의 값을 변경하려는 경우
         */
        //1. 순수 함수
        RunSomething runSomething1 = (number) -> {
            return number + 10;
        };
        System.out.println(runSomething1.doIt(1));

        //2. 순수 함수가 아닌 경우
        int baseNumber1 = 10;    //함수 바깥의 변수
        RunSomething runSomething2 = new RunSomething() {
            int baseNumber2 = 20;    //함수 바깥의 변수

            @Override
            public int doIt(int number) {
                baseNumber2++;  //Case2. 외부의 값을 변경하려는 경우
                return number + baseNumber1;    //Case1. 함수 바깥의 값을 가져다 사용하는 경우
            }
        };
    }
}

 

5. Java가 기본으로 제공하는 함수형 인터페이스

 - Java.lang.function 패키지

 - 자바에서 미리 정의해둔 자주 사용할만한 함수 인터페이스

 - Function<T, R>

 - BiFunction<T, U, R>

 - Consumer<T>

 - Supplier<T>

 - Predicate<T>

 - UnaryOperator<T>

 - BinaryOperator<T>

 

6. Function<T, R>

 - T 타입을 받아서 R 타입을 리턴하는 함수 인터페이스

   -> R apply(T t)

 - 함수 조합용 메소드

   -> andThen

   -> compose

 

7. BiFunction<T, U, R>

 - Function<T, R>와 유사하나, 입력값을 2개 받는다.

 - 두 개의 값 (T, U)를 받아서 R 타입을 리턴하는 함수

    -> R apply(T t, U u)

 

8. Consumer<T>

 - T 타입을 받아서 아무것도 리턴하지 않는 함수 인터페이스

   -> void Accept(T t)

 - 함수 조합용 메소드

   -> andThen

 

9. Supplier<T>

 - T 타입의 값을 제공하는 함수 인터페이스

   -> T Get()

 

10. Predicate<T>

 - T 타입의 값을 받아서 boolean을 리턴하는 함수 인터페이스

   -> boolean test(T t)

 - 함수 조합용 메소드

   -> And

   -> Or

   -> Negate

 

11. UnaryOperator<T>

 - Function<T, R>의 특수한 형태로, 입력값 하나를 받아서 동일한 타입을 리턴하는 함수 인터페이스

 

12. BinaryOperator<T>

 - BiFunction<T, U, R>의 특수한 형태로, 동일한 타입의 입력값 두개를 받아 리턴하는 함수 인터페이스

 

package me.whiteship.java8to11;

import java.util.function.*;

public class Foo {

    public static void main(String[] args) {
        //1. 클래스를 만들어서 함수형 프로그래밍
        /*
        public class Plus10 implements Function<Integer, Integer> {
            @Override
            public Integer apply(Integer integer) {
                return integer + 10;
            }
        Plus10 plus10 = new Plus10();
        System.out.println(plus10.apply(1));
         */

        //2. 람다 형식으로 함수형 프로그래밍1 (apply)
        /*Function<Integer, Integer> plus10 = (i) -> i+10;
        System.out.println(plus10.apply(2));*/

        //2. 람다 형식으로 함수형 프로그래밍2 (compose, andThen)
        // Compose : 가지고 뒤에 오는 함수 적용하여 계산 후 src의 입력 값으로 사용
        Function<Integer, Integer> plus10 = (i) -> i+10;
        Function<Integer, Integer> multiply2 = (i) -> i * 2;

        //plus10.compose(multiply2);  //multiply2 적용 후 plus10 하겠다는 의미
        Function<Integer, Integer> multiply2AndPlus10 = plus10.compose(multiply2);
        System.out.println(multiply2AndPlus10.apply(2)); // 10 + (2 * 2)

        // andThen : 앞의 값 계산 후 뒤의 입력 값으로 사용 (compose와 반대)
        Function<Integer, Integer> plus10AndMultiply2 = plus10.andThen(multiply2);
        System.out.println(plus10AndMultiply2.apply(2)); // (2 + 10) * 2


        Consumer<Integer> printT = (i) -> System.out.println(i);
        printT.accept(10);      //10 출력

        Supplier<Integer> get10 = () -> 15;
        System.out.println(get10.get());    //15 출력

        Predicate<String> startsWithPark = (s) -> s.startsWith("Park");
        //Predicate<Integer> isEven = (i) -> i%2 == 0;
        System.out.println(startsWithPark.test("Park"));


        // 같은 타입의 인풋, 아웃풋인 경우 UnaryOperator를 써서 좀 더 깔끔하게 처리 가능
        //Function<Integer, Integer> plus10 = (i) -> i+10;
        UnaryOperator<Integer> unaryPlus10 = (i) -> i+10;

        // BiFunction은 3개의 타입이 전부 다를 때 사용하고, BinaryOperator 는 3개의 타입이 전부 같을 때 사용한다.
    }
}

 

14. 람다 표현식

package me.whiteship.java8to11;

import java.util.function.*;

public class Foo {

    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.run();
    }

    private void run() {
        final int  baseNumber = 10;

        // 로컬 클래스
        class LocalClass {
            void printBaseNumber(){
                int baseNumber = 11;    //섀도잉
                System.out.println(baseNumber); // 11 출력
            }
        }


        // 익명 클래스
        Consumer<Integer> integerConsumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer baseNumber) {        //baseNumber 변수는 파라미터로 넘어온 baseNumber를 사용하게 됨 = 섀도잉
                System.out.println(baseNumber); //익명 클래스에서 로컬 변수 참조하는 법
            }
        };

        // 람다
        IntConsumer printInt = (i) -> {
            System.out.println(i + baseNumber);
        };

        printInt.accept(30);

        /*
         3개 클래스 모두 공통적으로 로컬 변수를 참조할 수 있다.
         java8 부터 final을 생략할 수 있는 경우가 있는데 해당 변수가 사실상 final인 경우 생략이 가능하다.
         사실상 fianl 의미 : 변수 선언 후 값을 변경하는 로직이 없는 경우 (= effective fianl)

         람다가 다른 부분은 다른 2개 클래스와 다르게 섀도잉이 불가능하다.
          -> 로컬 클래스나 익명 클래스에서 선언한 변수들은 해당 클래스 바깥 혹은 Nested 클래스에서 같은 이름의 변수 사용이 가능하다(덮어써짐 = 섀도잉)
          즉 람다는 섀도잉이 안되니 람다의 스콥은 람다를 감싸고 있는 클래스와 동일하다
          그렇기 때문에 람다에서 참조 가능한 로컬 변수는 fianl과 effective final 만 사용이 가능하다.(아닐 시 컴파일 에러)

            IntConsumer printInt = (baseNumber) -> {        //람다는 섀도잉이 안되므로 파라미터 이름을 baseNumber로 넘겨주어도 사용이 불가능하다(컴파일 에러 발생)
                System.out.println(baseNumber);
            };
         */
    }
}

'Java 정리' 카테고리의 다른 글

java 8 정리6  (0) 2021.05.21
java 8 정리5  (0) 2021.05.20
java 8 정리4  (0) 2021.05.19
java 8 정리3  (0) 2021.05.17
java 8 정리2  (0) 2021.05.13