728x90
람다식
@2024.04.24
람다식(Lambda Expression)
💡
자바 8부터 도입되었으며, 함수적 인터페이스의 인스턴스를 생성하는 표현식
주로 익명 함수의 형태로 사용된다.
@기본 구조
(매개변수 리스트) -> {람다 몸체}
주로 익명 함수의 형태로 사용된다.
@기본 구조
(매개변수 리스트) -> {람다 몸체}
- 함수형 프로그래밍의 개념이 객체 지향 프로그래밍 언어에 통합된 것
- 람다식 구성 요소
- 매개 변수 리스트
- 함수가 받아들이는 매개 변수들의 목록
- 화살표 ->
- 매개 변수와 람다 몸체를 구분하는 기호
- 람다 몸체
- 실제 람다식의 실행 부분
- 매개 변수를 사용하여 특정 작업을 수행하거나 그 결과를 반환 가능
- 매개 변수 리스트
- 특징
- 익명성
- 이름이 없기 때문에 익명으로 사용
- 함수형 인터페이스와 연결
- 람다 함수는 함수형 인터페이스의 구현체로 사용
- 함수형 인터페이스란 오직 하나의 추상 메서드를 가진 인터페이스
- 간결성과 표현력
- 코드를 간결하게 만들고, 명확한 표현을 가능하게 한다.
- 지연 실행
- 람다식은 실행될 필요가 있을 때만 코드가 실행되므로, 성능 향상에 도움
- 익명성
- 주의 사항
- 모든 상황에서 익명 클래스를 완전히 대체하지 않는다!
- 함수적 인터페이스에 대해서만 사용 가능
- 추상 메서드가 한 개만 있기에 컴파일러가 타입 추론이 가능
- 인터페이스에 여러 개의 추상 메서드가 있을 경우, 람다식으로 표현 불가능
- 함수적 인터페이스에 대해서만 사용 가능
- 타입 추론
- 자바 컴파일러는 대부분의 경우, 매개변수 타입을 추론 가능
- 따라서 타입을 명시적으로 선언하지 않아도 된다.
- 매개변수 괄호
- 매개변수가 하나의 표현식일 경우 괄호의 필요가 없다.
- 매개변수가 여러 개일 경우에는 괄호가 필요하다.
- 블록 사용
- 람다식 본문이 하나의 표현 식인 경우 중괄호가 필요 없다.
- 여러 줄의 코드를 포함하는 경우에는 중괄호를 사용해야 한다.
- 모든 상황에서 익명 클래스를 완전히 대체하지 않는다!
람다식 구현과 활용
- 매개 변수가 없을 경우
- () → { 동작 }
- 한 개의 매개 변수만 있을 경우 → 괄호 생략 가능!
- (a) → { 동작 } or a → { 동작 }
- 두 개 이상의 매개변수가 있을 경우
- (a, b) → { 동작 }
- 단일 표현식 경우 → 중괄호 생략 가능!
- (a, b) → a + b
- 복잡한 동작 또는 여러 줄의 코드
- (a, b) → { 동작 }
- 반환 값없음
- () → System.out.println(”안녕하세요”);
- 반환 값있음
- () → Math.random();
- 람다식은 주로 컬렉션의 스트림 API, 스레드 생성, 이벤트 리스너 등 다양한 곳에서 사용
- 예시 1
- 리스트의 각 요소 출력
import java.util.Arrays; import java.util.List; List<String> items = Arrays.asList("Apple", "Banana", "Cherry"); // 람다식을 이용하여 리스트의 각 요소 출력 items.forEach(item -> System.out.println(item));
- 예시 2
- 스레드 생성
// 익명 클래스를 이용하여 스레드 생성 및 실행 new Thread(new Runable(){ @Override public void run(){ for (int i =0; i < 5; i++){ System.out.println(i); } } } ).start(); // 람다식을 이용하여 스레드 생성 및 실행 new Thread(() -> { for (int i =0; i < 5; i++){ System.out.println(i); } }).start()
람다식과 인터페이스
함수적 인터페이스(Functional Interface)
💡
단 하나의 추상 메서드를 가진 인터페이스로, 자바 8부터 @FunctionalInterface 애너테이션을 사용하여 함수적 인터페이스임을 명시적으로 표현 가능하다.
- 람다식을 통해 인터페이스의 추상 메서드 구현을 제공
- 예시
- Comparator 인터페이스를 활용하여 문자열 리스트 정렬
List<String> names = Arrays.asList("Alic", "Bob", "Charlie"); // 방법 1 : 익명 클래스 활용하여 Comparator 구현 names.sort(new Comparator<String>() { @Override public int compare(String s1, String s2){ return s1.compareTo(s2); } }); System.out.println(names); // [Alice, Bob, Charlie] // 방법 2 : 람다식을 사용하여 Comparator 구현 names.sort((String s1, String s2) -> s1.compareTo(s2)); System.out.println(names); // [Alice, Bob, Charlie]
사용자 정의 함수적 인터페이스
💡
자바 표준 API에 포함되지 않으며, 사용자가 필요에 따라 만드는 함수적 인터페이스
- 특징
- 단일 추상 메서드
- 함수적 인터페이스는 단 하나의 추상 메서드를 가져야 한다.
- @FunctionalInterface 애노테이션
- 인터페이스가 함수적 인터페이스임을 명시적으로 표시
- 컴파일러는 애노테이션이 있는 인터페이스에 두 개 이상의 추상 메서드가 있는지 검사
- 기본 메서드와 정적 메서드 지원
- 자바 8부터는 인터페이스 내에 기본 메서드와 정적 메서드를 정의 가능
- 재사용과 유지 보수 용이
- 일반적인 작업을 위한 인터페이스를 정의
- 재사용이 가능함으로써 코드의 중복을 줄이고 유지 보수를 용이
- 단일 추상 메서드
- 함수형 인터페이스를 구현할 때, 람다 표현식을 사용하여 간결하게 표현 가능
⇒ 별도의 클래스를 만들지 않고도 인터페이스의 구현체를 직접 정의하고 사용 가능
⇒
Implement 키워드를 사용하지 않고 람다 표현식을 통해 함수형 인터페이스의 구현체를
정의하고 사용 가능// 사용자 정의 함수적 인터페이스 생성 @FunctionalInterface public interface MyInterface { int method(int x); } public class MyFunctionalTest{ public static void main(String[] args){ MyInterface f; // 익명 함수로 추상 메서드 구현 및 구현 객체 생성 f = new MyFunctionalTest() { @Override public void method(int x){ int result = x * 5; System.out.println(result); } }; f.method(10); // 람다 함수로 추상 메서드 구현 및 구현 객체 생성 f = x -> { int result = x*5; System.out.println(result); } }
- 예시
- 두 정수의 합, 차, 곱, 나눗셈을 각각 계산하는 람다식
- add, subtract, multiply, divides ⇒ 인터페이스의 구현체
// 사용자 정의 함수적 인터페이스 생성 @FunctionalInterface public interface IntBinaryOperation { int apply(int a, int b); } // IntBinaryOperation 인터페이스의 구현체를 가리키도록 람다 표현식으로 초기화 // 람다식은 apply() 추상 메서드를 메서드 오버라이딩 한 것 IntBinaryOperation add = (a, b) -> a + b; IntBinaryOperation subtract = (a, b) -> a -b; IntBinaryOperation multiply = (a, b) -> a * b; IntBinaryOperation divide = (a, b) -> a / b; System.out.println("10 + 5 = " + add.apply(10, 5)); // 15 System.out.println("10 - 5 = " + subtract.apply(10, 5)); // 5 System.out.println("10 * 5 = " + multiply.apply(10, 5)); // 50 System.out.println("10 / 5 = " + divide.apply(10, 5)); // 2
변수 범위와 람다식
💡
람다식에서 사용되는 지역 변수는 접근이 가능하다.
단, 이 지역변수는 반드시 final 이거나 사실상 fianl인 변수여야 한다.
단, 이 지역변수는 반드시 final 이거나 사실상 fianl인 변수여야 한다.
- 사실상 fianl인 변수 : final로 선언되지 않았어도 해당 변수에 재할당이 발생되지 않는 경우
int x = 10; System.out.println("람다에서 파이널로 사용."); Runnable r = () -> { System.out.println("x : " + x); }; x = 20; // 람다식에서 사용된 지역변수므로, 해당 변수에 재할당 불가능 r.run()
- 람다식이 실행되는 시점에는 람다식 안 메서드가 이미 실행을 마치고, 해당 지역 변수가 소멸
- 람다식은 복사된 변숫값으로 작동되며, 이는 변수가 변경되지 않는 가정하에 안전하게 작동 가능
람다와 자바 표준 API
💡
주요 함수적 인터페이스에 대해서 알아보자.
- Consumer<T>
- 하나의 매개변수를 받아서 아무런 반환 값 없이 처리
- 주로 입력값을 소비하는 작업을 수행하는 데 사용
- void accept(T t) 구현
- 주어진 입력값을 사용하여 어떤 처리하며 결과는 반환하지 않는다.
- 주로 입력값을 변경하거나 출력하는 작업에 사용
Consumer<String> printer = s -> System.out.println(s); printer.accept("hello~ yoon-D"); // hello~ yoon-D Consumer<String> conA = s -> System.out.println(s + "aaaa"); Consumer<String> conB = s -> System.out.println(s + "bbbb"); conA.accept("yoon-D"); // yoon-Daaaa conB.accept("yoon-D"); // yoon-Dbbbb //Consumer 의 andThen 메소드 활용 ConA가 실행되고, conB가 실행된다. Consumer<String> conAB = conA.andThen(conB); conAB.accept("hello "); // hello aaaa와 hello aaaabbbb
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Consumer<String> printConsumer = name -> System.out.println(name); names.forEach(printConsumer);
- Supplier<T>
- 매개변수 없이 특정 타입의 객체를 반환하는 함수
- 주로 객체를 생성하거나, 계산에 필요한 초깃값을 제공하는 데 사용
- T get() 구현
- 매개변수 없이 특정 타입의 객체를 반환
Supplier<Double> randomSupplier = () -> Math.random(); System.out.println(randomSupplier.get()); IntSupplier intSupplier = () -> (int) (Math.random() * 6) + 1; System.out.println(intSupplier.getAsInt());
- Function<T, R>
- 하나의 매개변수를 받아서 다른 타입의 결과를 반환하는 함수
- 주로 입력 값을 변환하거나 매핑하는데 사용
- R apply(T t) 구현
- 주어진 입력 값에 대해 어떤 처리를 하고 그 결과를 반환
Function<String, Integer> lengthFunc = s -> s.length(); System.out.println(lengthFunc.apply("hello carami!!"));
- Preditcate<T>
- 매개 변수 하나를 받아 주어진 조건에 따라 boolean 값을 반환
- 주로 컬렉션의 요소를 필터링하거나, 어떤 객체가 특정 조건을 만족하는지 검사하는 데 사용
- boolean test(T t) 구현
- 주어진 객체에 대해 조건을 검사하고 그 결과를 boolean 값으로 반환
Predicate<Integer> isPositive = x -> x > 0; System.out.println(isPositive.test(10)); System.out.println(isPositive.test(-10));
- UnaryOperatar<T>
- 입력과 출력이 동일한 타입을 가지는 연산을 수행
- apply() 구현
- 주로 자료 구조나 컬렉션의 요소를 변환 시키는데 사용
UnaryOperator<Integer> square = x -> x * x; System.out.println(square.apply(5));
- BiFunction<T, U, R>
- 두 개의 입력 매개변수를 받고 결과를 반환하는 함수를 표현
- T와 U는 매개 변수의 타입을 나타내고, R은 결과의 타입을 나타낸다.
- R apply(T t, U u) 메서드 구현
- 주로 두 개의 입력 값을 받아 어떤 처리를 하고 그 결과를 반환하는 데 사용
//BiFunction //두 개의 입력을 받고 하나의 결과를 반환 BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; System.out.println(add.apply(5, 6));
람다식 확장
메소드 참조
💡
람다식의 확장으로, 특정 메서드의 직접 참조를 통해 코드의 간결성을 더욱 향상시킨다.
- 람다식이 한 메서드만을 호출하는 경우, 람다식 대신 메서드 참조를 사용
- 메소드 참조의 형태
- 정적 메서드 참조
- 클래스::정적 메서드
- Math::max → Math 클래스의 정적 메서드 max를 참조
//BiFunction<Integer, Integer, Integer> maxFunc =(a,b)->Math.max(a,b); BiFunction<Integer, Integer, Integer> maxFunc = Math::max; System.out.println(maxFunc.apply(3, 5));
- 인스턴스 메서드 참조
- 객체 참조 변수::인스턴스 메서드
- String 객체 str에 대해 str::length → legth() 참조
//2. 인스턴스 메서드 참조 String str = "Hello world!"; Supplier<Integer> lengthFunc = str::length; System.out.println(lengthFunc.get());
- 임의 객체의 인스턴스 메서드 참조
- 클래스::인스턴스 메서드
- String::length → 모든 String 객체의 length() 참조
//3. 임의 객체의 인스턴 메서드 참조 List<String> words = Arrays.asList("Hello", "world", "java", "carami"); List<Integer> lengths = new ArrayList<>(); Function<String, Integer> lengthFunc2 = String::length; for (String word : words) { lengths.add(lengthFunc2.apply(word)); } System.out.println(lengths);
- 생성자 참조
- 클래스::new
- ArrayList::new → ArrayList 생성자 참조
//4. 생성자 참조 Supplier<List<String>> listSupplier = ArrayList::new; List<String> list = listSupplier.get(); list.add("hello"); list.add("hahaha"); list.add("carami"); System.out.println(list);
- 클래스::new
- 정적 메서드 참조
728x90