공부방/JAVA

람다식 기본 - 백기선 자바라이브스터디

EVO. 2023. 12. 2. 17:47

람다식 사용법

함수형 인터페이스 : 하나의 추상 메서드만 포함하는 인터페이스는 함수형 인터페이스 이다. 함수형 인터페이스의 경우 람다 표현식을 통해 인터페이스의 객체를 생성할 수 있다. 

 

Lamda 표현식을 사용하면서 익명 내부 클래스에 대한 너무 많은 정의를 피하고, 코드를 더욱 간결하게 만들고, 의미 없는 코드 쌍을 제거하고 핵심 로직만 남긴다.

 

 

요약: 추상 메서드가 하나만 포함된 인터페이스의 경우 Lamda 표현식을 사용하여 코드를 단순화 하여 객체를 생성할 수 있다.

 

람다식 생략 가능한 것

  • 매개변수 유형 생략 가능
  • 메서드 본문에 코드 한 줄만 있는 경우 중괄호 반한과 세미콜론 생략 가능
  • 메서드에 매개변수 하나만 있는 경우 괄호 생략 가능 

https://babgeuleus.tistory.com/116

 

익명객체와 람다는 결국엔 같은걸까?

저번 글에서 아래 두개의 코드는 같은 일을 수행한다고 글을 쓴 적이 있었다. 즉, 이 둘을 보면 컴파일러는 람다식을 익명객체로 변환한다고 볼 수 있다고 생각할 것이다. 정답을 알기 전 이 둘

babgeuleus.tistory.com

함수형 인터페이스

함수형 인터페이스는 하나의 추상 메서드만 있는 인터페이스를 말하지만 다른 메서드(default, static, private)도 가질 수 있다. 따라서 이 인터페이스가 함수형 인터페이스인지 헷갈릴 수 있기 때문에 @FunctionalInterface 주석을 추가하여 검증 받으면 된다.

 

함수형 인터페이스 역할

함수형 인터페이스는 일반적으로 메서드의 매개변수 및 반환 값 유형으로 사용될 수 있다.

 

메서드 매개변수와 반환 값 유형으로 함수형 인터페이스 활용

 

하지만 직접 함수형 인터페이스를 만들어 사용할 필요가 없다. 다음과 같이 이미 만들어진 표준 함수형 인터페이스를 사용하면 된다.

 

출처: [10분 테코톡] 깃짱, 이리내의 람다와 스트림

 

 

공급자 인터페이스 (Supplier 인터페이스)

메서드 매개변수 유형으로 공급자 인터페이스를 사용하여 람다 표현식을 통해 int 배열의 최대값을 찾는 예시이다.

 

 

소비자 인터페이스(Consumer 인터페이스)

공급자 인터페이스와 달리, 데이터를 소비하고 해당 데이터 유형은 제네릭에 의해 결정된다. 여기에는 지정된 일반 유형의 데이터를 소비하는 것을 의미하는 추상메서드 void aceept(T t)가 포함되어 있다.

 

또한 default 메서드인 andThen 메서드도 있는 데 이는 데이터를 소비할 때 먼저 작업을 수행한 다음 다른 작업을 수행하여 결합을 달성한다. 이 행위를 수행하려면 두 개이상의 Lamda 표현식이 필요하다.

 

예를들어, 문자열 배열에 여러 개의 정보가 있다. "이름: xx, 성별: xx" 형식으로 정보를 인쇄한다. 이름을 인쇄하는 동작을 Lamda 인스턴스로 사용해야한다. 두개의 Consumer 인터페이스를 연결하여 출력하도록 하는 예제이다.

 

 

 

 

조건부 인터페이스 (Predicate 인터페이스)

boolean 값 결과를 얻기 위해 사용한다.

 

조건부 판단이므로 AND 또는 NOT의 세가지 논리가 있다.

 

AND논리: default 메서드 and()

 

비논리적 : not()

 

 

기능 인터페이스(Function 인터페이스)

java.util.function.Function<T,R> 인터페이스는 한 유형의 데이터를 기반으로 다른 유형의 데이터를 얻는 데 사용되며 전자를 전제 조건, 후자를 사후 조건 이라고 한다. T의 데이터 유형을 R의 데이터 유형으로 변환

 

 

 

Variable Capture

