■ Java Lambda Expression(람다식)
- Java 1.8에서 추가된 함수형 프로그래밍(Functional Programming)을 구현하는 방식
- 클래스를 생성하지 않고 함수의 호출만으로 기능을 수행
- 병렬 처리가 가능함
- Lambda를 이용하면, 함수를 변수처럼 사용할 수 있다 (Use funcation like variable)
- Java에서 '메소드'를 '함수'처럼 사용하는 방식
* Java에는 '함수'라는 게 없음.
* 함수형 프로그래밍을 하려면 '1급 함수'라는 개념이 필요
• Functional Programming
- 순수 함수(Pure Function)를 구현하고 호출
* 순수 함수 : 외부의 상태를 변경하지 않으면서, 동일한 인자에 대해 항상 똑같은 값을 리턴하는 함수
- 매개 변수만을 사용하도록 만든 함수로 외부 자료에 부수적인 영향(Side Effect)이 발생하지 않도록 한다.
- 입력받은 자료를 기반으로 수행되고 외부에 영향을 미치지 않기 때문에, 병렬 처리 등이 가능하고 안정적으로 확장이 가능한 프로그래밍 방식이다.
• Lambda 사용 가능한 경우
- Lambda 식은 하나의 Method를 정의하기 때문에, 하나의 Abstract Method가 선언된 Interface만이 Lambda 식의 타겟 타입이 될 수 있다.
- Lambda를 위한 Interface를 작성하는 경우, Annotation으로 @FunctionalInterface를 써주면 Method를 하나만 작성하도록 체크해 준다.
- Lambda 식의 내부적인 동작은 Anonymous Object 동작이다.
• Lambda 문법
// 기본 형태
// (타입 매개 변수, ...) -> { 실행문 }
// (x, y) -> {return x + y}
// 1. 매개 변수가 하나인 경우, 괄호 생략 가능
// str -> { System.out.println(str); }
// 2. 매개 변수가 없는 경우, 괄호 생략 불가능
// () -> { System.out.println("NULL"); }
// 3. 중괄호 안의 구현부가 한문장인 경우 중괄호 생략
// str -> System.out.println(str)
// 4. return 문 하나인 경우, 중괄호와 세미콜론 생략 가능
// str -> return str.length(); // Error
// str -> str.length() // Possible
• Lambda 예제
class ComparatorTest implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.substring(1).compareTo(o2.substring(1));
}
}
class Test{
public static void main(String[] args) {
String[] test = {"Java", "Study", "Yoon", "Church"};
Arrays.sort(test);
System.out.println(Arrays.toString(test));
// 1. Comparator 구현
Arrays.sort(test, new ComparatorTest());
System.out.println(Arrays.toString(test));
// 2. 익명 내부 클래스
Arrays.sort(test, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.substring(2).compareTo(o2.substring(2));
}
});
System.out.println(Arrays.toString(test));
// 3. Lambda 구현
Arrays.sort(test,(o1, o2) -> o1.substring(3).compareTo(o2.substring(3)));
System.out.println(test);
}
}
* Lambda의 경우, 실제 객체가 만들어지는 것이 아니기 때문에 this를 이용해 익명 구현 객체처럼 자기 자신 객체를 호출할 수 없다.
functionalMethod(()-> System.out.println(this)); // Error
• 표준 함수형 인터페이스
- 자주 사용되는 함수형 인터페이스를 정의해 둔 API
계 열 | 입 력 | 출 력 | 메소드 | 설명 |
Consumer | O | X | void accept(T) | 입력을 소비 |
Supplier | X | O | T get() | 출력을 공급 |
Function | O | O | T apply(R) | 입력 -> 출력 함수 매핑 |
Operation | O | O | T apply(T) | 입력을 연산하여 동일 타입의 출력으로 리턴 |
Predicate | O | boolean | boolean test(T) | 입력을 판단 |
1. Consumer 계열
- Parameter 입력을 받아서 그것을 소비하는 Functional Interface
- accept(T) 메소드 사용 : 리턴 타입이 void인 특징
1) Consumer<T>, BiConsumer<T, U>
- 제네릭 타입을 입력 타입으로 받는다.
- Bi는 입력 값 2개를 받는다.
class Test {
public static void main(String[] args){
Consumer<String> consumer = (s) -> System.out.println(s);
consumer.accept("Hello");
// 입력 값이 두 개
BiConsumer<String, String> biConsumer = (t, u) -> System.out.println(t+u);
biConsumer.accept("StringA", "StringB");
}
}
2) PConsumer
- P : Primitive Type
- IntConsumer, LongConsumer, DoubleConsumer
- 입력으로 P Type을 받음
class Test {
public static void main(String[] args){
// IntConsumer, LongConsumer, DoubleConsumer
IntConsumer intConsumer = (x) -> System.out.println(x);
intConsumer.accept(10);
}
}
3) ObjPConsumer<T>
- P : Primitive Type
- ObjIntConsumer, ObjLongConsumer, ObjDoubleConsumer
- 입력으로 Object와 P Type 2개를 받음
class Test {
public static void main(String[] args){
ObjIntConsumer<String> objIntConsumer = (t, x) -> System.out.println(t + " : "+x);
objIntConsumer.accept("x",1023);
// ObjLongConsumer, ObjDoubleConsumer
}
}
2. Supplier 계열
- 아무런 입력을 받지 않고, 값을 하나 반환하는 Functional Interface
- 자료를 공급하는 역할을 한다.
- T get() 메소드 사용 : return Type으로 T 반환
1) Supplier<T>
class Test{
public static void main(String[] args){
Supplier<String> supplier = () -> "A String";
System.out.println(supplier.get());
}
}
2) PSupplier
- P : Primitive Type
- BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
- Return Type으로 P 반환
- P getAsP() 메소드 사용
class Test{
public static void main(String[] args){
// Supplier는 P Type 계열에서 getAsP 메소드로 정의
BooleanSupplier boolSup = () -> true;
System.out.println(boolSup.getAsBoolean());
// IntSuplier, LongSupplier, DoubleSupplier
IntSupplier rollDice = () -> (int)(Math.random()*6);
int x = 4;
IntSupplier intSupp = () -> x;
// Lambda로 동작한다. Local, Parameter 의 경우, 내부로 final로 저정됨
}
}
3. Function 계열
- Mapping : 입력 -> 출력의 Mapping을 하는 Functional Interface
- 입력 타입과 출력 타입은 다를 수 있다.
- apply() 메서드 사용
1) Function<T, R>, BiFunction<T, U, R>
class Test{
public static void main(String[] args){
Function<String, Integer> func = s -> s.length();
func.apply("Four");
// Bi가 붙으면 '입력'을 2가지를 받을 수 있다는 의미
BiFunction<String, String, Integer> funcTwo = (s, u) -> s.length();
}
}
2) PFunction<R>
- P Type을 입력으로 받고, R 타입으로 반환
class Test{
public static void main(String[] args){
// P Type Function은 입력을 P Type으로 받는다.
IntFunction<String> funcTree = value -> "" + value;
System.out.println(funcTree.apply(512));
}
}
3) PtoQFunction
- P Type을 입력으로 받고, Q 타입으로 반환
- P applyAsQ(p value) 메소드 사용
// PtoQFunction : P -> Q로 매핑하는 함수
// IntToIntFunction은 없다. 동일한 형태는 없다.
IntToDoubleFunction funcFive;
4) ToPFunction<T>, ToPBiFunction<T, U>
- P 타입으로 리턴
- p applyAsP() 메소드 사용
// ToP Type Function은 출력을 P 타입으로 한다.
ToIntFunction<String> funcFour = (s) -> s.length();
// 출력이 P타입인 경우에는 AsP가 들어간다.
funcFour.applyAsInt("ABCDE");
4. Operator 계열
- 입력받은 타입과 동일한 타입의 출력을 하는 Functional Interface
- Function 계열과 달리 입출력 타입이 다를 수 없다.
- apply() 메소드 사용
1) UnaryOperator<T>, BinaryOperator<T>
class Test{
public static void main(String[] args){
// 기본 Operator가 없다, 입력이 1개인 것을 Unary로 표현
UnaryOperator<String> operator = s -> s + ".";
System.out.println(operator.apply("왔다"));
BinaryOperator<String> operatorTwo = (s1, s2) -> s1 + s2;
System.out.println(operatorTwo.apply("나", "왔다"));
}
}
2) PUnaryOperator, PBinaryOperator
- P Type을 입력으로 받고, P Type으로 리턴
class Test{
public static void main(String[] args){
IntUnaryOperator op = value -> value * 10;
System.out.println(op.applyAsInt(5));
// LongUnaryOperator, DoubleUnaryOperator
IntBinaryOperator op2 = (v1, v2) -> v1 * v2;
System.out.println(op2.applyAsInt(5, 10));
// LongBinaryOperator, DoubleBinaryOperator
}
}
5. Predicate 계열
- 논리 판단을 해주는 Functional Interface
- 입력을 받아서 boolean 타입 출력으로 반환
- test() 메소드 사용
1) Predicate<T>, BiPredicate<T, U>
class Test{
public static void main(String[] args){
Predicate<String> pred = (s) -> s.length() == 4;
System.out.println(pred.test("abcd"));
System.out.println(pred.test("abcde"));
BiPredicate<String, Integer> pred2 = (s, v) -> s.length() == v;
System.out.println(pred2.test("abcd", 3));
}
}
2) PPredicate
- P Type을 입력받는다.
- IntPredicate, LongPredicate, DoublePredicate
class Test{
public static void main(String[] args){
IntPredicate pred3 = (i) -> i > 0;
System.out.println(pred3.test(3));
// LongPredicate, DoublePredicate
}
}
• 표준형 함수형 인터페이스의 메소드
1. andThen(), compose()
- 2개 이상의 함수형 인터페이스를 연결하기 위해 사용
1) A.andThen(B)
- A를 먼저 실행하고 B를 실행.
- Consumer, Funcation, Operator 계열의 default method 구현
Consumer<String> c0 = s -> System.out.println("c0:"+s);
Consumer<String> c1 = s -> System.out.println("c1:"+s);
Consumer<String> c2 = c0.andThen(c1);
c2.accept("STRING");
2) A.compose(B)
- B를 먼저 실행하고 A를 실행.
- Funcation, Operator 계열의 default method 구현
// Function 계열은 입력 -> 출력 이 연쇄가 되어야 된다.
Function<String, Integer> func1 = s -> s.length();
Function<Integer, Long> func2 = value -> (long)value;
Function<String, Long> func3 = func1.andThen(func2);
System.out.println(func3.apply("Four"));
2. and(), or(), negate, isEqual()
- Predicate 계열 기본 메소드/ 클래스 메소드
1) and(), or(), negate()
- and() : A && B
- or() : A || B
- negate() : !A
DoublePredicate p0 = (y) -> y > 0.5;
DoublePredicate p1 = (y) -> y < 0.7;
DoublePredicate p2 = p0.and(p1); // and
System.out.println(p0.test(0.9));
System.out.println(p1.test(0.9));
System.out.println(p2.test(0.6));
DoublePredicate p3 = p0.or(p1); // or
DoublePredicate p4 = p0.negate(); // not
2) isEqual()
- isEqual() : A == B
- 클래스 메소드이다.
// Class Method
Predicate<String> eq = Predicate.isEqual("String");
System.out.println(eq.test("String"));
System.out.println(eq.test("String!"));
3. minBy, maxBy()
- BinaryOperator 클래스의 클래스 메소드
- Compartor<T>를 입력받아 min, max 출력
BinaryOperator<String> minBy = BinaryOperator.minBy((o1, o2) -> {
return len(o1) > len(o2) ? 1 : -1;
});
BinaryOperator<Integer> maxBy = BinaryOperator.maxBy((val1, val2) -> {
return val1.compareTo(val2);
});
• 람다식에 기존 메소드/생성자 사용
- 람다식에 기존에 구현되어 있는 내용을 재사용하고자 할 때
- 생성자를 andThen(), compose와 같이 사용 가능
1. ClassName::instanceMethod
// 클래스::인스턴스_메소드
String[] strings = {"fast", "campus", "best"};
Arrays.sort(strings, String::compareTo);
// Comparator 인터페이스는 2개의 입력 o1, o2를 받음
// o1.인스턴스_메소드(o2)로 호출되는 메소드가 사용됨
2. ClassName::classMethod
Function<String, Integer> func0 = Integer::parseInt;
3. instance::instanceMethod
- 주어진 객체의 메소드를 호출
String s = "String";
Predicate<String> preq = s::equals;
pred.test("String"); // True
pred.test("String2"); // False
4. ClassName::new
Function<Integer, Integer> fnc = Integer::new;
5. ClassName[]::new
IntFunction<String[]> fnc2 = String[]::new;
String[] strings2 = fnc2.apply(100);
'Coding > Java' 카테고리의 다른 글
Java Stream API (0) | 2020.09.28 |
---|---|
Java Comparator & Comparable (0) | 2020.09.22 |
Java Inner Class(내부 클래스) (0) | 2020.09.18 |
Java Handling Exception(예외 처리) (0) | 2020.09.17 |
Java Generic(제네릭) (0) | 2020.09.17 |