[Java] 메서드 레퍼런스

Jeini·5일 전
0

☕️  Java

목록 보기
70/70
post-thumbnail

❗️Java 메서드 레퍼런스를 이해하고, Stream API에 적용할 수 있다


📌 메서드 레퍼런스(Method Reference)

자바의 람다식(Lambda)을 더 간결하게 쓸 수 있게 해주는 문법

  • 메서드를 참조, 해당 메서드의 동작을 다른 코드에서 재사용할 수 있는 기능이다.

📘 예시로 이해하기

✅ 람다식 버전

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

✅ 메서드 레퍼런스 버전

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

👉 System.out::println
이건 “System.outprintln 메서드를 그대로 써라” 라는 뜻.
결과는 완전히 동일하다.


📌 자주 사용되는 함수형 인터페이스들

☕️ 먼저 개념부터

  • 함수형 인터페이스는 추상 메서드가 하나만 있는 인터페이스이다.
    람다식은 결국 “그 하나의 메서드를 구현한 익명 클래스”로 동작한다.

자바에서는 대표적으로 5가지가 많이 쓰인다 👇

인터페이스파라미터 타입반환 타입추상 메서드 이름설명
Supplier<T>❌ 없음✅ TT get()값을 제공(생산) 하는 역할. 인풋 없이 결과만 반환
Consumer<T>✅ T❌ voidvoid accept(T t)값을 소비(사용) 하는 역할. 반환값 없음
Function<T, R>✅ T✅ RR apply(T t)입력을 받아 변환해서 반환
BiFunction<T, U, R>✅ T, U✅ RR apply(T t, U u)입력 2개를 받아 변환해서 반환
Predicate<T>✅ T✅ booleanboolean test(T t)입력을 받아 조건 판단 (true/false) 반환

🏭 1️⃣ Supplier<T> — “값을 공급하는 자”

@FunctionalInterface
public interface Supplier<T> {
    T get();
}
  • 매개변수가 없고, 값만 반환한다.
  • 즉, “필요할 때마다 새로운 값을 만들어 주는 공장” 같은 역할이다.

✅ 파라미터 / 반환타입

항목타입
파라미터❌ 없음
반환값T

✅ 예시

Supplier<String> supplier = () -> "java uu!";
System.out.println(supplier.get()); // java uu!

📌 설명:
get() 을 호출할 때마다 값을 “공급”해줌.
→ 주로 지연 초기화(lazy init), 랜덤값, UUID 생성 등에 쓰인다.

Supplier<Double> randomSupplier = Math::random;
System.out.println(randomSupplier.get()); // 매번 다른 난수

🍽️ 2️⃣ Consumer<T> — “값을 소비하는 자”

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
  • 입력값을 받아서 처리만 하고, 반환은 없음.
  • 말 그대로 “받은 걸 사용하는 사람”.

✅ 파라미터 / 반환타입

항목타입
파라미터T
반환값void

✅ 예시

Consumer<String> printer = s -> System.out.println("출력: " + s);
printer.accept("정잉이"); // 출력: 정잉이

📌 설명:
accept() 로 받은 값을 콘솔에 출력하거나, DB 저장, 파일 쓰기 같은 “행동”을 할 때 사용.

💡 andThen() 으로 여러 Consumer를 연결할 수도 있다 👇

Consumer<String> hello = s -> System.out.println("Hello " + s);
Consumer<String> bye = s -> System.out.println("Bye " + s);

hello.andThen(bye).accept("정잉이");
/*
Hello 정잉이
Bye 정잉이
*/

🔁 3️⃣ Function<T, R> — “입력값을 변환하는 자”

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
  • 입력값(T)을 받아서 변환 후 결과(R)를 반환.
  • “데이터를 가공해서 새로운 형태로 바꾸는 역할”

✅ 파라미터 / 반환타입

항목타입
파라미터T
반환값R

✅ 예시

Function<Integer, String> func = n -> "점수: " + n;
System.out.println(func.apply(95)); // 점수: 95

📌 설명:
map , transform , convert 같은 기능에 자주 쓰인다.
andThen() , compose() 로 함수 연결도 가능하다 👇

Function<Integer, Integer> square = x -> x * x;
Function<Integer, String> toString = x -> "결과: " + x;

System.out.println(square.andThen(toString).apply(5)); // 결과: 25

