CodeGym /행동 /JAVA 25 SELF /Method References (::): 메서드 참조

Method References (::): 메서드 참조

JAVA 25 SELF
레벨 21, 레슨 1
사용 가능

1. 소개

Method reference(메서드 참조)는 Java에서 기존에 존재하는 메서드(또는 생성자)를 함수형 인터페이스 구현으로 전달할 수 있게 해 주는 특별한 문법입니다. 시그니처가 일치한다면, 메서드를 람다식이 기대되는 곳에 “전달”할 수 있습니다.

문법:

Klass::metod
obekt::metod
Klass::new

람다가 “즉석에서 만든 작은 함수”라면, method reference는 “이미 있는 메서드를 그대로 전달”하는 것입니다. 마치 레시피를 다시 쓰는 대신 레시피 페이지 링크를 건네는 것과 비슷합니다.

간단한 예

다음과 같이 쓰는 대신:

list.forEach(s -> System.out.println(s));

이렇게 할 수 있습니다:

list.forEach(System.out::println);

더 간결하죠?

2. method reference의 종류

메서드 참조에는 네 가지 기본 형태가 있습니다. 이것만 알면 충분합니다.

정적 메서드 참조

문법: Klass::staticheskiyMetod

예:

Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"

람다로 작성하면 동일합니다:

Function<Integer, String> intToString = i -> String.valueOf(i);

객체의 인스턴스 메서드 참조

문법: obekt::metod

예:

PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("안녕, 세상!");

람다와 동등합니다:

Consumer<String> consumer = s -> printer.println(s);

클래스의 인스턴스 메서드 참조

문법: Klass::metod

여기서는 함수형 인터페이스의 첫 번째 매개변수가 메서드를 호출할 대상 객체가 됩니다.

예:

Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4

여기서 String::length는 다음과 같은 함수로 변환됩니다: (String s) -> s.length()

생성자 참조

문법: Klass::new

예:

Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();

람다와 동등합니다:

Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();

3. method reference를 언제 사용하나요?

람다가 추가 로직 없이 기존 메서드를 그대로 호출할 때 method reference를 쓰면 코드가 더 짧고 읽기 쉬워집니다.

예: 리스트 정렬

다음과 같이 쓰는 대신:

List<String> names = Arrays.asList("이반", "표트르", "안나");
names.sort((a, b) -> a.compareToIgnoreCase(b));

이렇게 할 수 있습니다:

names.sort(String::compareToIgnoreCase);

예: 컬렉션 처리

다음과 같이 쓰는 대신:

list.forEach(s -> System.out.println(s));

아주 간단하게:

list.forEach(System.out::println);

예: 요소 변환

다음과 같이 쓰는 대신:

List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

다음과 같이 할 수 있습니다:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

Stream API와 유틸리티 클래스 Collectors에 대해서는 레벨 30에서 자세히 배우게 됩니다 :P

4. method reference와 람다식 비교

동등성

method reference와 람다식은 자주 상호 대체 가능합니다. 시그니처가 일치하면 둘 다 함수형 인터페이스를 구현합니다.

예:

Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;

method reference를 쓰기 좋은 때

  • 람다가 추가 로직 없이 기존 메서드를 그대로 호출할 때.
  • 특히 긴 메서드 체인에서 가독성을 높이고 싶을 때.
  • “여기는 단순히 메서드 호출임”을 명시적으로 보여 주고 싶을 때.

method reference가 맞지 않을 때

  • 추가 로직(검증, 조건, 오류 처리 등)이 필요할 때.
  • 메서드를 호출하기 전에 매개변수를 변환해야 할 때.

예:

list.forEach(s -> {
    if (s != null) System.out.println(s);
});
// 여기서는 method reference가 맞지 않으므로 람다만 가능합니다.

5. 실습: 람다식을 method reference로 바꿔 보기

예 1: 동물 이름 출력

List<String> animals = Arrays.asList("고양이", "개", "앵무새");
animals.forEach(animal -> System.out.println(animal));

