람다식은 결국 메소드 정의이다!
class ArrangeList {
public static void main(String[] args) {
List<Integer> ls = Arrays.asList(1, 3, 5, 7, 9);
ls = new ArrayList<>(ls);
Consumer<List<Integer>> c = l -> Collections.reverse(l); // 람다식
c.accept(ls); // 순서 뒤집기 진행
System.out.println(ls); // 출력
}
}
public static void reverse(List<?> list)
이 static 메소드를 사용하는 것에 큰 의미가 있으므로,
자바 8에서 부터는 람다식 작성 필요 없이 다음과 같이 메소드 정보만 전달할 수 있다.
Consumer<List<Integer>> c = Collections::reverse;
이렇게 메소드 참조에서 람다식에 있는 인자 전달에 대한 정보를 생략할 수 있는 이유는
accept 메소드를 호출 시 전달되는 인자를 reverse 메소드를 호출하면서 그대로 전달하기 때문이다.
class JustSort {
public void sort(List<?> lst) { // 인스턴스 메소드
Collections.reverse(lst);
}
}
class ArrangeList3 {
public static void main(String[] args) {
List<Integer> ls = Arrays.asList(1, 3, 5, 7, 9);
ls = new ArrayList<>(ls);
JustSort js = new JustSort();
Consumer<List<Integer>> c = e -> js.sort(e); // 람다식 기반
c.accept(ls);
System.out.println(ls);
}
}
람다식에서 같은 지역 내에 선언된 참조변수 js에 접근하고 있다.
람다식이 인스턴스 생성으로 이어지지만, 람다식에서 접근 가능한 참조변수인 final로 선언되었거나 effectively final이면 같은 지역 내 선언된 참조변수에 접근할 수 있다.
🤔 effectively final이란?
사실상 final 선언이 된 것과 다름 없음을 뜻한다.
위의 예제에서 참조변수 js는 참조하는 대상을 수정하지 않았기 때문에
"사실상 final 선언이 된 것과 같다."
위 예제에서 등장한 람다식은 다음과 같은 메소드 참조로 변경할 수 있다.
Consumer<List<Integer>> c = js::sort;
import java.util.List;
import java.util.Arrays;
class ForEachDemo {
public static void main(String[] args) {
List<String> ls = Arrays.asList("Box", "Robot");
// 람다식 기반
ls.forEach(s -> System.out.println(s));
// 메소드 참조 기반
ls.forEach(System.out::println);
}
}
Collection<E>
인터페이스는 Iterable<T>
를 상속한다.
Iterable<T>
인터페이스에는 다음 디폴트 메소드가 정의되어 있다.
default void forEach(Consumer<? super T> action){
for (T t : this) // this = 이 메소드가 속한 컬렉션 인스턴스
action.accept(t); // 모든 저장된 데이터들에 대해 이 문장 반복
}
forEach 메소드 호출을 위해서 Consumer<T>
인터페이스에 대한 람다식 또는 메소드 참조를 전달해야 하는데 추상 메소드인 accept는 결과를 반환하지 않고 전달된 인자를 대상으로 어떤 결과를 보인다.
따라서 System.out.println 메소드를 사용하기 적합!
import java.util.function.ToIntBiFunction;
class IBox {
private int n;
public IBox(int i) { n = i; }
public int larger(IBox b) {
if(n > b.n)
return n;
else
return b.n;
}
}
class NoObjectMethodRef {
public static void main(String[] args) {
IBox ib1 = new IBox(5);
IBox ib2 = new IBox(7);
// 두 상자에 저장된 값 비교하여 더 큰 값을 반환
ToIntBiFunction<IBox, IBox> bf = (b1, b2) -> b1.larger(b2);
int bigNum = bf.applyAsInt(ib1, ib2);
System.out.println(bigNum);}
}
// ToIntBiFunction<T, U> int applyAsInt(T t, U u)
위 람다식에서 호출하는 메소드 larger가 첫번째 인자로 전달된 인스턴스의 메소드이며 다음과 같이 메소드 참조로 변환할 수 있다.
ToIntBiFunction<IBox, IBox> bf = IBox::larger;
// 클래스 이름::인스턴스 메소드 이름
람다식을 작성하다 보면 인스턴스를 생성하고 이의 참조 값을 반환해야 하는 경우가 있는데
이때 메소드 참조 방식을 사용할 수 있다.
interface SMaker {
String make(char[] ar);
}
class StringMaker {
public static String chsToString(char[] a, SMaker m) {
return m.make(a);
}
public static void main(String[] args) {
SMaker sm = (ar) -> {
return new String(ar);
};
char[] src = {'R', 'o', 'b', 'o', 't'};
String str = chsToString(src, sm);
System.out.println(str);
}
}
String 클래스의 public String(char[] value)
생성자를 이용한 String 인스턴스의 생성을 보이고 있다.
위의 람다식은 이렇게 줄일 수 있다.
Function<char[], String> f = ar -> new String(ar);
이는 메소드 참조방식으로 변경할 수 있다.
Funtion<char[], String> f = String::new;
// 클래스 이름::new