Java/Java 문법
익명 클래스, 람다식, 메소드 참조
hnjee
2025. 2. 28. 12:45
1. 익명 클래스
1-1. 익명 클래스
인터페이스명 변수명 = new 인터페이스명() {};
- 익명 함수 = 이름 없는 함수
- 자바에서는 익명 함수를 지원하지 않고, 익명 클래스가 대신 사용된다.
- 익명 함수 () → 함수 자체에 이름이 없음
- 익명 클래스 {} → 인터페이스를 구현하는데, 클래스의 이름이 없음
- 자바는 객체지향 언어라 원래 익명 함수를 지원하지 않았지만, 익명 클래스로 비슷한 역할을 한다.
- 인터페이스를 클래스로 직접 구현하지 않고, 익명 클래스를 통해 간편하게 구현하여 사용할 수 있다.
1-2. 익명 클래스 사용 예시
1) 일반적인 클래스 사용
interface Calculator {
int add(int a, int b);
}
// 보통은 이렇게 클래스를 별도로 정의해서 인터페이스 구현
class MyCalculator implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new MyCalculator(); // 객체 생성
System.out.println(calc.add(5, 3)); // 메소드 사용
}
}
- MyCalculator라는 이름을 가진 클래스를 만들어서 Caculator 인터페이스 구현 → 별도의 클래스 선언이 필요함
- new MyCalculator()로 인스턴스 만들어서 사용 → 객체를 따로 생성해야 사용 가능
2) 익명 클래스 사용
interface Calculator {
int add(int a, int b);
}
public class Main {
public static void main(String[] args) {
//인터페이스 자체는 인스턴스화할 수 없다
// Calculator calc = new Calculator(); //에러!
//단, 익명 클래스{}를 붙이면 컴파일러가 내부적으로
//인터페이스 구현한 클래스 만들어서 -> 그 클래스의 객체 생성까지 동시 진행함
Calculator calc = new Calculator() {
@Override
public int add(int a, int b) {
return a + b;
}
};
System.out.println(calc.add(5, 3)); // 출력: 8
}
}
- Calculator calc = new Calculator(); → 인터페이스를 인스턴스화 하는 것 불가능!
- 단, new Calculator()에 {}를 붙여서 익명 클래스를 쓰면
→ 컴파일러가 내부적으로 인터페이스를 구현한 새로운 클래스를 만들고
→ 그 클래스의 객체까지 동시에 생성함 - 즉, 일반적인 방식처럼 인터페이스를 구현한 클래스를 미리 정의해두고 → 정의된 클래스의 인스턴스를 생성하는게 아니라, 익명 클래스를 작성하는 동시에 클래스와 인스턴스 생성이 이루어지는 것.
- 따라서 익명 클래스 한 번만 작성하면 별도의 class 선언, 별도의 객체 생성 다 필요 없다.
- 일회성 함수에 적합 (이제 딱 한 번만 쓰고 버릴 거니까)
3) 비교
일반적인 방식 | 익명 클래스 | |
클래스 필요 여부 | O 인터페이스를 구현한 클래스 선언 필수 |
X 별도의 클래스 선언 필요없음 |
객체 생성 필요 여부 | O 인스턴스 생성 후 사용 |
X 별도의 객체 생성 필요 없음 |
사용 용도 | 자주 쓰는 기능 | 일회성 함수 (이벤트 리스너 등), 즉석에서 필요한 경우 |
2. 람다식
2-1. 람다식 표현
//1. (매개변수) -> {실행 코드}
(int a, int b) -> { return a + b; }
//2. 매개변수의 타입을 명시적으로 적어주지 않아도 된다.
(a, b) -> { return a + b; }
//3. 한 줄짜리 표현식이면 return과 {}도 생략 가능
(a, b) -> a + b;
//4. 매개변수 하나면 ()도 생략 가능
a -> a + 10;
- 람다식은 익명 함수, 익명 클래스를 더 짧고 간결하게 표현하는 문법이다.
- 람다식은 메서드가 딱 하나만 있는 함수형 인터페이스에서만 사용할 수 있다.
- 자바 제공 함수형 인터페이스(Predicate, Function 등) 활용 가능
2-2. 람다식 사용 예시
@FunctionalInterface //함수형 인터페이스 어노테이션
interface Calculator {
int add(int a, int b);
//int minus(int a, int b); //메소드가 한 개 이상인 경우 에러
}
public class Main {
public static void main(String[] args) {
//익명 클래스로 표현
Calculator calc = new Calculator() {
@Override
public int add(int a, int b) {
return a + b;
}
};
// 람다식으로 표현 (더 간결!)
Calculator calc = (a, b) -> a + b;
System.out.println(calc.add(5, 3)); // 출력: 8
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 익명 클래스
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
// 람다식 적용
Collections.sort(list, (s1, s2) -> s1.length() - s2.length());
System.out.println(list);
}
}
2-3. 함수형 인터페이스
- 함수형 인터페이스: 딱 하나의 추상 메서드만 가지는 인터페이스
- @FunctionalInterface: 컴파일러가 두 개 이상의 추상 메소드가 선언되었는지 체크해주는 어노테이션
- 하나의 추상 메소드가 선언된 함수형 인터페이스만 람다식의 타깃 타입이 될 수 있다.
- 자바에서는 java.util.function 패키지에서 기본적인 함수형 인터페이스를 제공한다.
인터페이스 | 메서드 | 설명 |
Predicate<T> | boolean test(T t) | 조건을 검사해서 true/false 반환 |
Function<T, R> | R apply(T t) | 입력값을 받아서 변환된 결과 반환 |
Consumer<T> | void accept(T t) | 입력값을 소비하고 반환값 없음 |
Supplier<T> | T get() | 매개변수 없이 값을 생성해서 반환 |
UnaryOperator<T> | T apply(T t) | 입력값 하나를 받아 같은 타입의 결과 반환 |
BinaryOperator<T> | T apply(T t1, T t2) | 입력값 두 개를 받아 같은 타입의 결과 반환 |
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public class Main {
public static void main(String[] args) {
Predicate<String> isEmpty = s -> s.isEmpty();
System.out.println(isEmpty.test("")); // true
System.out.println(isEmpty.test("Hello")); // false
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(10)); // true
System.out.println(isEven.test(7)); // false
}
}
3. 메소드 참조
3-1. 메소드 참조
클래스이름::메서드이름
객체이름::메서드이름
- 람다식을 더 간결하게 표현하는 문법
- 기존에 존재하는 메서드를 그대로 사용할 때 더 짧고 간결하게 표현 가능!
- :: (콜론 두 개) 를 사용해서 클래스 또는 객체의 메서드를 참조함
3-2. 메소드 참조의 유형
메소드 참조 유형 | 설명 | 예제 |
1) 정적 메서드 참조 | 클래스의 정적 메서드 사용 | ClassName::staticMethod |
2) 인스턴스 메서드 참조 (특정 객체) | 특정 객체의 메서드 사용 | instance::methodName |
3) 인스턴스 메서드 참조 (임의 객체) | 같은 타입의 여러 객체에서 공통 메서드 사용 | ClassName::methodName |
4) 생성자 참조 | 객체 생성 | ClassName::new |
1) 정적 메서드 참조 - ClassName::staticMethod
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
// 람다식 사용
Function<Double, Double> lambda = x -> Math.abs(x);
// 메서드 참조 사용 (더 간결!)
Function<Double, Double> methodRef = Math::abs;
System.out.println(lambda.apply(-5.5)); // 5.5
System.out.println(methodRef.apply(-5.5)); // 5.5
}
}
2) 인스턴스 메서드 참조 (특정 객체) - instanceName::methodName
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
String str = "hello";
// 람다식 사용
Supplier<String> lambda = () -> str.toUpperCase();
//이미 존재하는 str 객체를 capture해서 람다 객체 내부에 저장해서 str사용
// 메서드 참조 사용 (더 간결!)
Supplier<String> methodRef = str::toUpperCase;
System.out.println(lambda.get()); // "HELLO"
System.out.println(methodRef.get()); // "HELLO"
}
}
3) 인스턴스 메서드 참조 (임의 객체) - ClassName::methodName
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("banana", "apple", "cherry");
// 람다식 사용
names.sort((s1, s2) -> s1.compareTo(s2));
// 메서드 참조 사용 (더 간결!)
names.sort(String::compareTo);
System.out.println(names); // [apple, banana, cherry]
}
}
4) 생성자 참조 - ClassName::new
import java.util.function.Supplier;
class Person {
String name;
public Person() {
this.name = "No Name";
}
}
public class Main {
public static void main(String[] args) {
// 람다식 사용
Supplier<Person> lambda = () -> new Person();
// 메서드 참조 사용 (더 간결!)
Supplier<Person> methodRef = Person::new;
Person p1 = lambda.get();
Person p2 = methodRef.get();
System.out.println(p1.name); // No Name
System.out.println(p2.name); // No Name
}
}