•
자바의 함수는 정적 메서드와 같은 의미의 수학적인 함수처럼 사용되며 부작용을 일으키지 않는 함수를 의미한다.
•
자바 8에서는 함수를 새로운 값의 형식으로 추가했다. 즉 함수를 값처럼 취급하는 것이다.
•
먼저 자바 프로그램에서 조작할 수 있는 값을 생각해보자.
◦
int, double 형식의 기본값, String name의 name, 즉 객체의 참조도 값이다. 배열도 객체의 참조로서 값으로 넘겨줄 수 있다.
◦
그런데 왜 함수가 필요할까? 프로그래밍 언어의 핵심은 값을 바꾸는 것이다.
◦
전통적으로 프로그래밍 언어에서는 이 값으로 전달될 수 있는 이러한 것들을 일급 객체라고 부른다.
◦
이렇게 값을 전달할 수 없는 구조체는 2급 객체다. 위에서 언급한 값은 모두 일급 객체지만 메서드, 클래스 등은 2급 객체에 해당한다.
•
인스턴스화한 결과가 값으로 귀결되는 클래스를 정의할 때 메서드를 아주 유용하게 활용할 수 있지만 그 자체로 값이 될 수 없다.
•
런타임에 메서드를 전달할 수 있다면 즉, 메서드를 일급 객체로 만든다면 프로그래밍에 유용하게 사용할 수 있다.
1. 메서드와 람다를 일급 클래스로
•
메서드 참조 :: 를 사용해서 메서드를 값으로 직접 전달할 수 있다.
•
디렉터리에서 모든 숨겨진 파일을 필터링하는 코드
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isHidden(); // 숨겨진 파일 필터링
}
});
Java
복사
•
자바 8에서는 아래처럼 구현할 수 있다.
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
Java
복사
•
자바 8에서는 더 이상 메서드가 2급 객체가 아닌 일급 객체라는 것이다.
•
기존에 객체 참조를 이용해서 객체를 이리저리 주고 받았던 것처럼 자바 8에선 File::isHidden을 이용해서 메서드 참조를 만들어 전달할 수 있다.
람다: 익명 함수
•
자바 8에서는 메서드뿐만아니라 람다를 포함한 함수들도 값으로 취급할 수 있다.
◦
(int x) → x + 1, 즉 x라는 인수로 호출하면 x + 1을 반환하는 동작을 수행하도록 코드를 구현할 수 있다.
◦
MyMathUtils라는 클래스를 만든 다음 클래스 내부에 add1이라는 메서드를 정의해서 Utils::add1을 만들 수 있으므로 굳이 필요한가? 싶을 수 있다.
◦
메서드를 직접 정의할 수도 있지만 이용할 수 있는 편리한 클래스나 메서드가 없을 때 새로운 람다 문법을 이용하면 굳이 정의하지 않고도 더 간결하게 코드를 구현할 수 있다.
◦
람다 문법 형식으로 구현된 프로그램을 함수형 프로그래밍, 즉 함수를 일급값으로 넘겨주는 프로그램을 구현한다라고 한다.
2. 코드 넘겨주기 : 예제
•
Apple 클래스와 getColor 메서드가 있고 Apples 리스트를 포함하는 변수 inventory가 있다고 가정하자.
•
이때 모든 녹색 사과를 선택해서 리스트를 반환하는 프로그램을 구현하고자 한다. 이처럼 특정 항목을 선택해서 반환하는 동작을 filter라고 한다.
•
자바 8 이전이라면 filterGreenApple이라는 메서드를 구현했을 것이다.
public static List<Apple> filterGreenApples(List <Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory) {
if (GREEN.equals(apple.getColor())) {
result.add(apple);
}
} return result;
}
Java
복사
•
만약 무게가 150g 이상인 것만 필터링하고 싶다면 다음과 같이 또 구현했을 것이다.
public static List<Apple> filterHeavyApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if(apple.getWeight() > 150) {
result.add(apple);
}
}
return result;
}
Java
복사
•
소프트웨어 공학적인 측면에서 복붙의 단점은 복붙한 코드에 버그가 있다면 복붙한 모든 코드를 고쳐야한다는 것이다.
•
이 예제에서 두 메서드는 단순히 조건문만 다르고 모든 부분이 같다.
•
자바 8에서는 코드를 인수로 넘겨줄 수 있으므로 filter 메서드를 중복으로 구현할 필요가 없다.
public static boolean isGreenApple(Apple apple) {
return GREEN.equals(apple.getColor());
}
public static boolean isHeavyApple(Apple apple) {
return apple.getWeight() > 150;
}
// 명확히 하기위해 적어놓음
// 보통은 java.util.function에서 임포트함
public interface Predicate<T> {
boolean test(T t);
}
// 메서드가 p라는 이름의 프레디케이트 파라미터로 전달됨
static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p {
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory) {
if(p.test(apple)) {
result.add(apple);
}
}
return result;
}
// 아래처럼 메서드를 호출할 수 있다.
filterApples(inventory, Apple::isGreenApple);
filterApples(inventory, Apple::isHeavyApple);
Java
복사
3. 메서드 전달에서 람다로
•
하지만 한 두 번만 사용할 메서드를 매번 정의하는 것은 귀찮은 일이다. 자바 8에서는 이러한 문제를 간단하게 해결할 수 있다.
filterApples(inventory, (Apple a) -> GREEN.equals(a.getColor()));
filterApples(inventory, (Apple a) -> a.getWeight() > 150);
filterApples(inventory, (Apple a) -> a.getWeight() < 80 || RED.equals(a.getColor()) );
Java
복사
•
위 코드처럼 한 번만 사용할 메서드는 굳이 정의할 필요 없이 익명 람다를 사용해서 전달해줘도 무방하다.
•
만약 람다가 몇 줄 이상으로 길어진다면 익명 람다보다는 코드가 하는 일을 잘 설명하는 이름을 가진 메서드를 정의하고 메서드 참조를 활용하는 것이 바람직하다.