메소드 참조의 4가지 유형과 메소드 참조의 장점
1. static 메소드의 참조
2. 참조변수를 통한 인스턴스 메소드 참조
3. 클래스 이름을 통한 인스턴스 메소드 참조
4. 생성자 참조
기본적으로 람다식보다 조금 더 코드를 단순하게 할 수 있다는 장점이 있다.
(특정 경우에 대해서만 람다식을 조금 더 줄여쓸 수 있음)
일부 람다식을 메소드 참조로 대신하게 할 수 있다.
static 메소드의 참조: 람다식 기반 예제
class ArrangeList {
public static void main(String[] args) {
List<Integer> ls = Arrays.asList(1,3,5,7,9);
ls = new ArrayList<>(ls); // 타입인자 Integer 전달됨
Consumer<List<Integer>> c = l -> Collections.reverse(l);
c.accept(ls); // 순서 뒤집기 진행
System.out.println(ls);
}
}
Consumver<T> 에는 void accept(T t)가 정의되어 있고
accept메소드를 어떻게 정의하든 이는 사용자의 마음이다.
static 메소드의 참조: 메소드 참조 기반으로 수정
Consumer<List<Integer>> c = Collections::reverse;
accept 메소드 호출시 전달되는 인자를 reverse 메소드를 호출하며 그대로 전달한다는 약속에 근거한 수정이다.
이러면 static 메소드 Collections.reverse를 참조하여 Consumer.accept()를 호출하여 사용할 수 있게 된다.
인스턴스 메소드 참조 1: effectively final
effectivelry final 은 사실상 상수라는 의미이다.
아래 코드에서 js가 참조하는 인스턴스가 바뀌지 않으므로 이는 사실상 상수이므로 즉, effectively final이다.
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); // private 정적 클래스 ArrayList 리턴
ls = new ArrayList<>(ls); // 동적 ArrayList 로 변경
JustSort js = new JustSort(); // js 는 effectively final
Cousumer<List<Integer>> c = e -> js.sort(e); // 람다식 기반
c.accept(ls);
System.out.println(ls);
}
}
이 람다식 기반의 메소드 참조를 메소드 참조로 다음과 같이 수정할 수 있다.
이는 아래의 코드에서 js가 effectively final인 경우에만 허용이 된다.
js가 참조하는 인스턴스가 바뀌는 경우는 의도치 않은 다른 인스턴스의 메소드를 참조하는 경우가 생길 수 있으므로 위험하기 때문이다.
또는 js = null; 이라도 넣어주면 sort 메소드를 참조할 수조차 없게 된다.
이런 경우를 방지하기 위해 js는 effectively final 이어야 한다.
class JustSort {
public void sort(List<?> lst) {
Collections.reverse(lst);
}
}
class ArrangList3 {
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 = js::sort; // :: 기호는 컴파일러에 이것이 메소드 참조에 쓰일 것이라는 것도 전달해줌
c.accept(ls); // 이는 이제 js 인스턴스의 sort 함수를 호출하여 ls를 인자로 주는 것과 같음
System.out.println(ls);
}
}
인스턴스 메소드 참조 forEach
class Iterable<T> {
...
default void forEach(Consumer<? super T> action) {
for (T t : this) // this 는 이 메소드가 속한 컬렉션 인스턴스를 의미함
action.accept(t);
...
}
}
// 이 때 Consumer<?>의 ?는 T의 상위 클래스이다.
// 즉, accept 메소드에서는 T 클래스 안에만 정의되어 있는 메소드는 사용불가
// 대신 ? 클래스의 참조 변수에 T 클래스의 인스턴스를 대입하여 사용 가능
class ForEachDemo {
public static void main(String[] args) {
List<String> ls = Arrays.asList("Box", "Robot");
// forEach 메소드를 쓰고 싶은데
// forEach 메소드는 Consumer 인터페이스가 정의되길 원함
// 즉, accept 메소드를 정의해줘야 Iterable 객체 안의 모든 요소에 대하여 accept 메소드 적용
ls.forEach(t -> System.out.println(t));
ls.forEach(System.out::println);
}
}
인스턴스 메소드 참조 2: 인스턴스 없이 인스턴스 메소드 참조
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;
}
}
ToIntBiFunction<T, U> int applyAsInt(T t, U u); // 두 개의 인스턴스를 받아서 int를 리턴
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 을
ToIntBiFunction<IBox, IBox> bf = IBox::larger;
이렇게 줄일 수 있다.
생성자 참조
Function<T, R> R apply(T t)
// Function 인터페이스의 추상 메소드 apply는 T형 매개변수를 받아 R형 인스턴스를 리턴한다.
class StringMaker {
public static void main(String[] args) {
Function<char[], String> f = ar -> new String(ar);
char[] src = {'R', 'o', 'b', 'o', 't'};
String str = f.apply(src);
System.out.println(str);
}
}
이 때 람다식을 다음과 같이 표현할 수 있다.
Function<char[], String> f = String::new;
'언어 > Java' 카테고리의 다른 글
스트림의 이해 (0) | 2023.02.11 |
---|---|
Optional 클래스 (0) | 2023.02.11 |
정의되어 있는 함수형 인터페이스 (0) | 2023.02.07 |
람다와 함수형 인터페이스 (0) | 2023.02.07 |
람다(lambda)의 소개 (0) | 2023.02.03 |