[더 자바, Java8] 0~1. 함수형 인터페이스와 람다

eunsol Jo·2021년 9월 15일
1

🎱  더 자바, Java8

목록 보기
1/5
post-thumbnail

출처 ‣ 더 자바 Java 8, 백기선 / 인프런

0. 자바8 소개

  • 2014년 3월 출시, 현재 83%의 점유율 (2020년 기준)
  • LTS(오랜기간 업데이트를 제공, 다음 LTS버전은 자바 17) / 자바11도 LTS임
  • Non-LTS의 경우 다음버전 출시시(약6개월 주기) 업데이트를 해주지 않음.

1.1 주요기능

#람다 #메소드 레퍼런스 #스트림 API #Optional

1. 함수형 인터페이스와 람다

1.1 함수형 인터페이스 (SAM:Single Abstract Method)

추상메서드가 하나만 있는 인터페이스 (SAM:Single Abstract Method)

  • 추상메서드가 두개 있으면 안된다.
  • 다른(static/default) 메서드는 있어도 된다.
  • @FunctionalInterface 를 통해 함수형 인터페이스를 더 견고하게 관리 할 수 있다. (위반시 컴파일에러)
@FunctionalInterface // java standard lib에 들어서 별도 import가 되지 않음
public interface RunSomthing {

    // 함수형 인터페이스
    void doIt(); // interface는 abstract 생략가능

    // <자바8의 추가 기능>
    // 1. static 메서드 
    static void printName() {
        System.out.println("esjo");
    }
    
    // 2. default 메서드
    default void pringAge() {
        System.out.println("28");
    }

}

1.1.1 익명 내부 클래스 anonymous inner class

public class Main {
  public static void main(String[] args) {

      /** 익명 내부 클래스 anonymous inner class */

      // 자바8 이전 
      RunSomthing runSomthing1 = new RunSomthing() {
        @Override
        public void doIt() {
          System.out.println("Hello");
        }
      };

      // 자바 8
      RunSomthing runSomthing2 = () -> System.out.println("Hello");

      RunSomthing runSomthing3 = () -> {
        System.out.println("Hello");				
        System.out.println("Bye");				
      };

      runSomthing1.doIt();
      runSomthing2.doIt();
      runSomthing3.doIt();
  }
}

1.1.3 일급객체(First class object) 로 사용

*First class object?

  • 파라미터로 전달 가능
  • return 값으로 사용 가능
  • 변수 or 데이터 구조에 담기 가능

과거 자바에서 Method는 변수에 담을 수 없었다.
모던자바에서는 가능해짐. 함수를 하나의 Object(객체로 취급)

1.1.4 순수함수(Pure Function)

  • 사이드 이팩트 방지 (함수 밖에 있는 값을 변경하지 못함)
  • 상태가 없다(함수 밖에 정의된)
  • 입력받은 값이 동일한 경우, 결과 값이 같아야 한다. ⇒ 이를 보장하지 못하면, 함수형 프로그래밍이 아님!

    // 인터페이스
    @FunctionalInterface
    public interface RunSomthing {
        int doIt(int number);
    }

    // 구현
    public class Foo {
        public static void main(String[] args) {
          RunSomthing runSomthing = (number) -> {
  			  return number + 10;
          }

          runSomthing.doIt(1); // 11
          runSomthing.doIt(1); // 11
          runSomthing.doIt(1); // 11
        }
    }
  • 보장하지 못하는 경우 (=상태값에 의존하는 경우) ↔ 순수함수
    1. 함수 외부의 변수를 사용하는 경우
    2. 외부에 있는 값을 변경하는 경우
  // 인터페이스
  @FunctionalInterface
  public interface RunSomthing {
  	  int doIt(int number);
  }

  // 구현
  public class Foo {
    public static void main(String[] args) {
      RunSomthing runSomthing1 = new RunSomthing() {
        int baseNumber = 10;

        @Override
        public int doIt(int number) {
            baseNumber++; // 2) 외부의 값을 변경
            return number + baseNumber; // 1) 외부의 값을 사용
        }
      };
    }
  }

1.1.5 고차함수(Higher-Order Fucntion)

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

1.1.6 불변성

1.2 Java가 제공하는 함수형 인터페이스

1.2.1 java.lang.function

docs : https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

자주 사용할 만한 함수 인터페이스

  • Function<T, R> : T를 받아서 R을 리턴
    • 함수 조합 - apply, andThen
  • BiFunction<T, U, R> : T, U를 받아서 R을 리턴
  • Consumer : 리턴이 없다. ex. 입력값을 로그에 출력만 한다.
  • Supplier : 입력값 없이, 리턴만 존재
  • Predicate : T를 받아서, boolean을 리턴
    • 함수 조합 - And, Or, Negate
  • UnaryOperator : 입력/출력의 타입이 같은 경우. (Function을 상속받음)
  • BinaryOperator : 입력2개/출력의 타입이 같은 경우. (BiFunction의 모든 타입이 같은경우)

1.2.2 Function

  • R apply(T t)
    // 방법1. 클래스로 구현해서 사용
    import java.util.function.Function;

    public class Plus10 implements Function<Integer, Integer> {
        @Override
        public Integer apply(Integer number) {
            return number + 10;
        }
    }

    // 사용
    public class Foo {
        public static void main(String[] args) {
            Plus10 plus10 = new Plus10();
            System.out.println(plus10.apply(1));
        }
    }

    // 방법2. 람다식으로 바로 사용
    import java.util.function.Function;

    public class Foo {
        public static void main(String[] args) {
            Function<Integer, Integer> plus10 = (i) -> i + 10;
            System.out.println(plus10.apply(1));
        }
    }

