.
이전 iterator 글을 쓰면서 Iteraotr interface 소스를 읽어보다가, 매개변수에 Consumer 자료형의 변수가 있는 걸 보고 궁금해서 찾아보다가 람다식에 대해 알아보고 간략하게 정리하게 되었다.
.
lambda란?
JAVA8에서 추가되었으며 익명 함수 구현을 위한 것이다. 익명 함수는 적은 횟수나, 한 번만 정의되고 쓰일 함수를 말한다.
람다식을 사용하면 코드 라인 수를 줄이고 코드를 간결하게 작성할 수 있다. Stream API나, 함수적 인터페이스를 사용했을 때 더 강력하게 이용할 수 있다. 어떤 면에서는 병렬 처리나, 성능 향상의 장점도 있다고 한다.
( stackoverflow.com/questions/33492203/what-are-the-advantages-of-using-lambda-expressions-in-java-8 )
하지만 코드를 한 번에 이해하기 어렵다는 단점이 있다. 람다식 한 줄을 이해하기 위해서는 코드를 일일이 읽어봐야하는 일이 생길 수도 있다. 간결하게 작성할 수 있다는 점에선 어쩔 수 없는 일 같다.
생성
람다 식은 인터페이스의 익명 구현 객체를 생성한다. 이때, 인터페이스는 하나의 추상 메소드만 선언되어있는 상태여야 한다.
(매개변수, ...) -> {명령문; ...}을 통해서 정의한다. 매개변수나 명령문이 하나만 있다면 (), {}를 생략할 수 있다.
예시)
public class Main {
public static void main(String[] args) {
LambdaSample lambdaSample = (a)->{System.out.println("a: " + a);};
// LambdaSample lambdaSample2 = a->{System.out.println("a: " + a);};
// LambdaSample lambdaSample3 = (a)->System.out.println("a: " + a);
// LambdaSample lambdaSample4 = a->System.out.println("a: " + a);
lambdaSample.print(1);
}
public interface LambdaSample{
public void print(int a);
}
}
실행 결과)
함수적 인터페이스
하나의 추상 메소드만 가지고 있는 interface를 말하며, 디폴트 메소드나 정적 메소드는 별개로 포함할 수 있다. 많이 사용되는 형태의 인터페이스를 java.util.function 패키지에서 제공하기 때문에, 위에서 사용한 예제처럼 매번 인터페이스를 만들 필요는 없다.
제공하는 메소드 종류는 크게 다섯 가지로 아래와 같다.
Consumer | 하나 혹은 그 이상의 매개변수를 받고 return값은 없다. |
Supplier | 공급자, 할당의 역할을 한다. getter의 함수적 인터페이스. |
Function | 하나 혹은 그 이상의 매개변수를 받고 결과를 return한다. |
Operator | 매개변수에 대해 연산을 한 후 결과를 return한다. |
Predicate | 매개변수에 대한 boolean 값을 test하고 return한다. |
- Consumer
@FunctionalInterface 애노테이션이 붙어있어서 해당 인터페이스가 함수적 인터페이스임을 확인할 수 있다. 실제로도 추상 메소드 accept와, 디폴트 메소드 하나가 있다.
Consumer는 소비자로 매개변수는 받지만 return 값은 없는 것이 특징이다. 위 Consumer<T> 인터페이스 외에도 다양한 Consumer 인터페이스를 제공한다.
BiConsumer<T,U> | 2개의 매개변수를 받고 return 값은 없다. |
Consumer<T> | 1개의 매개변수를 받고 return 값은 없다. |
DoubleConsumer | double 형의 매개변수 하나를 받고 return 값은 없다. |
IntConsumer | int 형의 매개변수 하나를 받고 return 값은 없다. |
LongConsumer | long 형의 매개변수 하나를 받고 return 값은 없다. |
objDoubleConsumer<T> | object 형, double 형의 매개변수를 하나씩 받고 return 값은 없다. |
objIntConsumer<T> | object 형, int 형의 매개변수를 하나씩 받고 return 값은 없다. |
objLongConsumer<T> | object 형, long 형의 매개변수를 하나씩 받고 return 값은 없다. |
예제)
public class Main {
public static void main(String[] args) {
Consumer<Integer> consumer = (a)-> System.out.println("a: " + a) ;
consumer.accept(5);
BiConsumer<String, String> biConsumer = (a, b) -> System.out.println("a: " + a + " b: " + b);
biConsumer.accept("java", "jdk");
IntConsumer intConsumer = (a) -> System.out.println(a+5);
intConsumer.accept(10);
DoubleConsumer doubleConsumer = (a) -> System.out.println(a+5.0);
doubleConsumer.accept(13.3);
ObjIntConsumer<String> objIntConsumer = (a, b)-> System.out.println(a + Integer.toString(b));
objIntConsumer.accept("java", 8);
}
}
실행 결과)
- Supplier
Supplier는 공급자로 어떠한 객체의 인스턴스를 반환하는 메소드의 역할을 한다. 매개변수는 없고 return 값만 있다.
BooleanSupplier | Bollean 값을 생성해서 return한다. |
DoubleSupplier | double 값을 생성해서 return한다. |
IntSupplier | int 값을 생성해서 return한다. |
LongSupplier | long 값을 생성해서 return한다. |
Supplier<T> | 값을 생성해서 return한다. |
supplier를 사용하는 이유에 대해서도 찾아봤는데, getter나 생성자를 호출해서 직접 객체를 생성하는 것에 비해서 성능이 향상되는건 아니라고 한다. 잘 모르겠지만, getter보다 supplier를 사용했을 때 더 효율적인 상황이 있는 것 같다. getter를 수행하기 전에 조건이나 추가 작업이 필요할 때가 그렇다. 이 경우에는 람다식을 사용해서 코드 중복도 줄일 수 있다. 또, lazy evaluation을 구현해야할 때 supplier를 사용할 수 있다고 한다. 이 부분은 글을 읽어도 아직 잘 와닿지 않는다. 자세한 건 예제를 통해 설명하겠다.
예제)
public class Main {
static class Person{
String name;
String id;
int age;
public Person(String name, String id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public void print() {
System.out.println("info) name: "+ name +" id: " + id + " age: " + age);
}
}
static void getPerson(Supplier<? extends Person> supplier) {
Person person = supplier.get();
person.print();
}
public static void main(String[] args) {
getPerson(()->new Person("david", "1234", 20));
getPerson(()->new Person("Jenny", "3345", 25));
DoubleSupplier doubleSupplier = ()->Math.random();
System.out.println(doubleSupplier.getAsDouble());
}
}
실행 결과)
getPerson 메소드는 람다식을 매개변수로 받아서 person 객체를 생성하고 정보를 출력한다. 만약 정보 출력 전에 1초를 기다리게 하고 싶다면 다음과 같이 getPerson 메소드를 수정할 수 있다.
static void getPerson(Supplier<? extends Person> supplier) throws Exception {
Person person = supplier.get();
Thread.sleep(1000); // add
person.print();
}
또한, DoubleSupplier를 통해서 random의 난수를 출력하는 것 까지 확인해보았다.
- Function
function은 매개변수를 받아서 return 값을 매핑하는 역할을 한다.
BiFunction<T,U,R> | 'T'형, 'U'형 2개의 매개변수를 받고 'R'형 return 값을 만든다. |
DoubleFunction<R> | double 형 매개변수를 받고 'R'형 return 값을 만든다. |
DoubleToIntFunction | double 형 매개변수를 받고 int 형 return 값을 만든다. |
DoubleToLongFunction | double 형 매개변수를 받고 long 형 return 값을 만든다. |
Function<T,R> | 'T'형 매개변수를 받아서 'R'형 return 값을 만든다. |
IntFunction<R> | int 형 매개변수를 받고 'R'형 return 값을 만든다. |
IntToDoubleFunction | int 형 매개변수를 받고 double 형 return 값을 만든다. |
IntToLongFunction | int 형 매개변수를 받고 long 형 return 값을 만든다. |
LongFunction<R> | long 형 매개변수를 받고 'R'형 return 값을 만든다. |
LongToDoubleFunction | long 형 매개변수를 받고 double 형 return 값을 만든다. |
LongToIntFunction | long 형 매개변수를 받고 int 형 return 값을 만든다. |
ToDoubleBiFunction<T,U> | 'T'형, 'U'형 2개의 매개변수를 받고 double 형 return 값을 만든다. |
ToDoubleFunction<T> | 1개의 'T형' 매개변수를 받고 double 형 return 값을 만든다. |
ToIntBiFunction<T,U> | 'T'형, 'U'형 2개의 매개변수를 받고 int 형 return 값을 만든다. |
ToIntFunction<T> | 1개의 'T형' 매개변수를 받고 int 형 return 값을 만든다. |
ToLongBiFunction<T,U> | 'T'형, 'U'형 2개의 매개변수를 받고 long 형 return 값을 만든다. |
ToLongFunction<T> | 1개의 'T형' 매개변수를 받고 long 형 return 값을 만든다. |
예제)
public class Main {
static class Person{
String name;
String id;
int age;
public Person(String name, String id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getIdAndName() {
return "name: " + name + " id: " + id;
}
public void print() {
System.out.println("info) name: "+ name +" id: " + id + " age: " + age);
}
}
public static void main(String[] args) throws Exception {
Function<Person, String> function1 = (p)->{
return p.getIdAndName();
};
Person p = new Person("david", "1234", 20);
System.out.println(function1.apply(p));
DoubleToIntFunction doubleToIntFunction = (d)->(int)d;
System.out.println(doubleToIntFunction.applyAsInt(5.0));
IntToDoubleFunction intToDoubleFunction = (i)->(double)i;
System.out.println(intToDoubleFunction.applyAsDouble(10));
}
}
실행 결과)
- Operator
function 처럼 매개변수와 return 값이 있는 건 같으나, operator는 연산을 수행한 후 그 결과를 return한다는 차이가 있다.
BinaryOperator<T> | 같은 타입의 2개 매개변수를 피연산자로, 같은 타입의 연산 결과를 return 한다. |
DoubleBinaryOperator | double 형의 2개 매개변수를 피연산자로, double 형의 연산 결과를 return 한다. |
DoubleUnaryOperator | double 형의 1개 매개변수를 피연산자로, double 형의 연산 결과를 return 한다. |
IntBinaryOperator | int 형의 2개 매개변수를 피연산자로, int 형의 연산 결과를 return 한다. |
intUnaryOperator | int 형의 1개 매개변수를 피연산자로, int 형의 연산 결과를 return 한다. |
LongBinaryOperator | long 형의 2개 매개변수를 피연산자로, long 형의 연산 결과를 return 한다. |
LongUnaryOperator | long 형의 1개 매개변수를 피연산자로, long 형의 연산 결과를 return 한다. |
UnaryOperator | 1개 매개변수를 피연산자로, 같은 타입의 연산 결과를 return 한다. |
예제)
public class Main {
public static void main(String[] args) throws Exception {
IntBinaryOperator intBinaryOperator = (a, b)->{
return a + b;
};
System.out.println(intBinaryOperator.applyAsInt(3, 5));
IntUnaryOperator intUnaryOperator = (a) -> a * 5;
System.out.println(intUnaryOperator.applyAsInt(2));
DoubleBinaryOperator doubleBinaryOperator = (a, b) -> a - b;
System.out.println(doubleBinaryOperator.applyAsDouble(2.34, 1.29));
}
}
실행 결과)
- Predicate
Predicate는 매개변수를 통해서 boolean 값을 return하는, 어떠한 값을 예측하는 역할을 한다.
BiPredicate<T,U> | 2개의 매개변수로부터 boolean 값을 return 한다. |
DoublePredicate | 하나의 double 형 매개변수로부터 boolean 값을 return 한다. |
IntPredicate | 하나의 int 형 매개변수로부터 boolean 값을 return 한다. |
LongPredicate | 하나의 long 형 매개변수로부터 boolean 값을 return 한다. |
Predicate | 1개의 매개변수로부터 boolean 값을 return 한다. |
예제)
public class Main {
public static void main(String[] args) throws Exception {
Predicate<String> predicate = (s)->s.equals("lion");
System.out.println(predicate.test("lion"));
System.out.println(predicate.test("java"));
IntPredicate intPredicate = (i)->i>10;
System.out.println(intPredicate.test(5));
System.out.println(intPredicate.test(15));
}
}
실행 결과)
정리
lambda의 기초적인 정의와 사용법, 그리고 함수적 인터페이스에 관해서 공부하고 정리해보았다. 생각보다 글을 작성하는데 오래 걸렸는데, 예제를 만들어보며 더 빠르게 이해할 수 있게 된 것 같다.
기회가 된다면 andThen, compose같은 메소드나 stream, lazy evaluation같은 개념도 정리해보고 싶다.
- 참고 자료
en.wikipedia.org/wiki/Anonymous_function#Java
tourspace.tistory.com/3?category=788398
docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
stackoverflow.com/questions/40244571/when-we-should-use-supplier-in-java-8/40244843
'java' 카테고리의 다른 글
[210116] java comparable & comparator (0) | 2021.01.16 |
---|---|
[210112] java collection (0) | 2021.01.12 |
[201211] default method (0) | 2020.12.11 |
[201209] java iterator (0) | 2020.12.09 |
[201019] spring AOP pointcut annotation (0) | 2020.10.19 |