java8 - system.out::println(double colone, method reference)

들어가기

::(double conlon operator)이라고도 부르는것 같은데, 정식 명칭은 method reference라고 부른다.

method reference 이해하기

메소드 참조(method reference)는 메소드를 참조해서 매개변수의 정보 및 리턴 타입을 미리 알아내어, 람다식에서 사용하는 매개 변수를 생략하는 방식의 표현법이다.

말로 조금 풀어보자면 대상::메소드 가 있다면, 대상에서 메소드의 정보를 추출하여 람다식 처럼 익명 구현객체를 생성하는 것이다. 다만 참조하는 메소드의 매개변수를 생략 할 수 있다.

아래 단계별로 소스로 method reference를 알아보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public static void main(String[] args) {
List<Integer> testList = Arrays.asList(1,2,3,4,5);

//1단계: 함수인터페이스를 익명구현객체로 구현
testList.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer x) {
System.out.println(x);
}

});

//2단계: 1단계를 람다식으로 간단하게 표현
testList.forEach((x) -> System.out.println(x));

//3단계: 2단계를 더 간단하게 표현 : methed reference
testList.forEach(System.out::println);

//method reference도 익명구현객체를 생상하므로 이처럼 참조변수에 할당 할 수 있다.
Consumer<Integer> c = System.out::println;
}

1~3단계 모두 익명구현객체를 생성하는 코드이다. 점점 약식화된 표현식이며 3단계가 method refernece이다.

람다식도 굉장시 간단하지만, 위처럼 참조하는 메소드의 매개변수에 어떠한 변화 없이 그냥 사용할경우 매개변수를 생략할 수 있다.

처음에는 뭔가 코드 가독성이 떨어지는 것 같았지만, 보다보니 간편한거 같기도 하다.

사용법1

메소드 참조는 정적 또는 인스턴스 메소드, 그리고 생성자도 참조 할 수 있다.

참조할 메소드가 정적메소드인경우

클래스::정적메소드

1
2
3
4
5
6
7
8

public class Test{
public static void talk(String x){
System.out.println( x + "하하하");
}
}

Consumer<String> c2 = Test::talk;

참조할 메소드가 인스턴스 메소드인경우

먼저 클래스 이용하여 객체를 생성한뒤, 아래처럼 참조변수를 이용해서 메소드를 참조한다.

참조변수::메소드

1
2
3
4
5
6
7
8
9
10

public class Test{
public void talk(String x){
System.out.println( x + "하하하");
}
}

Test t1 = new Test();

Consumer<String> c3 = t1::talk;

사용법2

람다식에서 실행될 코드 컨텍스트 내부에는 람다식 외부의 클래스 멤버의 메소드를 사용할 수도 있고, 람다식에 매개변수의 멤버 메소드를 사용할 수도 있다.

1
(a, b) -> { a.instanceMethod(b); }

메소드 참조에서 위 코드를 아래처럼 표현할 수 있다.

클래스 :: instanceMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class A{
public int testB(B b) {
return 1;
}
}

class B{

}

public class Test2 {

public static void main(String[] args) {
// TODO Auto-generated method stub
A a = new A();
B b = new B();
//람다방식
ToIntBiFunction<A, B> f1 = (x, y) -> x.testB(y);
System.out.println(f1.applyAsInt(a, b));
//메소드 참조방식
ToIntBiFunction<A, B> f2 = A::testB;
System.out.println(f2.applyAsInt(a, b));
}

}

생성자 참조

람도식으로 생성자를 호출해서 객체를 생성할 수 있다.

(a, b) -> { return new 클래스(a, b); }

메소드 참조역시 람다처럼 생성자 참조가 가능하다.

클래스 :: new

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Account {

String name;
String id;

public Account(String name) {
this.name = name;
this.id = "test";
}

public Account(String name, String id) {
this.name = name;
this.id = id;
}

}

public class Test3 {

public static void main(String[] args) {

//람다 방식
Function<String, Account> f1 = (x) -> { return new Account(x); };
Account ac1 = f1.apply("오레오");

//메소드참조방식, 함수적인터페이스 타입을 이용해서 오버로딩된 생성자를 선택적 호출할수 있다.(Function)
Function<String, Account> f2 = Account::new;
Account ac2 = f2.apply("메로나");

//메소드참조방식, 함수적인터페이스 타입을 이용해서 오버로딩된 생성자를 선택적 호출할수 있다.(BiFunction)
BiFunction<String, String ,Account> f3 = Account::new;
Account ac3 = f3.apply("초코파이", "오예스");


}

}

마무리

method referece는 람다를 더 간결하게 표현하는 문법니다.
하지만 개인적으로 너무 간결하여 코드의 가독성이 떨어지는 것이 아닌가 생각해본다.
List의 foreach에 System.out::println 정도 쓰기에는 좋아보이긴 한다.

참고자료

https://stackoverflow.com/questions/31020269/what-is-the-use-of-system-outprintln-in-java-8

https://stackoverflow.com/questions/20001427/double-colon-operator-in-java-8