본문 바로가기
개발 언어/Java

[Java] 메서드 참조(Method reference) - 정적 메서드, 생성자 참조와 람다표현식 활용

by 정권이 내 2021. 12. 15.

 

[Java8] 메서드 참조(Method reference)

 

메서드 참조란 람다 표현식(Lambda expression) 의 축약형으로 볼수 있습니다.

 

이전 포스팅에서 작성했던 사과 필터링 예제코드에서 사과를 무게별로 정렬할때 사용했던 compareTo

메서드를 예시로 들어보겠습니다.

 

[람다 표현식]

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

 

[메서드 참조]

Comparator 클래스의 comparing 메서드를 사용한 방법입니다.

inventory.sort(Comparator.comparing((Apple::getWeight)));

 

메서드 참조를 이용하는 이유

람다 표현식은 메서드를 사용하라고 가르키는 방식이라면 메서드 참조는 해당 메서드를 직접 참조하는

방식인데 메서드 참조 방식이 가독성 측면에서 좀더 좋기 때문입니다.

 

메서드 참조는 클래스명과 메서드명 사이에 "::" (쌍 콜론) 구분자를 넣어서 사용합니다.

아래 표는 Java 에서 사용할수 있는 메서드 참조의 예시입니다.

람다 표현식메서드 참조
(Apple apple) -> apple.getWeight()Apple::getWeight
() -> Thread.currentThread.dumpStack()Thread.currentThread()::dumpStack
(str, i) -> str.substring(i)String::substring
(String s) -> System.out.println(s) Sytem.out::println
(String s) -> this.isValidName(s)this::isValidName

 

 

메서드 참조 만들기

 

1. 정적 메서드(static method) 참조

정적 메서드라는 것은 클래스의 객체를 생성하지 않고 클래스 자체로 호출할수 있는 함수 입니다.

 

forEach 메서드Consumer 함수형 인터페이스를 인자로 받아서 List<T> 에 대해 특정 작업을 수행 해주는

메서드 입니다.

printStr 메서드는 String 타입의 인자를 받아 콘솔창에 출력하는 정적 메서드입니다.

public static <T> void forEach(List<T> list, Consumer<T> consumer){
    for(T t : list){
        consumer.accept(t);
    }
}

public static void printStr(String str){
    System.out.println(str);
}

 

정적메서드 printStr 를 람다표현식과 메서드 참조 방식으로 각각 호출하고 있습니다.

public static void main(String[] args) {

    List<String> messages = Arrays.asList("Hello", "World");

    System.out.println("\n============ Lambda Expression ============\n");
    forEach(messages, (String str) -> FunctionalTest.printStr(str));

    System.out.println("\n============ Method Reference ============\n");
    forEach(messages, FunctionalTest::printStr);

}

 

[FunctionalTest.java 전체 코드]

public class FunctionalTest {

    public static <T> void forEach(List<T> list, Consumer<T> consumer) {
        for (T t : list) {
            consumer.accept(t);
        }
    }

    public static void printStr(String str) {
        System.out.println(str);
    }

    public static void main(String[] args) {

        List<String> messages = Arrays.asList("Hello", "World");

        System.out.println("\n============ Lambda Expression ============");
        forEach(messages, (String str) -> FunctionalTest.printStr(str));

        System.out.println("\n============ Method Reference ============");
        forEach(messages, FunctionalTest::printStr);

    }
}

 

2. 다양한 형식의 인스턴스 메서드 참조

static 이 아닌 클래스 객체의 메서드를 참조할때 사용합니다.

 

[Car.java]

자동차의 이름을 출력하는 메서드 PrintCarName 을 선언해놓은 클래스 입니다.

public class Car {

    String name;

    public Car(String name) {
        this.name = name;
    }

