Method Reference
Lambda의 마지막 입니다.
기존 람다식을 더 줄일 수 있는 Method reference에 대해서 설명합니다.
기본 형식은 아래와 같습니다.
ClassName::Method
ex) Person::getAge
person class에 있는 getAge()함수의 레퍼런스를 표현으로 실제 실행하는것이 아니기 때문에 ()를 붙이지 않습니다.
아래 예제를 보면 람다식이 method reference로 어떻게 표현되는지 감을 잡을 수 있습니다.
(Person p) -> p.getAge() ==> Person::getAge
() -> Thread.currentThread().dumpStack() ==> Thread.currentThread::dumpStack
(str, i) -> str.substring(i) ==> String::substring
(String s) -> System.out.println(s) ==> System.out::println
Method reference를 만드는 방법
다음과 같이 세가지 형태에 대해서 method reference의 표기가 가능합니다.
1. class내부에 존재하는 static 함수인 경우
(args) -> ClassName.staticMethod(args)
ClassName::statckMethod
ex) (String s) -> Integer.parseInt(s); /*Lambda expression*/
Integer::parseInt; //method reference
2. class 내부에 존재하는 일반 함수인 경우
(arg0, rest) -> arg0.instanceMethod(rest);
arg0의ClassName::instanceMethod;
ex) (List<String> list, element) -> list.containts(element); /*Lambda expression*/
List::contains; //method reference
3. 지역변수에 할당된 object가 있는경우
(args) -> expr.instnaceMethod(args)
expr::InstanceMethod
public class Main {
public Person mPerson = new Person();
public void methodReferenceTest() {
Consumer<String> setName1 = name -> mPerson.setName(name);
Consumer<String> setName2 = mPerson::setName; /*method reference*/
Supplier<String> getName1 = () -> mPerson.getName();
Supplier<String> getName2 = mPerson::getName; /*method reference*/
}
}
class Person {
private String mName;
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
}
생성자 레퍼런스
클래스를 생성 할 때 사용하는 생성자 역시 레퍼런스로 사용할 수 있습니다.
ClassName::new
위에 들었던 Person 객체처럼 만약 인수가 없는 생성자가 있다면 생성자의 Lambda signature는 () -> T 입니다.
즉 Supplier 형태 입니다.
따라서 이런경우 아래와 같이 생성해서 사용할 수 있습니다.
Supplier<Person> constructor1 = Person::new;
Person p1 = constructor1.get();
만약 생성자에 인수가 하나 있다면 (T) -> R 형태이므로 Function을 사용하면 됩니다.
따라서 Product의 class가 아래와 같다면 아래와 같이 표현할 수 있습니다.
Function<String, Product> constructor2 = Product::new;
Product p2 = constructor2.apply("새우깡");
만약 생성자가 여러개이거나 functionalInterface에서 지원하지 않는 형태일 경우 따로 정의해서 만들어 써도 됩니다.
여기서 중요한 점은 생성자 reference를 사용할 경우 객체의 생성이 delay된다는 점입니다. 즉 lazy initialize가 가능하게 됩니다.
실제 객체는 constructor1.get(); 처럼 get()을 호출하는 순간이나, constructor2.apply("새우깡"); 을 호출하는 순간에 생성됩니다.
이를 잘 이용한다면 factory method pattern에 아주 유용하게 쓸수 있습니다.
만약 A,B,C,D 타입에 따라 객체 생성을 해주는 클래스가 있다면 아래와 같이 구현됩니다.
public class Factory {
public static final int A_TYPE = 0;
public static final int B_TYPE = 1;
public static final int C_TYPE = 2;
public static final int D_TYPE = 3;
public IType getNewClass(int type) {
switch(type) {
case A_TYPE:
return new A();
case B_TYPE:
return new B();
case C_TYPE:
return new C();
case D_TYPE:
return new D();
default:
return new D();
}
}
}
interface IType {}
class A implements IType {}
class B implements IType {}
class C implements IType {}
class D implements IType {}
이 코드를 Method Reference를 사용시 아래와 같이 변경됩니다.
Switch문 제거를 위해서 HashMap을 사용하면서 method reference로 value를 같습니다.
이때의 ClassName::new는 정의된 시점이 아니라 get()함수가 불리는 순간 생성되므로 lazy initializing 되며, simple한 코드로 리팩토링 됩니다.
public class LambdaFactory {
public static final int A_TYPE = 0;
public static final int B_TYPE = 1;
public static final int C_TYPE = 2;
public static final int D_TYPE = 3;
public static HashMap<Integer, Supplier<IType>> mTypeFactoryMap;
static {
mTypeFacotryMap = new HashMap<>();
mTypeFacotryMap.put(A_TYPE, A::new);
mTypeFacotryMap.put(A_TYPE, B::new);
mTypeFacotryMap.put(A_TYPE, C::new);
mTypeFacotryMap.put(A_TYPE, D::new);
}
public IType getNewClass(int type) {
int key = D_TYPE;
if (mTypeFacotryMap.containsKey(type)) {
key = type;
}
return mTypeFacotryMap.get(key).get();
}
}
'개발이야기 > Java' 카테고리의 다른 글
Java 8 Comparator (0) | 2017.09.14 |
---|---|
Java 8 String join (3) | 2017.09.13 |
Java 8 Lambda Expression - 람다식 #3 (0) | 2017.09.12 |
Java 8 Lambda Expression - 람다식 #2 (1) | 2017.09.12 |
Java 8 Lambda Expression - 람다식 #1 (1) | 2017.09.09 |