콘텐츠로 이동

2022 10 18

2022-10-18

함수형 인터페이스

  • 정의 : 하나의 메서드가 선언된 인터페이스를 정의하여 람다식을 다루는 기술
  • 느낀점
    • 하나의 메서드를 가진 인터페이스를 정의하고,
    • 해당 메서드를 람다식으로 구체화 시켜 하나의 클래스로 구현하면,
    • 실질적으로는 이걸 결국 한번 호출해주는 로직이 필요함.
    • 마치 객체 Call 하듯
  • 써보기
    Test test = () -> System.out.println("Test!"); // 내가 만든거
    test.print(); //메서드 하나는 호출해줘야하네?
    
    // String을 반환받는 함수형 인터페이스
    Supplier<String> supplierTest = new Supplier<String>() {
        @Override
        public String get() {
            return "Hello!";
        }
    };
    String supplier = supplierTest.get();
    System.out.println("supplier = " + supplier);
    
    // String을 제공하는 함수형 인터페이스
    Consumer<String> consumerTest = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println("s = " + s);
        }
    };
    consumerTest.accept("hello~");
    
    // String을 받아 Integer를 반환하는 인터페이스
    Function<String, Integer> functionTest = new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
            return s.length();
        }
    };
    Integer value = functionTest.apply("hello world");
    System.out.println("value = " + value);
    
    // String, String을 받아 String을 반환하는 인터페이스
    BiFunction<String, String, String> biFunctionTest = (a, b) -> (a + b);
    String apply = biFunctionTest.apply("hello", "world");
    System.out.println("apply = " + apply);
    
    // String을 받아 Boolean을 반환하는 인터페이스
    Predicate<String> predicateTest = s -> s.equals("hello");
    boolean hello = predicateTest.test("hello");
    System.out.println("hello = " + hello);
    boolean world = predicateTest.test("world");
    System.out.println("world = " + world);
    
    // 다음과 같이 체이닝도 가능함
    Function<String, String> rightWay = s -> s;
    Function<String, String> reverseWay = s -> {
    StringBuilder stringBuilder = new StringBuilder(s);
    stringBuilder.reverse();
    return stringBuilder.toString();
    };
    
    Function<String, String> rightAgain = rightWay.andThen(reverseWay).andThen(reverseWay);
    String result = rightAgain.apply("test");
    System.out.println("result = " + result);
    
  • 메서드 참조를 활용하여 람다식을 간단히 표현 가능 (from Java8)
  • Connectable에서는?

    • DTO 변환해주는 로직. 메서드 참조를 통해 구현함.
    • 그냥 TicketMetadataAttribute를 받아 TicketMetadataAttributeResponse로 변환해주는 함수형 인터페이스를 매개변수로 받음.
      public static TicketMetadataAttributeResponse of(TicketMetadataAttribute attribute) {
          return new TicketMetadataAttributeResponse(
                  attribute.getTrait_type(),
                  attribute.getValue()
          );
      }
      
      public static List<TicketMetadataAttributeResponse> toList(List<TicketMetadataAttribute> attributes) {
          return attributes.stream()
                  .map(TicketMetadataAttributeResponse::of)
                  .collect(Collectors.toList());
      }
      
    • 독특하게도 map은 다음과 같이 구현되어있음
      • 왜 input은 super지?
        • 해당 input은 본인과 부모 클래스만 인풋으로 받을 수 있다는 것인데...
        • input을 부모로 받는 함수더라도, 자식을 부모가 품을 수 있기때문에 딱히 문제는 없겠구나
        • function이 실행되는 시점에 input의 부모로 치환하여 함수를 실행할 수 있음
      • 왜 output은 extends지?
        • 해당 output은 본인과 자식들만 구현할 수 있다.
        • 반환하는 값이 결국 본인으로 매핑되니까, 함수를 수행하는 결과값이 자식이더라도 부모로 변경할 수 있어 괜찮은가보다
        • 부모가 자식을 품을 수 있으니, 반환값은 자식이더라도 부모로 싹 변환하여 처리가능
          <R> Stream<R> map(Function<? super T, ? extends R> mapper);
          
  • 요런 괴랄한 로직도 가능
    public static void main(String[] args) {
        // 와일드카드를 활용하여 List<? extends GrandParent> 만들기
        List<? extends GrandParent> family = List.of(new GrandParent(), new Parent(), new Child());
    
        // input은 Object 타입, output은 ? extends Parent
        Function<Object, ? extends Parent> toChild = new Function<Object, Parent>() {
            @Override
            public Parent apply(Object child) {
                // Child 타입이면 Parent 반환
                if (child instanceof Child) {
                    return new Parent();
                } else {
                    return new Child();
                }
            }
        };
    
        List<? extends Parent> collect = family.stream()
            .map(toChild)
            // 여기서 map은...
            // input : ? super (capture of ? extends GrandParent) -> Object도 가능하겠다
            // output : ? extends Parent
            .collect(Collectors.toList());
        System.out.println("collect = " + collect);
    }
    
    // collect = [playground.generic.family.Child@52d455b8, playground.generic.family.Child@4f4a7090, playground.generic.family.Parent@18ef96]
    
  • 2트
    public static void main(String[] args) {
        List<? extends GrandParent> family = List.of(new GrandParent(), new Parent(), new Child());
        Function<Object, Parent> toChild = new Function<Object, Parent>() {
            @Override
            public Parent apply(Object child) {
                if (child instanceof Child) {
                    return new Parent();
                } else {
                    return new Child();
                }
            }
        };
    
        List<Parent> collect = family.stream()
            .map(toChild)
            .collect(Collectors.toList());
        System.out.println("collect = " + collect);
    }
    
    // collect = [playground.generic.family.Child@52d455b8, playground.generic.family.Child@4f4a7090, playground.generic.family.Parent@18ef96]