간펴니
간편 자바프로그래밍
간펴니
전체 방문자
오늘
어제
  • 전체보기 (185)
    • 알고리즘 (2)
    • JAVA (69)
      • 이펙티브 자바 (47)
      • JAVA 병렬프로그래밍 (5)
      • 자바 (17)
    • SPRING (60)
      • Spring (12)
      • IceWater Community (37)
      • Homme Shop (10)
      • 토비의 스프링 (1)
    • SPRING BOOT (4)
      • WhiteRecord (7)
    • 오류 (9)
    • DB (10)
      • ORACLE (5)
      • MYSQL (1)
      • MYBATIS (4)
      • JPA (0)
      • 대용량 데이터 베이스 (0)
      • SQL (0)
    • FRONT (8)
      • JSP (2)
      • JavaScript (2)
      • Jquery (3)
      • Thymeleaf (1)
    • AWS (6)
    • JNI (10)
    • 회고 (0)
    • MQ (0)
    • Radis (0)
    • Git (0)
    • Docker (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 블로그 컨셉 변경

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
간펴니

간편 자바프로그래밍

JAVA/자바

함수형 인터페이스

2023. 6. 10. 18:11
728x90

 

코드를 작성하다 보면 비슷한 로직의 중복 코드가 생길 수 있고, 그로 하여금 관리 지점이 늘어나기 때문에 대부분 리팩터링의 대상으로 취급한다.

 

함수형 인터페이스를 사용하면 효과적으로 코드의 중복을 줄일 수 있고, 가독성을 높일 수 있다.

 

  • Consumer

매개값은 있고, 반환값은 없다. 매개값을 전달받아 사용하고 아무것도 반환하지 않을 때 사용된다.

 

이를 소비 (Consume) 한다고 표현한다. accept 추상 메소드를 가지고 있다.

이름 기능 메소드
Consumer 객체 T를 받아 소비한다. void accept(T t)
BiConsumer<T, U> 객체 T와 U 두가지를 받아 소비한다. void accept(T t, U u)
DoubleConsumer double 값을 받아 소비한다. void accept(double value)
IntConsumer int 값을 받아 소비한다. void accept(int value)
LongConsumer long 값을 받아 소비한다. void accept(long value)
ObjDoubleConsumer 객체 T와 double 을 받아 소비한다. void accept(T t, double value)
ObjIntConsumer 객체 T와 int 를 받아 소비한다. void accept(T t, int value)

 

대표적으로 Stream 의 forEach 메소드의 매개변수 타입이 Consumer 이다.

 

 

 

  • Supplier

매개값은 없고, 반환값은 있다. 실행 후 호출한 곳으로 데이터를 공급 (Supply) 한다. getXXX 추상 메소드를 가지고 있다.

이름 기능 메소드
Supplier T 객체를 반환한다. T get()
BooleanSupplier boolean 값을 반환한다. boolean getAsBoolean()
DoubleSupplier double 값을 반환한다. double getAsDouble()
IntSupplier int 값을 반환한다. int getAsInt()
LongSupplier long 값을 반환한다. long getAsLong()

 

  • Function 

매개값도 있고, 리턴값도 있다. 주로 매개값을 반환값으로 매핑할 때, 타입 변환이 목적일 때 사용한다. 

 

이름 기능 메소드
Function<T, R> 객체 T를 객체 R로 매핑한다. R apply(T t)
BiFunction<T, U, R> 객체 T와 U를 객체 R로 매핑한다. R apply(T t, U u)
DoubleFunction double 값을 객체 R로 매핑한다. R apply(double value)
IntFunction int 값을 객체 R로 매핑한다. R apply(int value)
IntToDoubleFunction int 값을 double 값으로 매핑한다. double applyAsDouble(int value)
IntToLongFunction int 값을 long 값으로 매핑한다. long applyAsLong(int value)
LongToDoubleFunction long 값을 double 값으로 매핑한다. double applyAsDouble(long value)
LongToIntFunction long 값을 int 값으로 매핑한다. int applyAsInt(long value)
ToDoubleBiFunction<T, U> 객체 T와 U를 double 값으로 매핑한다. double applyAsDouble(T t, U u)
ToDoubleFunction 객체 T를 double 값으로 매핑한다. double applyAsDouble(T t)
ToIntBiFunction<T, U> 객체 T와 U를 int 값으로 매핑한다. int applyAsInt(T t, U u)
ToIntFunction 객체 T를 int 값으로 매핑한다. int applyAsInt(T t)
ToLongBiFunction<T, U> 객체 T와 U를 long 값으로 매핑한다. long applyAsLong(T t, U u)
ToLongFunction 객체 T를 long 값으로 매핑한다. long applyAsLong(T t)

 

 

  • Operator

Function 과 마찬가지로, 매개값도 있고, 반환값도 있다.

 

주로 매개값을 연산 (Operation) 하여 결과를 반환할 때 사용된다. 

 

이름 기능 메소드
BinaryOperator 객체 T와 T를 연산 후 객체 T를 반환한다. T apply(T t, T t)
UnaryOperator 객체 T를 연산 후 T를 반환한다. T apply(T t)
DoubleBinaryOperator 두개의 double 을 연산 후 double 을 반환한다. double applyAsDouble(double, double)
DoubleUnaryOperator 한개의 double 을 연산 후 double 을 반환한다. double applysDouble(double, double)
IntBinaryOperator 두개의 int 를 연산 후 int 을 반환한다. int applyAsInt(int, int)
IntUnaryOperator 한개의 int 를 연산 후 int 을 반환한다. int applyAsInt(int)
LongBinaryOperator 두개의 long 을 연산 후 long 을 반환한다. long applyAsLong(long, long)
LongUnaryOperator 한개의 long 을 연산 후 long 을 반환한다. long applyAsLong(long)

 

 

 

  • Predicate

매개값은 있고, 반환 타입은 boolean 이다. 매개값을 받아 검사하고 true 혹은 false 를 반환할 때 사용된다. test 추상 메소드를 가지고 있다.

 

이름 기능 메소드
Predicate 객체 T를 조사 후 boolean 값을 반환한다. boolean test(T t)
BiPredicate<T, U> 객체 T와 U를 비교 조사 후 boolean 값을 반환한다. boolean test(T t, U u)
DoublePredicate double 값을 조사 후 boolean 값을 반환한다. boolean test(double value)
IntPredicate int 값을 조사 후 boolean 값을 반환한다. boolean test(int value)
LongPredicate long 값을 조사 후 boolean 값을 반환한다. boolean test(long value)

 

 

 


아래는 프로젝트 중 옵저버 패턴을 구현하기 위해 사용했던 Function 인터페이스이다.

public abstract class Dispatcher {

  protected final Function<DispatchMessage, DispatchMessage> pipe;
	DispatcherContext dispatcherContext;

  public Dispatcher(DispatcherContext dispatcherContext) {
	this.dispatcherContext = dispathcerContext;
    this.pipe = collectHandler();

  }

  public abstract Function<DispatchMessage, DispatchMessage> collectHandler();


  public void dispatch(DispatchMessage message) {

      pipe.apply( message );

    }
  }


}

 


