• 람다란

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

    • 이름을 가지지는 않지만, 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트는 가질 수 있음

    • 람다의 특징

      • 익명: 보통의 메서드와 달리 이름이 없음
      • 함수: 람다는 특정 클래스에 종속적이지 않으므로 함수라고 부름, 하지만 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트 가짐
      • 전달: 람다 표현식을 메서드 인수로 전달하거나 변수로 저장 가능, 즉 메서드를 변수 또는 파라미터로 가능
      • 간결성: 익명 클래스처럼 코드가 많이 작성할 필요 없음
    • 람다 구조

      Untitled

      • 파라미터 리스트: Comparator의 compare메서드 파라미터(사과 두 개)
      • 화살표: 람다의 파라미터 리스트와 바디 구분
      • 람다 바디: 람다의 반환값에 해당하는 표현식
    • 람다의 대표적인 사용

      // 람다의 대표적인 사용 예시로 Predicate 함수형 인터페이스 파라미터로 람다식 전달
      List<Apple> filterList = inventory.stream().filter(a -> a.getWeight() > 150);
      
  • 함수 디스크립터

    • 함수형 인터페이스의 추상 메서드 시그니처 = 람다 표현시의 시크니처
    • 람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터라고 함
    • 메서드 시그니처는 메서드명/파라미터 순서/파라미터 타입 /파라미터 개수를 의미
    • 즉, 함수를 표현하는 방법
  • 실행 어라운드 패턴

    • 실제 자원을 처리하는 코드를 설정, 정리 두 과정이 둘러싸는 형태

    • 즉, 하나의 로직을 수행할 때 첫번째로 초기화/준비 코드가 수행 → 마지막에 정리/마무리 코드가 실행

    • 위 코드 사이에 실제 자원을 처리하는 코드를 실행하는 것

    • 예를 들면 try-with-resources 구문에서 설정, 정리 부분의 코드는 중복되므로 로직 구현코드만 바꾸면서 사용하는 패턴

    • 예시 코드

      // 아래과정을 통해 실행 어라운드 패턴으로 구현 가능
      // 실제 작업을 하는 코드는 return 부분만 해당
      public String processFile(BufferedReaderProcessor p) throws IOException {
      	try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
      		return p.process(br);
      	}
      }
      
      // 아래 함수형 인터페이스 구현체를 위 processFile의 파라미터로 전달
      // 이를 통해 동작 파라미터화를 통해 로직만을 바꿔 사용할 수 있음
      // 동작 파라미터로는 (BufferedReader -> String) 디스크립터와 일치하는 람다 전달 가능
      @FunctionalInterface
      public interface BufferedReaderProcessor {
      	String process(BufferedReader b) throws IOException;
      }
      
      // 아래처럼 람다를 전다랗여 로직을 동작 파라미터화 가능
      String oneLine = processFile((BuffredReader) -> br.readLine());
      String twoLine = processFile((BuffredReader) -> br.readLine() + br.readLine());
      
  • 대표적인 함수형 인터페이스

    • Predicate<T>

      • test()라는 추상 메서드를 정의, test()는 제네릭 형식 T의 객체를 인수로 받아 boolean 반환

      • 예시 코드

        // 예제는 String 객체를 인수로 받는 람다 정의
        @FunctionalInterface
        public interface Predicate<T> {
        	boolean test(T t);
        }
        
        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(p);
        		}
        	}
        }
        
    • Consumer<T>

      • 제네릭 형식 T 객체를 받아 void를 반환하는 accept() 추상 메서드 정의

      • 예를 들어 Integer 리스트를 인수로 받아 각 항목에 동작을 수행하는 forEach 메서드 정의 가능

      • 예시 코드

        // 예제는 Integer 타입 리스트의 각 원소를 출력하는 람다 정의
        @FunctionalInterface
        public interface Consumer<T> {
        	void accept(T t);
        }
        
        public <T> void forEach(List<T> list, Consumer<T> c) {
        	for(T t : list) {
        		c.accept(t);
        	}
        }
        
        forEach(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i));
        
    • Function<T, R>

      • 제네릭 형식 T를 인수로 받아 제넥릭 형식 R 객체를 반환하는 apply() 추상 메서드를 가짐

      • 예를 들면 Apple 객체를 인수로 받아 Integer 타입의 무게를 출력하는 함수

      • 예시 코드

        @FunctionalInterface
        public interface Function(T, R) {
        	R apply(T t);
        }
        
        public <T, R> List<R> map(List<T> list, Function<T, R> f) {
        	List<R> results = new ArrayList<>();
        	for(T t : list) {
        		result.add(f.apply(t));
        	}
        	return result;
        }
        
        List<Integer> i = map(Arrays.asList("lambda", "in", "action"),
        	(String s) -> s.length());
        
  • 기본형 특화

    • 자바의 모든 형식은 참조형(Byte, Integer, Object, List) 아니면 **기본형(int, double, byte, char)**에 해당

    • 하지만 제네릭 파라미터(Consumer<T>의 T)에는 참조형만 가능 → 제네릭 내부 구현 때문에 어쩔 수 없는 일

    • 이를 위해 자바는 기본형을 참조형으로 변환하는 기능 제공 → 이를 박싱이라고 함

    • 이 반대의 동작을 언박싱이라고 함

    • 개발자가 편리하게 사용할 수 있도록 방식/언박싱이 자동으로 이루어 지는데 이를 오토박싱이라고 함

    • 이러한 박싱/언박싱을 사용하면 변환 과정에서 비용이 소모됨 → 박싱된 값은 기본형을 감싸는 래퍼며 힙에 저장됨

    • Java8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱을 피할 수 있도록 특별한 버전의 함수형 인터페이스 제공

    • 예시 코드

      public interface IntPredicate {
      	boolean test(int t);
      }
      
      // 박싱 없이 진행
      IntPredicate evenNumbers = (int i) -> i % 2 == 0;
      eventNumbers.test(1000); 
      
      // 박싱하여 진행
      Predicate<Integer> oddNumbers = (Integer i) i % 2 != 0;
      oddNumbers.test(1000);
      
  • Java8애 추가된 함수형 인터페이스

    Untitled

  • 예외, 람다, 함수형 인터페이스 관계

    • 함수형 인터페이스는 확인된 예외를 던지는 동작을 허용하지 않음

    • 즉, 예외를 던지는 람다 표현식을 만드려면 확인된 예외를 던지는 함수형 인터페이스를 정의하거나 try/catch 블록으로 감싸야 함

    • 예시 코드

      // 확인된 예외를 던지는 함수형 인터페이스 정의
      @FunctionalInterface
      public interface BufferedReaderProcessor {
      	String process(BufferedReader b) throws IOException;
      }
      
      // try/catch를 이용해서 확인된 예외를 던지는 람다식
      BufferedReaderProcessor p = (BufferedReader b) -> {
      	try {
      		return b.readLine();
      	} catch (IOException e) {
      		throw new RuntimeException(e);
      	}
      }
      
  • 형식 검사, 형식 추론, 제약 다시 읽어 보기

  • 메서드 참조

    • 메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달 가능

    • 때로는 람다식보다 메서드 참조가 더 가독성이 좋을 수 있음

    • 예시 코드

      Untitled

      // 메서드 참조를 사용하지 않은 코드
      inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
      
      // 메서드 참조를 사용한 코드
      // 실제 메서드를 사용하지 않으므로 괄호()를 사용하지 않아도 됨
      inventory.sort(comparing(Apple::getWeight));
      
    • 메서드 참조를 사용하는 방법

      • 정적 메서드 참조
        • Integer의 parseInt 메서드는 Integer::parseInt로 표현 가능
      • 다양한 형식의 인스턴스 메서드 참조
        • String의 length 메서드는 String::length로 표현 가능
      • 기존 객체의 인스턴스 메서드 참조
        • Transaction객체를 할당받은 expensiveTransaction 지역 변수 존재
        • Transaction 객체는 getValue() 메서드를 가짐
        • 이를 사용한다면 expensiveTransaction::getValue를 통해 표현
  • 생성자 참조

    • 정적 메서드 참조하는 방법과 유사, ClassName::new를 통해 사용 가능

    • 예시 코드

      // 생성자 참조를 사용하지 않은 코드
      Supplier<Apple> c1 = () -> new Apple();
      Apple a1 = c1.get();
      
      // 생성자 참조를 사용한 코드
      Supplier<Apple> c2 = Apple::new;
      Apple a2 = c2.get();
      
      // 생성자 파라미터 2개를 받는 생성자 참조
      BiFunction<String, Integer, Apple> c3 = (color, weight) -> New Apple(color, weight);
      Apple a3 = c3.get();
      
      // 생성자 파라미터 3개를 받는 생성자 참조
      TriFunction<String, Integer, Integer, Apple> c4 = (color, weight, order) -> new Apple(color, weight, order);
      Apple a4 = c4.get();
      
  • 부족하면 3.9 다시 보기