    public void PrintCarName() {
        System.out.printf("Car Name is : %s\n", name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

[FunctionalTest.java]

public class FunctionalTest {

    public static <T> void forEach(List<T> list, Consumer<T> consumer) {
        for (T t : list) {
            consumer.accept(t);
        }
    }
    
    public static void main(String[] args) {

        List<Car> carList = new ArrayList<>();
        carList.add(new Car("SM5"));
        carList.add(new Car("SM7"));

        System.out.println("\n============ Lambda Expression ============");
        forEach(carList, (Car tmpCar) -> tmpCar.PrintCarName());

        System.out.println("\n============ Method Reference ============");
        forEach(carList, Car::PrintCarName);

    }
}

 

생성자(Constructor) 참조 만들기

정적 메서드 참조와 비슷하게 생성자를 참조할수 있습니다. 차이점은 new 키워드를 사용하는 것입니다.

 

1. 인자가 없는 생성자

// Lambda Expression
Supplier<Car> supplier1 = () -> new Car();
Car car1 = supplier1.get();

// Method Reference
Supplier<Car> supplier2 = Car::new;
Car car2 = supplier2.get();

 

2. 인자가 있는 생성자

// Lambda Expression
Function<String, Car> function1 = (String s) -> new Car(s);
Car car1 = function1.apply("SM5");

// Method Reference
Function<String, Car> function2 = Car::new;
Car car2 = function2.apply("SM5");

 

람다표현식과 메서드 참조 활용

 

아래 포스팅에서 예제로 만든 사과 필터링 문제를 동작 파라미터화, 익명클래스. 람다표현식, 메서드참조

방식을 순서대로 사용해 코드가 간결해지는 과정을 살펴보겠습니다.

무게로만 정렬을 할것이기 때문에 여기서는 코드를 좀더 단순화 했습니다.

 

[Java] 동작 파라미터화의 개념과 람다표현식으로 활용하기 #Java8

Java8 동적 파라미터화 (behavior parameterized) 와 람다표현식 소프트웨어 개발을 하다보면 고객들의 요구사항이 바뀌는 경우는 하루에도 수차례씩 일어나는것이 일상입니다. 그렇게 때문에 소프트웨

ksr930.tistory.com

 

[sort 메서드 원형]

정렬 기능은 Java 에서 제공하고 있는 sort 메서드를 사용하지만 정렬 방식인 Comparator 는 사용자가

직접 인자로 전달해야 합니다.

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

 

공통 코드

[Apple.java]

public class Apple {

    Integer weight;

    Apple(int weight) {
        this.weight = weight;
    }

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }
}

 

 

1. 동작 파라미터화

함수형 인터페이스를 상속받은 AppleComparator 클래스를 별도로 생성한후 코드블럭을 해당클래스의

메서드에 생성하는 방법입니다. 만들어놓은 코드블럭은 sort 메서드 사용시 인자로 전달하게 됩니다.

 

[AppleComparator.java]

import java.util.Comparator;

public class AppleComparator implements Comparator<Apple> {
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
}

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.List;

public class FunctionalTest {

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100));
            inventory.add(randApple);
        }
		
        // 동작 파라미터화
        inventory.sort(new AppleComparator());
        for (Apple apple : inventory)
            System.out.printf("Apple Weight : %d\n", apple.getWeight());

    }
}

 

2. 익명 클래스

클래스를 한번만 생성하여 사용하려는 목적이라면 클래스를 익명으로 생성하는 방법이 있습니다.

sort 메서드 사용시 인자에 익명클래스를 직접 전달합니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class FunctionalTest {

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100));
            inventory.add(randApple);
        }
		
		// 익명 클래스
        inventory.sort(new Comparator<Apple>() {
            @Override
            public int compare(Apple o1, Apple o2) {
                return o1.getWeight().compareTo(o2.getWeight());
            }
        });
        
        for (Apple apple : inventory)
            System.out.printf("Apple Weight : %d\n", apple.getWeight());

    }
}

 

3. 람다 표현식 사용

익명 클래스 방식은 코드가 길어지므로 람다표현식으로 좀더 간결하게 작성할수 있습니다. 이전 포스팅에서

설명했듯이 함수형 인터페이스를 인자로 받는 메서드에는 람다표현식을 사용할수 있습니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.List;

public class FunctionalTest {

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100));
            inventory.add(randApple);
        }
		
		// 람다표현식
        inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

        for (Apple apple : inventory)
            System.out.printf("Apple Weight : %d\n", apple.getWeight());

    }
}

 

위 코드에서 람다표현식은 좀더 간결하게 표현할수 있습니다.

 

[Comparable 함수 원형]

Comparator는 Comparable 키를 추출하여 Comparator 객체로 만드는 Function 함수를 인자로 받는

정적 메서드 comparing을 포함합니다.

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.List;

public class FunctionalTest {

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100));
            inventory.add(randApple);
        }
		
		// 람다표현식
		inventory.sort(Comparator.comparing((Apple apple) -> apple.getWeight()));

        for (Apple apple : inventory)
            System.out.printf("Apple Weight : %d\n", apple.getWeight());

    }
}

 

 

4. 메서드 참조

람다표현식을 마지막으로 한번더 간결하게 할수있습니다. 코드가 짧아져서 간결해졌을 뿐만 아니라 가독성과

코드의 목적이 좀더 확실하게 보여집니다.

 

[FunctionalTest.java]

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class FunctionalTest {

    public static void main(String[] args) {

        List<Apple> inventory = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            Apple randApple = new Apple((int) (Math.random() * 150 + 100));
            inventory.add(randApple);
        }
        
		// 메서드 참조
        inventory.sort(Comparator.comparing(Apple::getWeight));

        for (Apple apple : inventory)
            System.out.printf("Apple Weight : %d\n", apple.getWeight());

    }
}

 

 

 

[Java] 람다표현식으로 함수형 인터페이스(Predicate, Consumer, Function, Supplier) 활용하기

[Java8] 람다 표현식 (Lambda expression) 람다 표현식은 익명함수를 단순화하여 메서드의 인자로 전달하는 표현 방식 입니다. 익명함수 처럼 이름은 존재하지 않고 1급 객체라는 특징을 가지고 있습니

ksr930.tistory.com

 

[Java] 동작 파라미터화의 개념과 람다표현식으로 활용하기 #Java8

Java8 동적 파라미터화 (behavior parameterized) 와 람다표현식 소프트웨어 개발을 하다보면 고객들의 요구사항이 바뀌는 경우는 하루에도 수차례씩 일어나는것이 일상입니다. 그렇게 때문에 소프트웨

ksr930.tistory.com

 

반응형

댓글