@Component
public class TestDispatcher extends Dispatcher {


  public TestDispatcher(DispatcherContext dispatcherContext) {

    super(dispatcherContext);

  }

  @Override
  public Function<DispatchMessage, DispatchMessage> collectHandler() {

    Function<DispatchMessage, DispatchMessage>
        collection = new 1Handler(dispatcherContext);
     collection = collection.andThen(new 2Handler(dispatcherContext));
     collection = collection.andThen(new 3Handler(dispatcherContext));
     collection = collection.andThen(new 4Handler(dispatcherContext));

    return collection;
  }


}

추상 클래스를 상속받아, 구현체를 만들어준다.

 

collectHandler의 역할은 Function 인터페이스를 구현할 구현체를 지정해주는 역할이다.

 

 

@Component
public class AAAConsumer {

  private final TestDispatcher dispatchers;

  public AAAConsumer(TestDispatcher dispatchers) {

    this.dispatchers = dispatchers;

  }

  @RabbitListener
  public void read(final Message message) {

	dispatchers.dispatch( .. );

  }
}

옵저버 ( dispather ) 를 호출하는 부분이다. 

 

RabbitMQ 리스너를 이용한 예제이다.

 

 

이제 TestDispatcher의 dispatch가 호출됐다.

 

추상클래스인 Dispatcher에 따라 apply 를 호출할 Function은 collectorHandler에 의해 정해지고,

 

