❗️Java 메서드 레퍼런스를 이해하고, Stream API에 적용할 수 있다
자바의 람다식(Lambda)을 더 간결하게 쓸 수 있게 해주는 문법
✅ 람다식 버전
list.forEach(s -> System.out.println(s));
✅ 메서드 레퍼런스 버전
list.forEach(System.out::println);
👉 System.out::println
이건 “System.out
의 println
메서드를 그대로 써라” 라는 뜻.
결과는 완전히 동일하다.
자바에서는 대표적으로 5가지가 많이 쓰인다 👇
인터페이스 | 파라미터 타입 | 반환 타입 | 추상 메서드 이름 | 설명 |
---|---|---|---|---|
Supplier<T> | ❌ 없음 | ✅ T | T get() | 값을 제공(생산) 하는 역할. 인풋 없이 결과만 반환 |
Consumer<T> | ✅ T | ❌ void | void accept(T t) | 값을 소비(사용) 하는 역할. 반환값 없음 |
Function<T, R> | ✅ T | ✅ R | R apply(T t) | 입력을 받아 변환해서 반환 |
BiFunction<T, U, R> | ✅ T, U | ✅ R | R apply(T t, U u) | 입력 2개를 받아 변환해서 반환 |
Predicate<T> | ✅ T | ✅ boolean | boolean test(T t) | 입력을 받아 조건 판단 (true/false) 반환 |
<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()); // 매번 다른 난수
<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 정잉이
*/
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
✅ 파라미터 / 반환타입
항목 | 타입 |
---|---|
파라미터 | ✅ 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
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
✅ 파라미터 / 반환타입
항목 | 타입 |
---|---|
파라미터 | ✅ 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
<T>
— “값을 검사(판단)하는 자”@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
✅ 파라미터 / 반환타입
항목 | 타입 |
---|---|
파라미터 | ✅ 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
👉 스태틱 메서드 참조
👉 생성자 참조
✅ 클래스이름::메서드이름
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");
✅ 클래스이름::new
Supplier<StringBuilder> supplier = StringBuilder::new;
StringBuilder sb = supplier.get(); // 새 StringBuilder 생성됨
System.out.println(sb.append("정잉이 최고!")); // 정잉이 최고!
Supplier
는 파라미터가 없고 객체를 “공급”하는 인터페이스이다.Function<String, Integer> func = Integer::new;
Integer num = func.apply("123");
System.out.println(num); // 123
Integer::new
는 Integer(String)
생성자를 참조한 것.s -> new Integer(s)
와 같다.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
로 줄인 것.✅ 객체참조변수(instance)::메서드이름(method)
객체.메서드()
대신 객체::메서드
로 쓴다.class Greeter {
void hello(String name) {
System.out.println("안녕, " + name + "!");
}
}
Greeter greeter = new Greeter();
Consumer<String> greet = greeter::hello;
greet.accept("정잉이"); // 안녕, 정잉이!
✅ 클래스이름(Class)::메서드이름(method)
List<String> names = List.of("정잉이", "홍길동", "김코딩");
List<String> upper = names.stream()
.map(String::toUpperCase) // 각 문자열의 toUpperCase 호출
.toList();
System.out.println(upper); // [정잉이, 홍길동, 김코딩]