Skip to content

Instantly share code, notes, and snippets.

@gksxodnd007
Last active April 18, 2018 04:12
Show Gist options
  • Save gksxodnd007/c49de4909b58204c8b2cafed25bdea8c to your computer and use it in GitHub Desktop.
Save gksxodnd007/c49de4909b58204c8b2cafed25bdea8c to your computer and use it in GitHub Desktop.
java8 Lambda

람다식이란?

람다식(Lambda expression)은 간단히 말해서 메서드를 하나의 '식(expression)'으로 표현한 것이다. 메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 '익명 함수(anonymous function)'이라고도 한다.

int[] arr = new int[5];
Arrays.setAll(arr, i -> (int)Math.random() * 5) + 1;

위의 람다식은 다음 메서드와 같다.

int method(int i) {
  return (int)(Math.random() * 5) + 1;
}

위의 메서드는 클래스에 포함되어야 하므로 클래스를 새로 만들어야 하고, 객체도 생성해야만 비로소 필요한 메서드를 호출 할 수 있다. 그러나 람다식은 이 모든 과정없이 오직 람다식 자체만으로도 이 메서드의 역할을 대신할 수 있다. 게다가 람다식은 메서드의 매개변수로 전달되어지는 것이 가능하고, 메서드의 결과로 반환 될 수도 있다.

다음은 람다의 사용법이다.

일반 메서드

반환타입 메서드이름(매개변수 선언) {
  문장들
}

람다식

(매개변수 선언) -> {
  문장들
}

반환값이 있는 메서드의 경우, return문 대신 '식(expression)'으로 대신 할 수 있다. 식의 연산결과가 자동적으로 반환값이 된다. 이때는 '문장(statement)'이 아닌 '식'이므로 끝에 ';'을 붙이지 않는다.

//변경 전
(int a, int b) -> {
  return a > b ? a : b;
}

//변경 후
(int a, int b) -> a > b ? a : b

람다식에 선언된 매개변수의 타입은 추론이 가능한 경우는 생략할 수 있는데, 대부분의 경우에 생략가능하다. 람다식에 변환타입이 없는 이유도 항상 추론이 가능하기 때문이다.

(a, b) -> a > b ? a : b

아래와 같이 선언된 매개변수가 하나뿐인 경우에는 괄호()를 생략할 수 있다. 단, 매개변수의 타입이 있으면 괄호()를 생략할 수 없다.

//변경 전
(a) -> a * a
//변경 후
a -> a * a //ok

//변경 전
(int a) -> a * a
//변경 후
int a -> a * a //error

마찬가지로 괄호{}안의 문장이 하나일 때는 괄호{}를 생략할 수 있다. 이 때 문장의 끝에 ';'을 붙이지 않아야 한다는 것에 주의하자.

//변경 전
(String name, int age) -> {
  System.out.println("name: " + name ", age:" + age);
}
//변경 후
(String name, int age) ->
  System.out.println("name: " + name ", age:" + age);

괄호{}안의 문장이 return문일 경우 괄호{}를 생략할 수 없다.

(int a, int b) -> { return a > b ? a : b;} //ok
(int a, int b) -> return a > ? a : b //error

자바에서 모든 메서드는 클래스 내에 포함되어야 하는데, 람다식은 어떤 클래스에 포함되는 것일까? 람다식은 익명 클래스의 객체와 동등하다.

(int a, int b) -> a > b ? a : b

//위 람다식은 다음과 같다.
new Object() {
  int max(int a, int b) {
    return a > b ? a : b;
    }
}

람다식으로 정의된 메서드를 호출하려면 참조변수에 저장해야한다.

타입 function = (int a, int b) -> a > b ? a : b;

참조변수 function의 타입은 인터페이스로 정의해야한다. 이때 인터페이스에 @FunctionalInterface 애노테이션을 붙인다. 이를 "함수형 인터페이스"라고 부른다. 함수형 인터페이스에는 오직 하나의 추상 메서드만 정의되어 있어야한다. 그래야 람다식과 인터페이스의 메서드가 1:1로 연결될 수 있기 때문이다. 반면에 static메서드와 default메서드의 개수에는 제약이 없다.

@FunctionalInterface
public interface Function {
  public abstract int max(int a, int b);
}

public class Lambda {
  Function funtionLambda = (int a, int b) -> a > b ? a : b;
  Function functionAnonyObject = new Function() {
    public int max(int a, int b) {
      return a > b ? a : b;
    }
  }

  functionLambda.max(3, 5);
  functionAnonyObject.max(3, 5);
}

이때 익명객체를 람다식으로 대체가 가능한 이유는, 람다식도 실제로는 익명 객체이고, Function인터페이스를 구현한 익명 객체의 메서드 max()와 람다식의 매개변수의 타입과 개수 그리고 반환값이 일치하기 때문이다.

람다식을 참조변수로 다룰 수 있다는 것은 메서드를 통해 람다식을 주고받을 수 있다는 것을 의미한다.

람다식의 타입과 형변환

  • 함수형 인터페이스로 람다식을 참조할 수 있다는 것일 뿐, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아니다. 람다식은 익명 객체이고 익명 객체는 타입이 없다. 정확히는 타입은 있지만 컴파일러가 임의로 이름을 정하기 때문에 알 수 없는 것이다. 그래서 대입 연산자의 양변의 타입을 일치시키기 위해 아래와 같이 형변환이 필요하다.
Function function = (Function)(() -> {}); //양변의 타입이 다르므로 형변환 필요
  • 람다식은 이름이 없을 뿐 분명히 객체인데도, 아래와 같이 Object타입으로 형변환 할 수 없다. 람다식은 오직 함수형 인터페이스로만 형변환이 가능하다.

외부 변수를 참조하는 람다식

  • 람다식도 익명 객체, 즉 익명 클래스의 인스턴스이므로 람다식에서 외부에 선언된 변수에 접근하는 규칙은 앞서 익명 클래스에서 배운 것과 동일하다.
  • 람다식 내에서 참조하는 지역변수는 final이 붙지 않았어도 상수로 간주된다.
  • Inner클래스와 Outer클래스의 인스턴스 변수는 상수로 간주되지 않는다.
  • 외부 지역변수와 같은 이름의 람다식 매개변수는 허용되지 않는다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment