코딩 공부/Java

[Java] Chpater 3 - 05) 람다(Lambda)

sintory-04 2025. 2. 26. 20:16

    1️⃣ 익명 클래스란?

    • 익명 클래스는 이름이 없는 클래스를 익명 클래스라고 합니다.
    • 별도의 클래스 파일을 만들지 않고 코드 내에서 일회성으로 정의해 사용하기 때문에 이름이 없다고 부릅니다.
    • 인터페이스, 클래스(일반, 추상)의 구현과 상속을 활용해 익명 클래스를 구현할 수 있습니다.
      → 람다에서는 인터페이스를 사용한 익명 클래스가 활용됩니다.

    ✅ 인터페이스를 활용한 익명 클래스 예제

    • 익명 클래스를 코드내에서 직접 구현하기 때문에 클래스 파일을 만들 필요가 없습니다.
      → 하지만 코드가 길어집니다.
    public interface Calculator {
    
        int sum(int a, int b);
    }
    public class Main {
    
        public static void main(String[] args) {
    
                // ✅ 익명 클래스 활용
            Calculator calculator1 = new Calculator() {
                @Override
                public int sum(int a, int b) {
                    return a + b;
                }
            };
    
            int ret1 = calculator1.sum(1, 1);
            System.out.println("ret1 = " + ret1);
        }
    }

    2️⃣ 익명클래스의 특징

    - 그렇다면 드는 생각이 있습니다. 굳이 코드가 길어지는데 왜 쓰는 것이냐? 장, 단점에 대해서 알아보겠습니다.

    • 익명 클래스는 클래스 선언과 객체 생성이 한 번에 이루어지는 클래스의 형태로, 이름이 없는 클래스입니다.
    • 주로 간단한 구현이나 일회성으로 사용되는 객체에 유용하게 사용됩니다.

    ✅ 익명 클래스의 특징

    • 클래스를 별도로 정의하지 않고 한 번에 클래스를 구현할 수 있음
    • 코드가 길어지지만, 작은 인터페이스나 추상 클래스를 구현하는 경우 유용
    • 다형성을 활용할 때 코드가 깔끔해질 수 있음

    ✅ 익명 클래스의 장점

    1) 간결성

    • 클래스를 따로 만들지 않고, 즉석에서 객체를 생성하고 구현할 수 있기 때문에 코드가 간결해짐.

    2) 일회성 사용

    • 한번만 사용될 경우, 클래스를 따로 작성하지 않고 익명 클래스로 구현하는 것이 코드의 효율성을 높임.

    3) 다형성 활용

    • 상속이나 인터페이스 구현을 한 번에 할 수 있어 다형성의 장점을 바로 활용할 수 있음.

    ✅ 익명 클래스의 단점

    1) 코드가 길어짐

    • 너무 긴 구현을 할 경우 익명 클래스로 작성하면 코드가 매우 복잡해질 수 있음.

    2) 디버깅 어려움

    • 클래스 이름이 없으므로 디버깅이나 오류 추적이 어려울 수 있음.

    3) 재사용 불가

    • 한 번만 사용하는 코드에서 유용하지만, 여러 번 사용하려면 별도의 클래스로 분리하는 것이 더 좋음.

    3️⃣ 람다(Lambda)

    • 람다는 익명 클래스를 더 간결하게 표현하는 문법입니다.
    • 함수형 인터페이스 를 통해서 구현하는 것을 권장합니다.
      → 하나의 추상 메서드만 가져야하기 때문입니다.
      → 하지만, 하나의 추상 메서드를 가진 일반 인터페이스를 통해서도 사용 가능합니다.

    ✅ 람다식을 활용한 익명 클래스 변환 방법

    • 컴파일 시점에 컴파일러가 (a, b) -> a + b 람다 표현식을 보고 sum() 메서드를 가진 익명 클래스를 구현합니다.
    • Calculator 인터페이스에 추상 메서드가 하나뿐이기 때문에 컴파일러는 (a, b) -> a + b 람다 표현식이sum() 메서드라고 추론 가능하기 때문입니다.
    // 람다 표현식
    Calculator calculator1 = (a, b) -> a + b;
    
    // 익명클래스
    Calculator calculator1 = new Calculator() {
            @Override
            public int sum(int a, int b) {
                    return a + b;
            }
    };
    @FunctionalInterface // ✅ 함수형 인터페이스 선언
    public interface Calculator {
    
        int sum(int a, int b); // ✅ 오직 하나의 추상 메서드만 선언해야합니다.
    }
    public class Main {
    
        public static void main(String[] args) {
    
                ...
    
            // ✅ 람다식 활용
            Calculator calculator2 = (a, b) -> a + b;
            int ret2 = calculator2.sum(2, 2);
            System.out.println("ret2 = " + ret2);
        }
    }

    4️⃣ 람다 사용시 주의사항

    ✅ 람다식을 활용할때는 꼭 함수형 인터페이스를 활용할 것!

    • 함수형 인터페이스단 하나의 추상 메서드만 가지도록 강제하는 어노테이션입니다.
    • 람다식에서는 함수형 인터페이스가 활용됩니다.
    • 인터페이스에 두 개 이상의 추상 메서드가 존재하면 컴파일러가 어떤 메서드를 구현하는지 모호해지기 때문입니다.
    • 예를 들어 오버로딩(Overloading) 기능을 통해 같은 이름의 sum() 메서드를 여러 형태로 정의한다면 람다 표현식이 어떤 메서드를 구현하는 것인지 명확하지 않아 모호성이 발생할 수 있습니다.
    public interface Calculator {
        int sum(int a, int b);        // ✅ 선언 가능
        int sum(int a, int b, int c); // ⚠️ 오버로딩으로 선언 가능 모호성 발생!
    }
    @FunctionalInterface // ✅ 함수형 인터페이스 선언
    public interface Calculator {
        int sum(int a, int b); // ✅ 오직 하나의 추상 메서드만 선언해야합니다.
        int sum(int a, int b, int c); // ❌ 선언 불가 에러발생!
    }

    오버로딩(Overloading) vs 오버라이딩(Overriding)

    • 오버로딩은 같은 클래스나 인터페이스 내에서 동일한 메서드 이름을 사용해서 선언하는 기능입니다.
      → 매개변수의 개수나 타입, 순서는 다르게 선언해야 합니다.
    • 오버라이딩은 부모 클래스에 정의된 메서드를 자식 클래스에서 재정의하는 것을 의미합니다.

    5️⃣ 람다식을 매개변수로 전달하는 방법

    ✅ 익명 클래스를 변수에 담아 전달

    • 람다식 없이 직접 객체를 생성해서 전달하는 방식입니다.
    • 클래스의 익명 객체를 만든 다음에 매개변수로 전달합니다.
    public class Main {
        public static int calculate(int a, int b, Calculator calculator) {
            return calculator.sum(a, b);
        }
    
        public static void main(String[] args) {
    
            Calculator cal1 = new Calculator() {
                @Override
                public int sum(int a, int b) {
                    return a + b;
                }
            };
    
            // ✅ 익명 클래스를 변수에 담아 전달
            int ret3 = calculate(3, 3, cal1);
            System.out.println("ret3 = " + ret3); // 출력: ret3 = 6
        }
    }

    ✅ 람다식을 변수에 담아 전달

    • 람다식을 변수에 담아 매개변수로 전달하는 방식입니다.
    • 람다식을 전달하면 calculate() 메서드의 매개변수의 타입으로 Calculator 인터페이스를 구현했는지 추론되기 때문에 람다식을 전달 가능합니다.
    public class Main {
    
        public static int calculate(int a, int b, Calculator calculator) {
            return calculator.sum(a, b);
        }
    
        public static void main(String[] args) {
            Calculator cal2 = (a, b) -> a + b;
    
            // ✅ 람다식을 변수에 담아 전달
            int ret4 = calculate(4, 4, cal2);
            System.out.println("ret4 = " + ret4); // 출력: ret4 = 8
        }
    }

    ✅ 람다식을 직접 전달

    • 람다식을 직접 전달합니다.
    • 마찬가지로 calculate() 메서드의 매개변수의 타입으로 Calculator 인터페이스를 구현했는지 추론됩니다.
    public class Main {
    
        public static int calculate(int a, int b, Calculator calculator) {
            return calculator.sum(a, b);
        }
    
        public static void main(String[] args) {
            // ✅ 람다식을 직접 매개변수로 전달
            int ret5 = calculate(5, 5, (a, b) -> a + b);
            System.out.println("ret5 = " + ret5); // 출력: ret5 = 10
        }
    }