article thumbnail image
Published 2022. 8. 30. 09:51

콜론 두개 (::) 

 

이중 콜론 연산자)의 정식 명칭은 메소드 참조 표현식(method reference expression)이며, 결론부터 말하자면 람다식에서 파라미터를 중복해서 쓰기 싫을 때 사용합니다.

 

말 그대로 람다 표현식(expression)에서만 사용 가능하고, 사용 방법은 [인스턴스]::[메소드명(또는 new)]으로 사용하는데, 예제를 통해 보는 것이 이해가 빠릅니다. 스태틱 메소드인 경우 인스턴스 대신 클래스 이름으로 사용할 수 있습니다.

 

Method Reference

Lambda의 마지막 입니다. 

기존 람다식을 더 줄일 수 있는 Method reference에 대해서 설명합니다.

 

 

기본 형식은 아래와 같습니다.

ClassName::Method

ex) Person::getAge

person class에 있는 getAge()함수의 레퍼런스를 표현으로 실제 실행하는것이 아니기 때문에 ()를 붙이지 않습니다.

 

(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의 표기가 가능합니다.

 

 

 

class내부에 존재하는 static 함수인 경우

 

(args) -> ClassName.staticMethod(args)ClassName::statckMethod
ex) (String s) -> Integer.parseInt(s); /*Lambda expression*/     Integer::parseInt; //method reference

 

 

 

class 내부에 존재하는 일반 함수인 경우

 

(arg0, rest) -> arg0.instanceMethod(rest);arg0의ClassName::instanceMethod;
ex) (List<String> list, element) -> list.containts(element); /*Lambda expression*/    List::contains; //method reference

 

 

 

지역변수에 할당된 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("새우깡");

 

 

예1) 리스트를 순회하면서 println을 하고자 할 때

import java.util.Arrays;
import java.util.List;
public class DoubleColonTest {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("김갑순", "김갑돌");
        // x를 건네고 받는 과정에서 x를 두 번 적게 된다.
        names.forEach(x -> System.out.println(x));
        // 아예 x들을 빼버리고 아래와 같이 작성할 수 있다.
        names.forEach(System.out::println);
    }
}

forEach의 첫 번째 구문은 람다식이 x를 파라미터로 넘기고 println(x)이 그 파라미터를 받는 과정에서 x를 두 번 사용하게 됩니다. 람다식이 건네는 파라미터와 받는 쪽의 파라미터가 동일할 때, 두 번째 구문처럼 System.out::println으로 줄여쓸 수 있습니다.

 

사용 방법이 [인스턴스]::[메소드명(또는 new)] 라고 했는데, 여기서는 System.out이 인스턴스 부분이며, 그 인스턴스의 메소드 중 하나인 println이 메소드명으로 사용되었습니다.

 

 

예2) Stream의 map()을 사용해 새로운 스트림을 생성하고자 할 때

import java.util.Arrays;
import java.util.List;
public class DoubleColonTest {
    public String addNim(String s) {
        return s + "님";
    }
    public static void main(String[] args) {
        List<String> names = Arrays.asList("김갑순", "김갑돌");;
        DoubleColonTest dct = new DoubleColonTest();
        names.stream().map(x -> dct.addNim(x)).forEach(System.out::println); // 적용 전
        names.stream().map(dct::addNim).forEach(System.out::println); // 적용 후
    }
}

x -> dct.addNim(x) dct:addNim로 바꿀 수 있습니다.

만약 addNim()이 스태틱 메소드인 경우 다음과 같이 사용 가능합니다.

 

names.stream().map(DoubleColonTest::addNim).forEach(System.out::println);

 

 

예3) 생성자가 파라미터 한 개로 이루어진 DTO의 배열을 생성하고자 할 때

public class Dog {
    private String name;
    private String species;
    // ...setter
    // ...getter
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", species='" + species + '\'' +
                '}';
    }
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DoubleColonTest {
    public static String addNim(String s) {
        return s + "님";
    }
    public static void main(String[] args) {
        List<String> names = Arrays.asList("김갑순", "김갑돌");
        List<Dog> dogs1 = names.stream()
                .map(x -> new Dog(x)) // 적용 전
                .collect(Collectors.toList());
        List<Dog> dogs2 = names.stream()
                .map(Dog::new) // 적용 후
                .collect(Collectors.toList());
        dogs2.forEach(x -> x.setSpecies("이탈리안 그레이 하운드"));
        System.out.println(dogs1);
        System.out.println(dogs2);
    }
}

x -> new Dog(x); 도 위와 같이 Dog::new 로 축약할 수 있습니다. 이것은 생성자 참조 표현식이라고도 합니다.

 

 

게터를 참조 표현식으로 변환: 스트림을 해시맵으로 그룹화하는 예제

... .stream()
                .collect(Collectors.groupingBy(Dog::getSpecies, HashMap::new, toList()));
// x -> x.getSpecies() 가 Dog::getSpecies 와 동일

x -> x.getSpecies()라는 람다식이 필요한 경우 Dog::getSpecies로 줄여쓸 수 있습니다.

 

 

예4) 함수형 인터페이스를 구현할 때 파라미터의 종류와 개수가 같으면 사용 가능 

@FunctionalInterface
public interface StringToDog {
    public String convert(String name, String species, int price);
}
public class Dog {
    private String name;
    private String species;
    private int price;
    public static String introduce(String name, String species, int price) {
        return name + " : " + species + " : " + price;
    }
    // ............
}
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DoubleColonTest {
    public static void main(String[] args) {
        // 파라미터의 개수와 타입이 같다면 메소드 참고 표현식이 적용된다.
        StringToDog stringToDog1 = (name, species, price) -> Dog.introduce(name, species, price);
        StringToDog stringToDog2 = Dog::introduce;
        System.out.println(stringToDog1.convert("개똥이", "믹스", 100));
        System.out.println(stringToDog2.convert("누렁이", "믹스", 1000));
    }
}

함수형 인터페이스를 구현할 때 람다식을 사용하는데 파라미터의 개수, 종류와 표현식에서 사용된 메소드의 파라미터 개수, 종류가 같다면 위와 같이 축약할 수 있습니다.

 

 

 

더블콜론과 람다.

더블콜론은 람다의 간결한 버전중 하나이다 아래 예제를 보자

List<String> list = List.of("Peter", "Thomas", "Edvard", "Gerhard");

    // print using lambda
    list.forEach(item -> System.out.println(item));

 

 

자바의 더블콜론은 위 람다식을 아래와 같이 더욱 간결하게 해준다

즉 람다식이 이미 존재하는 메소드와 동일한 기능이면 메소드 레퍼런스로 람다식을 대체할 수 있다.

  // print using :: (method reference operator)
    list.forEach(System.out::println);

 

복사했습니다!