워밍업 클럽 BE-0기 3일차 과제

워밍업 클럽 BE-0기 3일차 과제

안녕하세요. 오늘도 퇴근하고 과제를 수행하기 위해 밥먹고 바로 켰습니다. 사실 바로 안켰습니다. T1 경기를 마저보고 켜서 작성 중입니다. 3일차 과제는 키워드와 질문들로 주어졌습니다. 우선, 질문을 먼저 해보고 키워드에 대해 알아보는 식으로 글을 작성하겠습니다.

[키워드]

익명 클래스 / 람다 / 함수형 프로그래밍 / @FunctionalInterface / 스트림 API / 메소드 레퍼런스

[질문]

  • 자바의 람다식은 왜 등장했을까?

  • 람다식과 익명 클래스는 어떤 관계가 있을까? - 람다식의 문법은 어떻게 될까?

첫 번째 질문에 답해보겠습니다. 람다식이라는 것은 자바뿐만 아니라 많은 프로그래밍 언어에서 사용할 수 있습니다. 모든 언어에서 제공되지는 않지만, 우리가 주로 현업에서 사용하는 언어들은 이를 지원합니다. 유명한 언어들 중에 이를 지원하지 않는 언어는 C, 포트란, 파스칼 정도입니다. 그리고 이러한 람다식이 Java 8에서 지원하게 된 이유에는 함수형 프로그래밍을 지원하기 위해 등장했습니다.

 

그러면 함수형 프로그래밍이란 무엇일까요? 함수형 프로그래밍은 기존의 명령형 프로그래밍 기반 개발의 문제점인, 소프트웨어의 크기가 커짐에 따라 복잡해지는 문제를 해결하기 위해 등장했습니다. 함수형 프로그래밍은 거의 모든 것을 순수 함수로 나누어 문제를 해결합니다. 그리고 이 함수는 최대한 작아야 합니다. 그리고 이로인해 가독성과 유지보수를 용이하게 해주는 장점을 취합니다.

 

그 다음 두 번째 질문에 답해보겠습니다. 자바는 람다식을 지원하기 이전에 익명 클래스라는 것으로 비슷한 형식의 기능을 지원했습니다. 예시로

public class LambdaVSAnonymous {
	public static void main(String[] args) {
		List<Integer> numbers = new ArrayList<>();
		numbers.add(1);
		numbers.add(2);
		numbers.add(3);
		numbers.add(4);
		numbers.add(5);

		numbers.removeIf(new Predicate<Integer>() {
			@Override
			public boolean test(Integer number) {
				return number % 2 == 0;
			}
		});

		System.out.println("결과 (익명 클래스): " + numbers);
	}
}

우선 Predicate라는 이상한 애가 있기는 한데 여기서는 참 거짓을 따져주는 애라고만 알면 될 것 같습니다. 이 코드는 Predicate라는 함수형 인터페이스를 사용해서 리스트 안에 있는 홀수들만 뽑아내는 코드입니다. 그러면 이를 람다식으로 바꿔보겠습니다.

public class LambdaVSAnonymous {
	public static void main(String[] args) {
		List<Integer> numbers = new ArrayList<>();
		numbers.add(1);
		numbers.add(2);
		numbers.add(3);
		numbers.add(4);
		numbers.add(5);
		
		numbers.removeIf(number -> number % 2 == 0);

		System.out.println("결과 (람다식): " + numbers);
	}
}

숫자를 넣어주는 부분을 제외하고 숫자를 필터링하는 부분만 보면 확실히 코드가 줄어들었습니다. 그리고 removeIf() 메서드 안에 전달된 저 하나의 식을 마치 매개변수를 넘기듯이 넘겼습니다. 이러한 것을 프로그래밍 언어에서는 일급 객체라고 부릅니다. 람다식의 문법은 위와 같이 (인자) -> 명령문 과 같이 정의할 수 있으며 인자는 없을 수도, 더 많을 수도 있고 명령문에는 블록 본문이 들어갈 수 있거나 생략될 수 있습니다. 이렇게 익명 클래스와 람다는 서로를 대체할 수 있습니다.

 

그리고 우리가 넘긴 (number -> number % 2 == 0) 이 람다식(혹은 익명 클래스)은 함수형 인터페이스의 구현을 제공합니다. 그러면 함수형 인터페이스는 무엇일까요?

 

함수형 인터페이스는 추상 메서드를 적어도 한 개는 선언한 인터페이스를 의미합니다. 그리고 이러한 기능을 하는 인터페이스에는 @FunctionalInterface라는 어노테이션을 달아줍니다. 물론 생략이 가능합니다. 하지만 생략하게 된다면 함수형 인터페이스의 형식을 깰 수 있으므로 달아주도록 합니다.

image

우리가 넘겨준 람다식을 받는 Predicate에도 test()라는 추상 메서드가 선언되어 있습니다. 매개변수를 받으면 그것의 참 거짓을 판단해주는 추상 메서드가 있습니다. 자바에서는 기본적으로 여러 가지 함수형 인터페이스를 제공하는데 이 덕분에 우리가 따로 함수형 인터페이스를 선언해야할 수고를 덜어줍니다. 그리고 이러한 함수형 인터페이스에는 스트림의 중간 연산 메서드에서도 지원하기에, 스트림과 람다식을 조합하여 복잡한 명령문을 여러 줄 작성할 수고를 덜어줍니다.

 

그리고 우리가 사용하는, 혹은 우리가 만든 함수형 인터페이스와 메서드의 시그니쳐와 호환이 된다면 메서드 레퍼런스를 사용해서 더 간단하게 코드를 작성할 수 있습니다.

Book book = bookRepository.findByName(request.getBookName())
				.orElseThrow(IllegalArgumentException::new);

강의에서 우리는 위와 같은 코드를 작성했는데요. IllegalArgumentException::new 이 부분이 메서드 레퍼런스를 사용한 예시입니다. orElseThrow() 메서드는 내부를 들어가보면 Supplier라는 함수형 인터페이스를 가지고 있습니다. 이 함수형 인터페이스는 입력값은 없고 출력만 있는 함수형 인터페이스입니다. 우리는 Exception을 던지면서 아무런 메세지를 넘겨준 적이 없죠? 이게 메서드의 시그니쳐와 함수형 인터페이스와의 호환이 된다는 조건이 맞았기 때문입니다. 반대로 그러면 IllegalArgumentException("msg")를 사용하고 싶으면 어떻게 될까요?

User user = userRepository.findByName(request.getUserName())
				.orElseThrow(() ->new IllegalArgumentException("울랄라"));

아쉽게도 메서드 레퍼런스는 사용할 수 없고 람다식을 메서드 레퍼런스로 바꿀 수 있다면 바꿀 수 있다고 알려주는 인텔리제이도 아무런 시그널을 주지 않게 됩니다.

 

오늘은 Java 8에서 추가된 여러 기능들 중 함수형 프로그래밍과 연관이 있는 것들에 대해 알아보았습니다.

 

좋은 밤 되시고, 훌륭한 개발자가 되기 위해 노력하는 워밍업 클러버 여러분들 화이팅입니다.

댓글을 작성해보세요.