⚙️ 4️⃣ BiFunction<T, U, R> — “입력 2개를 변환하는 자”

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}
  • 입력이 2개, 반환이 1개인 Function 버전.
  • “두 값을 받아서 계산하거나 조합할 때” 사용.

✅ 파라미터 / 반환타입

항목타입
파라미터T, U
반환값R

✅ 예시

BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(10, 20)); // 30

📌 설명:
→ 예를 들어 map 에서 두 값을 병합하거나,
두 개의 객체를 합쳐 새 객체를 만드는 데 자주 써.

💡 andThen() 도 사용 가능 👇

BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
Function<Integer, String> toString = n -> "결과: " + n;

System.out.println(multiply.andThen(toString).apply(3, 4)); // 결과: 12

🔍 5️⃣ Predicate<T> — “값을 검사(판단)하는 자”

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
  • 입력값을 받아 true / false로 판단함.
  • “조건 검사기”라고 보면 된다.

✅ 파라미터 / 반환타입

항목타입
파라미터T
반환값boolean

✅ 예시

Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true

📌 설명:
filter() 나 조건 분기, 유효성 검사에 아주 자주 쓰여.
and() , or() , negate() 같은 논리연산도 가능 👇

Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;

System.out.println(isPositive.and(isEven).test(4));  // true
System.out.println(isPositive.and(isEven).test(-2)); // false

📌 메서드 참조 방법

👉 스태틱 메서드 참조
👉 생성자 참조

1️⃣ Static 메서드 참조

클래스이름::메서드이름

public class Printer {
	public static void printSomething(String text) {
    	System.out.println(text + "를 출력합니다.")
    }
}
// Static Method Rdference
Consumer<String> exe = Printer::printSomething;
Consumer<String> exe2 = text -> Printer.printSomething(text); //람다식 표현

exe.accept("do something");
exe2.accept("do something");

2️⃣ 생성자 참조

클래스이름::new

✏️ 예시 1: 기본 생성자 (파라미터 없음)

Supplier<StringBuilder> supplier = StringBuilder::new;
StringBuilder sb = supplier.get(); // 새 StringBuilder 생성됨
System.out.println(sb.append("정잉이 최고!")); // 정잉이 최고!
  • Supplier 는 파라미터가 없고 객체를 “공급”하는 인터페이스이다.
    → 그래서 기본 생성자 참조랑 잘 어울린다.

✏️ 예시 2: 매개변수가 있는 생성자

Function<String, Integer> func = Integer::new;
Integer num = func.apply("123");
System.out.println(num); // 123
  • 여기서 Integer::newInteger(String) 생성자를 참조한 것.
  • 즉, s -> new Integer(s) 와 같다.

✏️ 예시 3: 사용자 정의 클래스 생성자

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}

Function<String, Person> personCreator = Person::new;
Person p = personCreator.apply("정잉이");
System.out.println(p.name); // 정잉이
  • 람다식으로 쓰면 (name) -> new Person(name) 인데, 그걸 더 간단히 Person::new 로 줄인 것.

3️⃣ 객체 인스턴스 메서드 참조

✅ 객체참조변수(instance)::메서드이름(method)

  • 이미 생성된 객체의 인스턴스 메서드를 참조할 때 사용.
  • 즉, 객체.메서드() 대신 객체::메서드 로 쓴다.
class Greeter {
    void hello(String name) {
        System.out.println("안녕, " + name + "!");
    }
}

Greeter greeter = new Greeter();
Consumer<String> greet = greeter::hello;
greet.accept("정잉이"); // 안녕, 정잉이!

4️⃣ 임의 객체(클래스 타입의 인스턴스) 메서드 참조

클래스이름(Class)::메서드이름(method)

  • Stream 사용으로 자연스럽게 쓰게 됨.
  • 특정 객체가 아니라, 임의의 같은 타입 객체에 대해 메서드를 호출할 때 사용한다.
  • 내부적으로는 람다의 첫 번째 파라미터가 자기 자신(this) 으로 들어가는 구조이다.
List<String> names = List.of("정잉이", "홍길동", "김코딩");

List<String> upper = names.stream()
        .map(String::toUpperCase) // 각 문자열의 toUpperCase 호출
        .toList();

System.out.println(upper); // [정잉이, 홍길동, 김코딩]
profile
Fill in my own colorful colors🎨

0개의 댓글