구현체인 TestDispatcher에서 정의된대로 1~4 핸들러가 순차적으로 호출된다.

 

위의 예제에서 코드는 생략됐지만 각 핸들러는 RabbitListener에서 받은 message의 type으로 자신이 변수에 맞는 핸들러인지 구분한다.

 

핸들러가 구분됐다면 apply를 통해 해당 핸들러에서 구현된 로직을 수행하고, 그에 맞는 리턴 결과를 반환한다.(DispatherMessage 또한 추상클래스로 각 핸들러에 맞는 메세지 타입이 구현되어 있다. )

 

 

 

이렇게 옵저버 패턴을 사용한다면 후에 기능을 추가해야 할 때 Dispather를 상속받아 구현체를 만들고 , 그에 맞는 handler를 추가해주면 된다. 다른 기능에는 전혀 영향을 미치지 않는다. 가독성이 뛰어나 쉽게 기능들을 구분하고 확인할 수 있다.

 

추상클래스의 유연함을 위해 Function 인터페이스를 사용한 경험이였다.

 


 

프로젝트 중 특정 어플리케이션에서 사용할 라이브러리를 만들어야 하는 상황이 있었다.

 

A,B,C 기능을 사용할 수 있어야 하는 어플리케이션에서 C 기능을 따로 라이브러리로 만들어서 모듈을 담당한 인원에게 전달해야 했다.

 

 

public class Client {

	private final Consumer< 화면캡쳐본 > a;


	public RdpClient( Consumer< 화면캡쳐본 > a) {
                      ... 생략
                    
	}
    
    // 라이브러리 기능들
    public void 마우스이동 ; 
    public void 키보드이동 ; 

}

라이브러리는 특정 화면에서 마우스와 키보드를 이동할 수 있는 기능이 있고, 화면캡쳐본을 0.1초마다 Consumer를 통해 지속적으로 제공해준다.

 

  private void 화면캡쳐전송() {

        a.accept(화면캡쳐본);

  }

 

라이브러리 내 화면캡쳐를 담당하는 부분에서 Client를 참조할 수 있고, Client의 Consumer를 accept 한다.

 

 

이제 어플리케이션은 해당 기능을 사용하기 위해 라이브러리를 import 하고, Client를 생성해줘야 한다.

 

import 라이브러리C

public class 어플리케이션 {

//생성자 등등..


	public void updateScreenConsumer( 화면캡쳐본 ) {
		// 화면캡쳐본으로 로직 수행
        }
        
        
        public static void main(String[] args){
        
       	Client client = new Client(
       	this::updateScreenConsumer )
      	client.마우스이동
      	client.키보드이동
      	.....
        
        }

}

 

어플리케이션에선 이렇게 Consumer를 등록해 0.1초마다 전송되는 화면 캡쳐본을 받을 수 있게 된다.

 

 


 

함수형 인터페이스는 어플리케이션의 가독성과 유연성을 위해 활용 할 수 있는 곳이 매우 많다. 

 

지속적으로 공부하고 활용해보도록 하자.

 

 

 

728x90
저작자표시 (새창열림)

'JAVA > 자바' 카테고리의 다른 글

제네릭과 애너테이션 활용기 ( 커맨드 패턴 )  (0) 2023.07.23
[Java] 해시 알고리즘이란?  (0) 2023.06.30
[Java] Enum, 열거타입  (0) 2023.06.07
[JAVA] SOLID 객체지향 설계 5원칙  (2) 2022.02.26
[JAVA] Static과 Final  (0) 2022.02.26
    'JAVA/자바' 카테고리의 다른 글
    • 제네릭과 애너테이션 활용기 ( 커맨드 패턴 )
    • [Java] 해시 알고리즘이란?
    • [Java] Enum, 열거타입
    • [JAVA] SOLID 객체지향 설계 5원칙
    간펴니
    간펴니
    개발공부 기록하는 곳

    티스토리툴바