람다식이란


람다식(Lambda Expression)이란 함수를 하나의 식(expression)으로 간단하게 표현한 것이다. 함수를 람다식으로 표현하면 메소드의 이름이 생략 가능하기 때문에 람다식은 익명 함수(Anonymous Function)의 한 종류라고 볼 수 있다.

 

람다식의 특징


  • 람다식 내에서 사용되는 지역변수는 final이 붙지 않아도 상수로 간주된다.
  • 람다식으로 선언된 변수명은 다른 변수명과 중복될 수 없다.

 

람다식의 장단점


장점

  • 코드를 간결하게 만들 수 있다.
  • 식에 개발자의 의도가 명확히 드러나 가독성이 높아진다.
  • 함수를 만드는 과정없이 한 번에 처리할 수 있어 생산성이 높아진다.
  • 병렬 프로그래밍이 용이하다.

단점

  • 람다를 사용하면서 만든 익명 함수는 재사용이 불가능하다.
  • 디버깅이 어렵다.
  • 람다를 남발하면 비슷한 함수가 중복 생성되어 코드가 지저분해질 수 있다.
  • 재귀로 만들 경우에 부적합하다.

 

람다식 작성하는 방법


  • 메서드의 이름과 반환 타입을 제거하고 '->' 를 블록 {} 앞에 추가
  • 구현될 메소드가 한 줄이라면 메소드 블록을 나타내는 중괄호 생략 가능
  • 반환값이 있는 경우 식이나 값만 적고 return문 생략 가능
  • 매개변수의 타입이 추론 가능하면 생략 가능
//람다식 변경 전
int max(int a, int b) {
	return a > b ? a : b;
}

//람다식 변경 후
(int a, int b) -> {
	return a > b ? a : b;
}

//중괄호, return문 생략 기능
(int a, int b) -> a > b ? a : b

//매개변수 타입 생략 가능
(a, b) -> a > b ? a : b;

 

람다식의 예


메서드 람다식 비고
int max(int a, int b) {
          return a > b ? a : b;
}
(a, b) -> a > b ? a : b  
int printVar(String name, int i) {
          System.out.println(name+"="+i);
}
(name, i) -> System.out.println(name+"="+i)  
int square(int x) {
          return x * x;
}
x -> x * x 매개변수 하나일 시는 가로 생략 가능
int roll() {
          return (int) (Math.random() * 6);
}
() -> (int) (Math.random() * 6)  

 

함수형 인터페이스


람다식과 깊은 관계가 있는 인터페이스가 있다. 바로 함수형 인터페이스이며, 람다식을 다루기 위한 인터페이스이다.  Java에서 람다 표현식으로 구현이 가능한 인터페이스는 오직 추상 메소드가 1개뿐인 인터페이스만 가능하다. 그렇기 때문에 추상 메소드가 1개 만을 선언할 수 있는  함수형 인터페이스를 알고 있어야한다. 

 

//함수형 인터페이스
//추상 메소드를 한개 이상 추가할 시 에러 발생
@FunctionalInterface
interface LambdaPrinter {
    public void print(String msg);
}

public class test {
    public static void main(String[] args) {

        //람다식 사용 안한 익명함수
        LambdaPrinter p1 = new LambdaPrinter() {
            @Override
            public void print(String msg) {
                System.out.println(msg);
            }
        };
        p1.print("Hello Lambda!");

        //람디식 사용한 익명함수
        LambdaPrinter p2 = (String msg) -> System.out.println(msg);
        p2.print("Hello Lambda!!");
    }
}

 

java.util.function 패키지


java.util.function 에는 자주 사용될 것 같은 함수형 인터페이스가 이미 정의되어 있다.

  메서드 설명
java.lang.Runnable void run() 매개변수 없음, 반환값 없음
Supplier<T> T get() 매개변수 없음, 반환값 있음
Consumer<T> void accept(T t) 매개변수 있음, 반환값 없음
Function<T, R> R apply(T t) 하나의 매개변수를 받아서 결과를 반환
Predicate<T> boolean test(T t) 하나의 매개변수 boolean 타입을 반환
//Supplier 사용 예시
Supplier<Integer> s = () -> (int)(Math.random()*100) + 1;
System.out.print(s.get()); //68(랜덤한 숫자)

//Consumer 사용 예시
Consumer<Integer> c = i -> System.out.print(i);
c.accept(5); // 5

//Predicate 사용 예시
Predicate<Integer> p = i -> i%2==0;
System.out.print(p.test(2)); //true

//Function 사용 예시
Function<Integer, Integer> f = i -> i/10*10;
System.out.print(f.apply(20)); // 20

 

컬렉션 프레임웍과 함수형 인터페이스를 같이 사용하면 기능 구현을 더욱더 편리하게  할 수 있게 된다.

인터페이스 메서드 설명
Collection boolean removeif(Predicate<E> fillter) 조건에 맞는 요소를 삭제
Iterable void forEach(Consumer<T> action) 모든 요소에 작업 action을 수행
List void repalceAll(UnaryOperator<E> operator) 모든 요소를 변환하여 대체
Map void forEach(Biconsumer<K, V> action) 모든 요소에 작업 action을 수행
ArrayList<Integer> list = new ArrayList<>();
for(int i  = 0 ; i < 10 ; i++) {
       list.add(i);
}

//list의 모든 요소 출력
list.forEach(i -> System.out.println(i));

//list에서 2 또는 3의 배수 제거
list.removeIf(x -> x%2==0 || x%3==0);
list.forEach(i -> System.out.println(i));

//list의 각 요소에 10 곱하기
list.replaceAll(i -> i*10);
list.forEach(i -> System.out.println(i));

HashMap<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");

//map의 모든 요소를 {k, v}형식으로 출력
map.forEach((k, v) -> System.out.println("{" + k + ", " + v + "}"));

 

메서드 참조


 

메서드 참조는 람다 표현식에서 불필요한 매개변수를 제거하여 사용할 수 있도록 하여 람다식을 더 간단하게 표현할 수 있도록 도와준다. 기본적인 문법은 클래스이름::메소드이름 또는 참조변수이름::메소드이름이며 참조가능한 메서드는 일반 메서드, static 메서드, 생성자가 있다.

//static 메서드 참조
//Function<String, Integer> f = (String s) -> Integer.parseInt(s);
Function<String, Integer> f = Integer::parseInt;
System.out.println(f.apply("1"));

//일반 메서드 참조
//Consumer<String> c = (c) -> System.outprintln(c); 
Consumer<String> c = System.out::println; 
consumer.accept("Hello World!!");

//생성자 메서드 참조
//Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new;
System.out.println(s.get());

그 외 더 다양하게 메서드 참조를 사용할 수 있다.

//메서드 참조로 생성자가 있는 객체 생성
//Function<Integer, MyClass2> f1 = (i) -> new MyClass2(i);
Function<Integer, MyClass2> f1 = MyClass2::new;
System.out.println(f1.apply(1)); //MyClass2@39ba5a14

//메서드 참조로 int배열 생성
//Function<Integer, int[]> f2 = (i) -> new int[i];
Function<Integer, int[]> f2 = int[]::new;
System.out.println(f2.apply(5).length); //5

//컬렉션과 메서드 참조를 같이 사용하여 리스트 값 출력 
//forEach는 함수형 인터페이스인 Consumer를 매개변수로 받기 때문에 System.out 메서드 참조를 사용할 수 있다
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//list.forEach((i) -> System.out.print(i));
list.forEach(System.out::print); //1 2 3 4 5

'JAVA' 카테고리의 다른 글

String, StringBuffer, StringBuilder의 차이점  (0) 2024.04.24
Stream  (0) 2022.07.25
enum  (0) 2022.01.16
JAVA 실행과정 & JVM  (0) 2022.01.09
예외 처리  (0) 2022.01.05

+ Recent posts