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
    }
}