• 람다식은 이름이 없는 익명 함수이다.
  • 람다식은 아래와 같이 매개변수, 화살표, 실행문 으로 구성된다.
(param1, param2) -> { action with params }
  • 람다 표현식은 함수형 인터페이스를 인자로 받는 메서드에만 사용 할 수 있다.

함수형 인터페이스

  • 함수형 인터페이스는 오직 하나의 추상 메서드를 갖고있는 인터페이스다.
    default, static, private method는 몇 개가 있어도 상관없다.
  • 함수형 인터페이스를 작성 할 때 @FunctionalInterface 어노테이션을 사용하면, 두 개 이상의 추상 메서드를 작성했을 때 컴파일 에러를 발생시켜 준다. 또한 해당 인터페이스가 함수형 인터페이스라는 문장이 doc파일에 추가된다.
  • 함수형 인터페이스 타입 변수에 람다식을 할당 할 수 있다.

자주 사용 하는 함수형 인터페이스

함수형 인터페이스를 직접 작성할 일은 흔치 않다. java.util.function 패키지에서는 이미 다양한 함수형 인터페이스를 제공해주고 있다. 그 중 빈번히 사용되는 몇가지 함수형 인터페이스에 대해 알아보자.

  • Predicate

    객체에 대한 참, 거짓 판단이 필요할 때 구현 한다.

  • Consumer/Supplier

    이름 그대로 소비자와 공급자의 역할을 한다.
    Consumer는 매개 변수를 받아 그 값을 소비하고, 아무것도 리턴 하지 않는다.
    Supplier는 매개 변수 없이, 리턴 값만 존재한다.

e.g. Stream API
Stream 클래스에는 함수형 인터페이스를 매개변수로 이용하는 다양한 정적 메서드들이 있다.
아래 예시는 Supplier, Predicate, Consumer 인터페이스를 매개변수로 받는 generate, filter, forEach 메서드에 람다식을 인자로 전달해 사용하는 예시이다.

Stream.generate(() -> new Random().nextInt(100) + 1)
	.filter(n -> n % 2 != 0)
	.limit(10)
	.forEach(x -> System.out.println(x));

// 위 코드는 1 ~ 100 사이의 홀수 난수를 10개 출력한다.  
  • Comparable/Comparator

    기본 자료형(e.g. int, char, byte, .. etc)은 비교 연산자(<, >, <=, >=, ==, !=)를 이용해 손쉽게 비교 할 수 있지만, 객체는 사용자가 비교 기준을 정해주지 않으면 어떤 객체가 더 높은 우선 순위를 갖는지 판단 할 수 없다.
    두 인터페이스는 객체를 비교할 수 있도록 만든다.

e.g. 기본 정렬 규칙을 정의할 때는 Comparable을 사용한다.

public class Cat implements Comparable<Cat> {

    String name;
    int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Cat 의 인스턴스를 비교할 때는 나이를 이용 할 것이다.
    @Override
    public int compareTo(Cat cat) {
        return this.age - cat.age;
    }

    @Override
    public String toString() {
        return name + ", " + age + "살";
     }
}
Cat kami = new Cat("Kami", 2);
Cat bella = new Cat("Bella", 1);
Cat kong = new Cat("Kong", 3);
Cat haYang = new Cat("HaYang", 6);

Cat[] cats = {kami, haYang, bella, kong};

// Cat 클래스의 compareTo 메서드에 정의한 규칙에 따라 정렬된다.
Arrays.stream(cats)
	.sorted()
	.forEach(System.out::println);
/*
  Bella, 1살
  Kami, 2살
  Kong, 3살
  HaYang, 6살
*/

e.g. 새로운 정렬 규칙을 정의하고 싶을 때는 Comparator을 사용한다.

Arrays.stream(cats)
	.sorted(Comparator.comparing(o -> o.name))
	.forEach(System.out::println);
/*
  Bella, 1살
  HaYang, 6살
  Kami, 2살
  Kong, 3살
*/

메서드 참조와 생성자 참조

수행하고자 하는 액션이 이미 메서드로 구현되어져 있는 경우, 아래와 같은 형태의 특수한 문법을 사용한다.

1. Class::staticMethod - 정적 메서드를 참조
2. Instance::instanceMethod - 특정 객체의 인스턴스 메서드를 참조
3. Type::methodName - 특정 자료형(ex. String)을 갖는 임의의 객체의 인스턴스 메서드 참조
4. Class::new - 생성자 참조

e.g. 메서드 참조
리스트의 모든 요소를 출력하고자 할 때, 아래와 같이 세 가지 방법으로 동일한 결과를 얻을 수 있다.

List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5);

// 방법1 - 반복문
    for(int num : nums) { System.out.println(nums); }

// 방법2 - 화살표함수
    numList.forEach(n -> System.out.println(n));

// 방법3 - 메서드 참조
    numList.forEach(Systme.out::println)

방법3 은 PrintStream의 인스턴스인 System.out 의 인스턴스 메서드를 참조한 것이다.

람다 표현식 처리

람다를 사용하는 핵심 목적은 원하는 시점에 함수를 실행하는 것이다.
함수형 인터페이스를 매개 변수로 받는 메서드를 작성하면 람다 표현식을 소비할 수 있다.
Java에는 다양한 표준 함수형 인터페이스가 준비돼 있다.

변수 유효범위

람다 표현식은 자신을 감싸는 유효 범위에 속한 사실상 최종(effectively final) 지역 변수 에만 접근 할 수 있다.
사실상 최종 변수란 절대로 변경되지 않는 변수를 의미한다.

// error
for(int i=0; i<5; i++) {
    new Thread(() -> System.out.println(i)).start();
}

// ok
int[] nums = {0, 1, 2, 3, 4};
for(int num : nums) {
    new Thread(() -> System.out.println(num)).start();
}

/*
 아래 for loop는 반복 시 마다 새로운 변수 num을 생성하고, nums 배열에 있는 다음 요솟값을 할당 받는다.  
 반면 위 for loop는 변수 i에 매번 새로운 값을 할당하기 때문에 규칙에 어긋난다.   
*/

고차 함수

함수를 매개 변수로 받거나, 함수를 반환하는 함수

public static Comparator<String> reverse(Comparator<String> c) {
	return (x, y) -> c.compare(y, x);
}

위 코드는 Comparator 를 인자로 받아, 변형된 Comparator 을 반환한다. Comparator 인터페이스에는 다양한 고차함수가 다양하게 구현되어져 있다.

익명 클래스

한번만 사용 될 클래스는 익명으로 만들 수 있다.

interface Calculator {
    public int sum(int... a);
}

class CalculatorImpl implements Calculator{
	@Override
	public int sum(int... nums) {
		return Arrays.stream(nums).sum();
	}	
}

public class Main {
    public static void main(String[] args) {
        
      Calculator classic = new CalculatorImpl();
      /*
       인터페이스는 생성자를 갖을 수 없다. 
       아래 new 키워드는 "Calculator 인터페이스를 구현하는 익명 클래스"의 인스턴스를 생성한다.
      */
      Calculator anonymous = new Calculator() {
			@Override
			public int sum(int... nums) {
				return Arrays.stream(nums).sum();
			}	
      };

      Calculator lambda = (int... nums) -> {	
			return Arrays.stream(nums).sum();
      };

      System.out.println(classic.sum(1,2,3,4,5));	
      System.out.println(anonymous.sum(1,2,3,4,5));		
      System.out.println(lambda.sum(1,2,3,4,5));
    }
}

카테고리:

업데이트:

댓글남기기