람다식
- 람다식은 이름이 없는 익명 함수이다.
- 람다식은 아래와 같이 매개변수, 화살표, 실행문 으로 구성된다.
(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
익명 클래스
한번만 사용 될 클래스는 익명으로 만들 수 있다.
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));
}
}
댓글남기기