람다
를 이해하기 위해서는 함수형 프로그래밍 방식을 이해 해야 한다. 람다는 함수형 프로그래밍 방식을 위한 자바 기술이다.
관련블로그는 여기를 참고하길
: https://velog.io/@mooh2jj/왜-함수형-프로그래밍인가
람다는 λ
라고 표기하며 원래는 수학기호로 1930년대 수학자 알론소 처치의 람다 계산식에서 시작되었다고 한다.
최신의 프로그램언어에서 사용되는 람다식 혹은 람다함수는 함수형 언어의 특징에서 나온것으로 나중에 한번이상 실행할 수 있는 코드블록을 말하며 실제 구현에는 익명 함수형태로 사용한다.
자바에서 쓰는 람다는 함수형 인터페이스와 함수형 프로그래밍이 적용된 새로운 자바 API를 이해하고 사용해야 한다.
1) 익명: 보통의 메서드와 달리 이름이 없으므로 익명이라 표현한다. 구현해야 할 코드에 대한 코드 걱정거리가 하나 줄어든 것이다.
2) 함수: 람다는 특정 클래스에 종속되지 않기에 메서드가 아니라 함수
라 불린다.
하지만 메서드처럼 파라미터 리스트, 바디, 반환형식, 가능한 예외 리스트를 포함한다.
3) 전달: 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
4) 간결성: 익명 클래스처럼 많은 코드를 구현할 필요가 없다.
(매개변수, ...) -> { 실행문 }
람다식 예)
public int sum(int a, int b) {
return a + b;
}
(a, b) -> { a + b }; // 중괄호 빼도 된다.
✔ 자바에서는 람다는 사실 명확하게 보면 익명 클래스
로 생각할 수 있다. 위에서 봤던 람다식은 다음 익명 클래스와 동일하게 생각할 수 있다.
new Object() {
int sum(int a, int b) {
return a + b;
}
}
✔ 추상메서드가 하나인 인터페이스에서(대표적으로 함수형 인터페이스, Runnable 인터페이스)
람다식이 유용하다.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Start Thread");
Thread.sleep(1000);
System.out.println("End Thread");
}
});
Thread thread = new Thread(() -> {
System.out.println("Start Thread");
Thread.sleep(1000);
System.out.println("End Thread");
});
람다는 기본적으로 함수의 구조로 되어 있으며 자바에서는 ->
와 같이 화살표 형태의 기호를 이용해 매개변수를 함수 바디로 전달하는 형태를 취한다.
( parameters ) -> expression body // 인자가 여러개 이고 하나의 문장으로 구성
( parameters ) -> { expression body } // 인자가 여러개 이고 여러 문장으로 구성
() -> { expression body } // 인자가 없고 여러 문장으로 구성
() -> expression body // 인자가 없고 하나의 문장으로 구성
하지만 람다는 매개변수만 전달하지 않는다. 메서드, 생성자도 전달할 수 있다.
메서드/생성자 레퍼런스는 이 함수형 인터페이스를 구현하는 더 간결한 방법이다.
::
을 사용해 메서드 레퍼런스를 사용할 수 있다.
클래스 :: 정적메서드
클래스 :: 인스턴스메서드
인터페이스 :: 인스턴스메서드
print(msg -> System.out.println(msg));
print(System.out::println);
메서드 레퍼런스와 유사하지만 ::new
를 사용한다.
기본적으로는 메서드 레퍼런스와 같이 메서드에 대한 참조를 전달하는 것이지만 단순히 메서드에 대한 참조가 아니라 새로운 객체를 생성한다
는 점에서 차이가 있다.
List<String> labels = ...
Stream<Button> stream = labels.stream().map(Button::new);
List<Button> buttons = stream.collect(Collectors.toList());
1,2,3 세개의 값을 가지고 있는 배열의 각 원소값을 가져와 제곱값을 출력하는 코드다.
기존의 for문을 이용하지 않고 람다를 사용하면 한줄로 쭈욱 처리할 수 있다.
위의 스트림(stream)
은 데이터의 흐름
으로서 원본 데이터가 변하지 않는 특성으로 람다를 활용한 자바8의 기술이다.
컬렉션 순회
를 많이 쓰는 자바는 Stream()을 써서 Stream의 메서드들은 중간연산들의 산출물을 매개변수삼아 람다
를 많이 사용한다.
자세한 내용은 이 블로그에 정리했었다.
Java Stream, Collector 왜 쓰는 거야?
스트림에서는 게으른(Lazy) 특성으로 2 가지로 나누어진다. 중간연산, 최종연산이다.
중간연산은 선택적이다. 안 쓸 수도 있고 쓸 수도 있다.
Arrays.asList(1,2,3).stream()
.map(i -> i*i)
.forEach(System.out::println);
결과
1 4 9
함수형 프로그램을 구현하다 보면 구조적으로 비슷한 유형의 함수형 인터페이스
가 필요한 경우가 많이 발생 합니다.
함수형 인터페이스의 특징은 추상 메서드가 하나 정의
되어 있다는 것입니다.
이 경우 java.util.function 패키지에 정의된 함수형 인터페이스들을 사용할 수 있다.
특히 다음에 배우게 될 Stream API
를 제대로 이해하기 위해서는 다음의 함수형 인터페이스를 잘 알아 두어야 한다.
※ 이미지 출처 : https://dinfree.com/lecture/language/112_java_9.html
이들 함수형 인터페이스는 타입파라미터를 가지는데 각각의 의미는 다음과 같다.
T: 첫번째 인자 Type
U: 두번째 인자 Type
R: 리턴 타입
전형적인 함수를 지원한다. 하나의 인자와 하나의 리턴을 가진다.
method는 apply()
가 있고
default method는 andThen() (Consumer 와 동일 기능) 과
compose() (andThen()과 순서만 다름) 가 있습니다.
static method는 파라메터를 그대로 리턴하는 identity()가 있습니다
Function<String, Integer> f = str -> Integer.parseInt(str);
Integer result = f.apply("10");
인자를 받아 소모하고 리턴이 없는 함수형 인터페이스.
accept() 메서드
를 추상 메소드로 선언한 함수형 인터페이스다.
가장많이 쓰이는 Stream 인터페이스의 forEach 메서드는 void forEach(Consumer<? super T> action)
로 정의되어 있으며 Cosumer 함수형 인터페이스를 인자로 가지고 있다.
Arrays.stream(strArr).sorted().forEach(str -> System.out.println(str));
인자가 없으며 리턴타입만 존재하는 형태. 순수함수에서 결과를 바꾸는건 오직 입력 뿐인데 입력이 없다는 것은 내부에서 랜덤 함수 같은것을 쓰는게 아닌이상 항상 같은 것을 리턴한다는 것을 알 수 있다.
get() 메서드
를 추상 메소드로 선언한 함수형 인터페이스다.
Supplier<String> s = () -> "msg from supplier";
String result = s.get();
거의 안쓰임
??? 추후 정리
하나의 인자와 boolean 타입의 리턴값을 가지고 있고
test() 메소드
를 추상 메소드로 선언한 함수형 인터페이스다.
and(), or(), negate(), isEqual(), not() 등 중개연산 default 메소드를 제공하며 이를 사용하여 복수의 조건을 추가할 수 있다.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
// ...
}
Predicate<String> p = str -> str.contains("msg");
boolean result = p.test("hello"); // false
Predicate<String> strContain = (str) -> str.contains("aljja");
Predicate<String> strStartsWidth = (str) -> str.startsWith("http");
Predicate<String> strEndsWidth = (str) -> str.endsWith("com");
String url = "aljjabaegi.tistory.com";
boolean result = strContain.or(strStartsWidth).and(strEndsWidth).negate().test(url);
System.out.println(result); /** false */