다음과 같이 바뀝니다:

animals.forEach(System.out::println);

예 2: 문자열을 숫자로 변환

List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

다음과 같이 바뀝니다:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

예 3: 이름으로 객체 정렬

List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));

다음과 같이 바뀝니다:

animalList.sort(Comparator.comparing(Animal::getName));

여기서 Animal::getName은 클래스의 인스턴스 메서드에 대한 참조입니다.

예 4: 생성자를 통한 객체 생성

Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();

다음과 같이 바뀝습니다:

Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();

6. 시그니처 매핑은 어떻게 동작하나

method reference는 메서드의 시그니처가 함수형 인터페이스의 추상 메서드와 일치할 때에만 사용할 수 있습니다.

@FunctionalInterface
interface IntToString {
    String convert(int value);
}

public class Demo {
    public static String intToHex(int value) {
        return Integer.toHexString(value);
    }

    public static void main(String[] args) {
        IntToString converter = Demo::intToHex;
        System.out.println(converter.convert(255)); // ff
    }
}

여기서 Demo::intToHex가 맞는 이유는 int를 받고 String을 반환하기 때문입니다.

7. 매개변수가 있는 생성자와 method reference

생성자가 매개변수를 받더라도, 시그니처가 일치하면 method reference를 사용할 수 있습니다.

@FunctionalInterface
interface AnimalFactory {
    Animal create(String name);
}

class Animal {
    private String name;
    public Animal(String name) { this.name = name; }
    public String getName() { return name; }
}

AnimalFactory factory = Animal::new;
Animal cat = factory.create("바르식");
System.out.println(cat.getName()); // 바르식

8. method reference 사용 시 흔한 실수

오류 1: 시그니처 불일치.
메서드 시그니처가 인터페이스의 추상 메서드와 일치하지 않으면 컴파일 오류가 발생합니다. 예를 들어 인터페이스는 두 개의 매개변수를 기대하는데, 참조가 한 개의 매개변수를 받는 메서드를 가리키는 경우입니다.

오류 2: 객체 없이 클래스의 인스턴스 메서드 참조 사용.
Klass::metod 형태를 사용할 때, 인터페이스의 첫 번째 매개변수는 호출 대상 객체가 됩니다. 매개변수의 개수나 순서를 혼동하면 매핑 오류가 발생합니다.

오류 3: 추가 로직이 필요한 곳에서 method reference 사용.
조건, 로깅, 예외 처리 등이 필요하다면 메서드 참조가 아니라 람다를 사용하세요.

오류 4: 오버로드된 메서드에 대한 참조.
클래스에 동일한 이름의 메서드가 여러 개 있으면, 컴파일러가 어떤 메서드를 선택해야 할지 모를 수 있습니다. 이럴 때는 함수형 인터페이스의 타입을 명시하는 것이 도움이 되기도 합니다.

오류 5: 객체 없이 인스턴스 메서드 참조 사용.
예를 들어 String::toUpperCase는 map에서 올바르게 동작합니다. 첫 번째 매개변수가 String 객체 자신이기 때문입니다. 그러나 적절한 컨텍스트 없이(정적 메서드를 기대하는 자리에서) 사용하면 오류가 발생합니다.

1
과제
JAVA 25 SELF, 레벨 21, 레슨 1
잠금
장치 식별자 변환기 📟
장치 식별자 변환기 📟
1
과제
JAVA 25 SELF, 레벨 21, 레슨 1
잠금
맞춤형 로깅 시스템 📝
맞춤형 로깅 시스템 📝
1
과제
JAVA 25 SELF, 레벨 21, 레슨 1
잠금
창고 재고 정리 🍎🍌🍐
창고 재고 정리 🍎🍌🍐
1
과제
JAVA 25 SELF, 레벨 21, 레슨 1
잠금
가상 애완동물 팩토리 🐾
가상 애완동물 팩토리 🐾
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION