한 번만 사용할 메소드를 일일히 정의하면 정의할 메소드가 너무 많아지지 않겠나!
그럴때 사용하기 좋은 것이 람다이다. 본인은 일회용 메소드 비슷하게 이해하고 있다.
람다가 없으면 있는 선택지는 익명클래스를 사용하는 겁니다.
Consumer<String> c = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("Hello, " + s);
}
};
c.accept("Lambda!");
이렇게 일회용 메소드를 익명클래스를 이용해서도 사용할순 있다만, 너무 복잡하고 무엇보다 쓰기가 너무 귀찮습니다.
단, 익명클래스는 여러개의 메서드를 가질 수 있다는 장점?이 있습니다.(이럴 거면 그냥 클래스를 하나 만드는게..)
이런 귀찮음을 해결 할 수 있는게 람다입니다
일단 람다가 어떻게 생겨먹었는지부터 보자
(매개변수,매개변수) → {
표현식
}
//매개변수 하나일때 () 생략가능
매개변수 -> {
표현식
}
//한 줄이면 {}도 생략가능, return쓰면 안됌(암시적 반환)
매개변수 -> 표현식
람다는 → 연산자를 사용한다.
→ 좌측에 있는 매개변수가 → 우축에 있는 명령문에 들어가 연산수행 가능
이 람다는 메소드와 동일하게 반환값을 변수에 할당할 수 있다.
추가로 람다식이 한줄일때 반환값이 있더라도 return을 명시하면 문법 오류 발생함.
(여러줄 일땐 {}과 함께 return 필수)
Function<Integer, Integer> square = x -> return x * x; - ❌
Function<Integer, Integer> square = x -> x * x; - ⭕
Consumer, Supplier, Function, Runnable 등이 있다.@FunctionalInterface)@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
Consumer 는 accept()라는 추상 메소드를 가진다.accept() 는 입력값만 있고 반환을 하지 않는다. 즉, 소비만 한다.Consumer<String> AnonymousConsumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("Hello, " + s);
}
};
AnonymousConsumer.accept("익명클라스~!");
Consumer<String> MultiLambda = (s) -> {
System.out.println("Hello, " + s);
};
MultiLambda.accept("여러줄 람다~");
Consumer<String> SingleLambda = s -> System.out.println("Hello, " + s);
SingleLambda.accept("한 줄 람다~");
/*
출력
Hello, 익명클라스~!
Hello, 여러줄 람다~
Hello, 한 줄 람다~
*/
public interface Supplier<T> {
T get();
}
Supplier는 get()이라는 추상 메소드를 가짐get()은 매개변수를 받지 않고 반환만 한다. 즉, 공급만 한다.Supplier<String> justStringOut = () -> "Supplying..";
String str = justStringOut.get();
System.out.println(str);
Supplier<Integer> randomNumberSupplier = () -> (int) (Math.random() * 100);
for (int i = 0; i <= 5; i++){
System.out.println(randomNumberSupplier.get());
}
/*
출력
Supplying..
31
84
64
16
50
43
*/
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
apply() 라는 추상 메서드 가짐Function<Integer, Integer> square = x -> x * x;
Integer applied = square.apply(5);
System.out.println(applied);
Function<String, String> upperCase = x -> x.toUpperCase();
String Uppercase = upperCase.apply("lambda");
System.out.println(Uppercase);
/*
출력
25
LAMBDA
*/
@FunctionalInterface
public interface Runnable {
void run();
Thread클래스가 Runnable구현)Thread t = new Thread(runnable); t.start(); ⇒ 멀티코어 CPU에서는 실제로 동시에 병렬 실행! (싱글코어에서는 그런 것처럼 보임)Runnable runnable = () -> System.out.println("단지 실행할 뿐..");
runnable.run();
//출력 : 단지 실행할 뿐..
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
/*
출력
Thread-0: 0
Thread-1: 0
Thread-0: 1
Thread-1: 1
Thread-0: 2
Thread-1: 2
Thread-0: 3
Thread-1: 3
Thread-0: 4
Thread-1: 4
*/
이처럼 Runnable을 Thread에 전달하여 멀티쓰레드가 실행된다.
밑에서 대표적인 메서드 체이닝 방법 두개를 소개해 보겠다.
this 반환)public class MethodChaining {
public String name;
public int age;
public boolean isGay;
public String gay;
public MethodChaining setName(String name) {
this.name = name;
return this;
}
public MethodChaining setAge(int age) {
this.age = age;
return this;
}
public MethodChaining setGay(boolean isGay) {
this.isGay = isGay;
gay = (isGay) ? "게이" : "게이아님";
return this;
}
public MethodChaining show() {
System.out.println("이름: " + name + " 나이: " + age + " 게이?: " + gay);
return this;
}
}
public class LambdaTest {
public static void main(String[] args) {
new MethodChaining()
.setName("홍석천")
.setAge(54)
.setGay(true)
.show();
}
}
/*
출력
이름: 홍석천 나이: 54 게이?: 게이
*/
이런식으로 같은 객체에서 this를 반환하는 메소드들을 연속해서 호출할 수 있다.
andThen()Consumer과 Function함수형 인테페이스에는 사실 accept()말고도 하나의 메소드가 더 있다.
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
두 함수형 인터페이스의 andThen을 잘보라!
모두 자기 자신을 반환하고 있다!(Function의 compose도 마찬가지다.)
즉, 이 두 함수형 인터페이스는 andThen과 compose(Function)를 통해서 메서드 체이닝이 가능하다.
//첫번째 방법
Consumer<String> consumer1 = (s) -> System.out.println("첫 번째 람다!: " + s);
Consumer<String> consumer2 = consumer1.andThen((s) -> System.out.println("두 번째 람다!: " + s));
consumer2.accept("메서드 체이닝!");
//두번때 방법
Consumer<String> consumer1 = (s) -> System.out.println("첫 번째 람다!: " + s);
Consumer<String> consumer2 = (s) -> System.out.println("두 번째 람다!: " + s);
Consumer<String> chaining = consumer1.andThen(consumer2);
chaining.accept("메서드 체이닝!");
/*출력
첫 번째 람다!: 메서드 체이닝!
두 번째 람다!: 메서드 체이닝!
*/
Function<Integer, Integer> function1 = i -> i * 2;
Function<Integer, Integer> function2 = i -> i + 10;
Function<Integer, Integer> answer = function1.andThen(function2);
Function<Integer, Integer> composed = function1.compose(function2);
System.out.println(answer.apply(3));
System.out.println(composed.apply(3));
/*
출력
16
26
*/
위와 같이 Function의 compose()는 andThen()과 실행순서가 반대이다.
⇒ andThen : 왼쪽실행 후 오른쪽실행
⇒ compose: 오른쪽 실행 후 왼쪽 실행
사실 위에 this를 반환하는 메소드들이 연속으로 호출이 가능한건 점(.)왼쪽의 메소드의 반환타입과 점(.) 오른쪽의 메소드의 매개변수 타입이 같기 때문에 메소드 체이닝이 가능하다고 생각해서
같은 원리로 this를 반환하지 않아도 반환타입가 매개변수 타입만 만족한다면 메소드 체이닝은 가능한게 아닌가..? 라고 생각했는데 뭐.. 되긴되는데 이건 메서드체이닝이 아니었다.
⇒ 걍 메서드를 중첩 호출하는 거임
public class Main {
public static String methodA() {
return "hello";
}
public static int methodB(String input) {
return input.length();
}
public static void main(String[] args) {
int result = methodB(methodA()); //이건 그냥 메서드 중첩 호출이다...
System.out.println(result);
}
}