1.2.3 Function 함수 조합용 메서드

  • andThen : 함수를 먼저 실행하고, 입력값을 실행 (실행함수 우선 실행)
    import java.util.function.Function;

    public class Foo {
        public static void main(String[] args) {
            Function<Integer, Integer> plus10 = (i) -> i + 10;
            Function<Integer, Integer> multiply2 = (i) -> i * 2;

	        Function<Integer, Integer> plus10Andmultiply2 = plus10.andThen(multiply2);
            
            System.out.println(plus10Andmultiply2.apply(2)); // (2 + 10) * 10 = 120
        }
    }
  • compose : 입력값을 실행한후, 함수를 실행. (입력함수 우선 실행)
    import java.util.function.Function;

    public class Foo {
        public static void main(String[] args) {
            Function<Integer, Integer> plus10 = (i) -> i + 10;
            Function<Integer, Integer> multiply2 = (i) -> i * 2;

	        Function<Integer, Integer> multiply2AndPlus10 = plus10.compose(multiply2);
            
            System.out.println(multiply2AndPlus10.apply(2)); // (2 * 2) + 10 = 14
        }
    }

1.3 람다 표현식

Supplier<Integer> get10 = () -> {
	return 10;
}

// 함수가 한줄
Supplier<Integer> get10 = () -> 10;

// 파라미터가 두개 이상
BinaryOperator<Integer> sum = (a, b) -> a + b;

// 타입을 적어줄 수 있지만 선언부의 타입으로 유추 가능해서 생략가능
BinaryOperator<Integer> sum = (Integer a, Integer b) -> a + b;

1.3.1 변수 캡쳐

  • 람다와 로컬 클래스 or 익명 클래스가 다른점 = 쉐도잉 (int n = 10; int n = 11; ⇒ n은 11으로 덮어짐)
  • 람다는 쉐도잉이 안된다.
  • 로컬 클래스 or 익명 클래스 는 별도의 scope이나, 람다는 run 메서드와 같은 scope
  • 람다는 final or effectively final 만 참조한다. (아닌경우 컴파일 에러)

람다 밖의 변수 = 자유변수
자유변수를 사용하는것 = 람다 캡쳐링

import java.util.function.Consumer;
import java.util.function.IntConsumer;

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

    private void run() {
        final int baseNumber = 10; // final을 생략 할수 있는경우 = 실제 더이상 변경하지 않는경우 (effectively final)

        // 로컬 클래스 (local class)
        class LocalClass {
            void printBaseNumber() {
                int baseNumber = 12; // 쉐도잉됨
                System.out.println(baseNumber);
            }
        }

        // 익명 클래스 (anonymous class)
        Consumer<Integer> integerConsumer = new Consumer<Integer>() {
            @Override
            public void accept(Integer i) {
                int baseNumber = 11; // 쉐도잉됨
                System.out.println(i + baseNumber);
            }
        };

        // 람다 (Lamda)
        IntConsumer printInt = (i) -> {
            // int baseNumber = 1; // 같은 scope안에 같은 변수명을 사용할 수 없다. 컴파일 에러
            System.out.println(i + baseNumber); // 로컬변수 참조
        };

        new LocalClass().printBaseNumber(); // 12
        integerConsumer.accept(10); // 21
        printInt.accept(10); // 20
    }
}

1.4 메소드 레퍼런스

기존 메서드 or 생성자 호출시 간결한 표현 가능

// 아래 예제를 위한 참조 클래스
class Greeting {

    private String name;
    
    public Greeting() {
    }

    public Greeting(String name) {
        this.name = name;
    }

    static String hi(String name) {
        return "hi " + name;
    }

    String hello(String name) {
        return "hello " + name;
    }
}

1.4.1 스태틱 메소드 참조 ⇒ 타입::스태틱 메소드

  UnaryOperator<String> hi = Greeting::hi;
  System.out.println(hi.apply("eunsol")); // SOUT = hi eunsol

1.4.2 특정 객체의 인스턴스 메소드 참조 ⇒ 객체 레퍼런스::인스턴스 메소드

    Greeting greeting = new Greeting();
    UnaryOperator<String> hello = greeting::hello;
    System.out.println(hello.apply("eunsol")); // SOUT = hello eunsol

1.4.3 임의 객체의 인스턴스 메소드 참조 ⇒ 타입::인스턴스 메소드

    String[] names = {"esjo", "cho", "jeo"};
    // 1. 익명함수
    Arrays.sort(names, new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return 0; // 구현
        }
    });

    // 2. 람다
    Arrays.sort(names, (o1, o2) -> { /*int값을 리턴하는 로직 구현*/ });

    // 3. 메소드 참조
    // - compareToIgnoreCase : 자기자신과 파라미터 String 값을 비교해서 int 값을 넘겨줌 
    // - compare함수와 파라미터+리턴값이 동일
    Arrays.sort(names, String::compareToIgnoreCase); // arrayList내부의 임의의 객체들을 참조함

1.4.4 생성자 참조 ⇒ 타입::new

    // 기본 생성자 (파라미터X)
    Supplier<Greeting> newGreeting = Greeting::new;
    Greeting greeting = newGreeting.get();

    // 파라미터 있는 생성자
    Function<String, Greeting> newGreeting = Greeting::new;
    Greeting greeting = newGreeting.apply();

    // => 메서드 레퍼런스 만으로 판단하기 어렵다.
profile
Later never comes 👩🏻‍💻

0개의 댓글