람다 표현식은 final인 지역변수 와 지역변수이지만 변경하면 안되는 것들을 사용할 수 있다. 예제를 보면서 확인해보자

 

Example

 

다음 코드는 final은 아니지만 한번만 초기화되는 변수 answer이 있다. 이 변수는 람다 표현식에서 사용할 수 있다. 이를 effective final 이라고 부른다.

 

Example 2

 

다음 코드는 final 로컬 변수를 람다 표현식에서 사용할 수 있음을 알 수가 있다.

 

Example 3

 

다음 코드는 람다표현식에서 사용되고 있는 변수는 외부에서 변경될 수(재할당할 수) 없음을 보여주는 코드이다.

 

 

Example 4

 

 

지역변수가 아닌 static이나 private인 인스턴스 변수가 사용되고 있는 코드이다. 람다표현식에 사용되며 재할당이 가능하다.

 

 

즉, 유일하게 제약되는 상황이 지역변수만이 final로 선언해서 상수로 만들거나 지역변수의 값을 재할당 하지 않아야 한다. 이러한 제약이 있는 이유는 내부적으로 인스턴스 변수와 지역변수의 태생이 다르기 때문이다. 

 

인스턴스 변수는 힙에 저장되고, 지역변수는 스택에 위치한다. 만약 재할당이 허용된다면 지역변수를 할당한 스레드가 변수를 변경하게되고 람다식을 실행하고 있는 스레드는 미처 변경된 변수가 아닌 변경되기 전 변수의 값을 사용함으로서 문제가 생길 수 있기 때문이다. static이라고 변경될 수 있지 않냐고 반문할 수 있다.

 

하지만 다른 점은 static은 힙에 저장되있어서 람다식 스레드가 힙영역의 static 변수를 참조해 변경사항을 반영하지만, 다른 스레드에서 할당한 지역변수는 스택에 존재하기 때문에 람다식의 스레드가 참조할 수 없기 때문에 이렇게 제약을 두는 것이다.

 

 

메서드,  생성자 레퍼런스

메서드 참조

  • 메서드 참조는 특정 메서드만 호출하는 람다의 축약형이라고 할 수 있다
  • 메서드 참조를 이용하면 기존 메서드 구현으로 람다 표현식을 만들 수 있다
  • 이때 명시적으로 메서드 명에 참조함으로써 가독성을 높일 수 있다

Example (정적 메서드 참조)

 

 

Example 2 (다양한 형식의 인스턴스 메서드 참조)

 

예를들어 s-> s.length()의 람다식 표현은 String::length 으로 표현이 가능하다.

 

Example 3 (기존 객체의 인스턴스 메서드 참조)

 

예를들어 람다 외부 변수 apple에 대해 () -> apple.getWeight() 는 apple::getWeight로 변경가능하다.

 

연습

Function<String,Integer> func1 = (String str) -> Integer.parseInt(str);

 

위의 람다식은 아래와 같이 메서드 참조를 이용해서 표현을 할 수가 있다.

Function<String,Integer> func1 = Integer::parseInt;

 

람다식의 일부가 생략되지만, 컴파일러는 우변의 parseInt 메서드 선언부와 좌변의 Function 인터페이스에 지정된 제네릭 타입으로 부터 형식 추론을 할 수가 있다.

 

람다  표현식의 파라미터가 2개의 타입이 같은 경우에도 메서드 참조를 이용해서 표현을 할 수 있다.

BiFunction<String, String, Boolean> biFunction = (str1,str2) -> str1.equals(str2);

BiFunction<String, String, Boolean> biFunction = String::equals;

 

 

생성자 참조

ClassName::new 처럼 클래스명과 new 키워드를 사용해서 생성자의 참조를 만들 수 있다.

 

 

매개변수를 받는 생성자는 다음과 같이 참조를 만들 수 있다.

 

인스턴스화 하지 않고 생성자에 접근할 수 있는 다양한 상황에 응용할 수 있다.

 

출처:&nbsp;https://dev-kani.tistory.com/38

 

BiFunction 함수형 인터페이스를 이용하면 생성자가 2개일때 생성자 참조로 만들 수 있다.

 

 

다만 인수가 세 개이상인 생성자에서 생성자 참조를 사용하려면 기존에 제공하는 표준 함수형 인터페이스가 없기 때문에 직접 만들어야 한다.

 

 

레퍼런스