Page tree
Skip to end of metadata
Go to start of metadata

Introduction

Java8에 추가된 콜론 연산자(::)에 대해 잘 설명된 외국의 아티클을 번역하면서 부연설명을 덧붙이겠습니다.

원문을 읽고싶다면, http://www.baeldung.com/java-8-double-colon-operator 을 확인 바랍니다.

Prerequisite


 

1. 개요

이 기사에서 우리는 Java 8의 이중 콜론 연산자 (::)에 대해 논의하고 연산자가 사용될 수 있는 예를 살펴보겠습니다.


 

2. Lambda에서 Double Colon Operator까지

람다표현식으로 코드가 매우 간결해질 수 있다는 것을 확인할 수 있었습니다.

예를들어, comparator을 생성하기 위해 다음과 같은 구문이면 충분합니다. (validtaion check는 생략합니다)

람다표현식
Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge());

 또는

람다표현식(with type inference)
Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge());

 그러나 이보다 더 잘 나타내고 읽기 쉽게할 수 있는 코드를 만들 수 있을까요? 살펴보죠.

이중 콜론 연산자
Comparator c = Comparator.comparing(Computer::getAge);

 우리는 이름만으로 특정 메소드를 호출할 수 있도록 :: 연산자를 사용합니다.

 

참고

위의 코드를 java 8 이전에서는 Inner Class를 이용해서 구현할 수 있었습니다.

Comparator<Computer> abstractInnerClass = new Comparator<Computer>(){

			@Override
			public int compare(Computer c1, Computer c2) {
				return c1.getAge().compareTo(c2.getAge());
			}
			
		};

그러나 java 8 이후에는 람다표현식, Method Reference를 이용해 훨씬 간결하게 표현할 수 있습니다.


 

3. 어떻게 작동할까?

간단하게 말하면, Method Reference를 사용할 때, 타겟 레퍼런스는 :: 앞에 놓고 메소드명은 :: 뒤에 놓습니다.

Computer::getAge;

 우리는 Computer라는 클래스에 정의된 getAge 메소드에 대한 Method Reference를 찾고 그 Method Reference를 이용해 실행시킬 것입니다. 

Function<Computer, Integer> getAge = Computer::getAge;
Integer computerAge = getAge.apply(c1);

 함수를 참조하고 있다는 것과 함수의 인자가 정확해야 한다는 것에 주목하세요.


 

4. Method Reference

우리는 다양한 경우에서 이중 콜론 연산자를 사용할 수 있습니다.

1) 정적 메소드

List inventory = Arrays.asList(
  new Computer( 2015, "white", 35), new Computer(2009, "black", 65));
inventory.forEach(ComputerUtils::repair);

(ComputerUtils 의 repair 가 정적 메소드라는 것을 가정하고 있는 것으로 보입니다)

 

2) 기존 객체의 인스턴스 메소드

다음은 기존 객체의 인스턴스의 메소드를 참조하는 흥미로운 예제를 살펴보겠습니다.

System.class 에 정의된 out 이라는 필드는 PrintStream 타입입니다. 

Computer c1 = new Computer(2015, "white");
Computer c2 = new Computer(2009, "black");
Computer c3 = new Computer(2014, "black");
Arrays.asList(c1, c2, c3).forEach(System.out::print);

따라서 java 에 이미 정의된 기존 객체의 인스턴스(System.out) 의 메소드(print) 를 호출하기도 합니다.


3) 새로 생성한 객체의 인스턴스 메소드

Computer c1 = new Computer(2015, "white", 100);
Computer c2 = new MacbookPro(2009, "black", 100);
List inventory = Arrays.asList(c1, c2);
inventory.forEach(Computer::turnOnPc);

 사용자가 정의한 Computer라는 클래스의 turnOnPc 메소드를 호출할 수도 있습니다. 

 

4) 특정 객체의 슈퍼 메서드

Computer(super class)에 calculateValue라는 메소드가 있다고 가정합니다.

public Double calculateValue(Double initialValue) {
    return initialValue/1.50;
}

그리고 MacbookPro(sub class) 에서 calculateValue를 재정의 했습니다. 

@Override
public Double calculateValue(Double initialValue){
    Function<Double, Double> function = super::calculateValue;
    Double pcValue = function.apply(initialValue);
    return pcValue + (initialValue/10) ;
}

MacbookPro 인스턴스에서 calculateValue 메소드를 호출하면, Computer(super class)의 calculateValue도 호출될 것입니다. 

타겟 메소드에 클래스이름이 아닌 super 도 올 수 있다는 것을 의미합니다. 

macbookPro.calculateValue(999.99);

 

5. Constructor Reference

1) 새로운 인스턴스 생성하기

객체를 인스턴스화 하는 생성자를 참조할 수도 있는데 그것은 매우 간단합니다. 

@FunctionalInterface
public interface InterfaceComputer {
    Computer create();
}
 
InterfaceComputer c = Computer::new;
Computer computer = c.create();

그렇다면 생성자에서 두개의 파라미터를 가지는 경우에는 어떻게 해야할까요?

BiFunction<Integer, String, Computer> c4Function = Computer::new; 
Computer c4 = c4Function.apply(2013, "white");

만약 파라미터가 3개 이상이라면 새로운 Functional interfce를 정의해줘야합니다. 

@FunctionalInterface
interface TriFunction<A, B, C, R> { 
    R apply(A a, B b, C c); 
    default <V> TriFunction<A, B, C, V> andThen( Function<? super R, ? extends V> after) { 
        Objects.requireNonNull(after); 
        return (A a, B b, C c) -> after.apply(apply(a, b, c)); 
    } 
}

 

2) 배열 생성하기

마지막으로 5개의 Computer 객체의 배열을 만드는 방법입니다.

Function <Integer, Computer[]> computerCreator = Computer[]::new;
Computer[] computerArray = computerCreator.apply(5);

Conclusions

Java 8에서 추가된 이중 콜론 연산자를 다양하게 활용할 수 있고, 덕분에 간결한 코드를 작성할 수 있습니다.

특히 Streams와 함께하면 더 유용합니다.