[자바 무료 강의] [심화] 익명 클래스와 람다 - 코드라떼
Lesson List button
코스자바로 배우는 프로그래밍
hamburger button
강의[심화] 익명 클래스와 람다최종수정일 2021-11-21
아이콘약 15분

익명 클래스와 람다에 대해서 알아보는 강의입니다. 내용 자체가 어려운 부분이 있으므로 차분히 이해하면서 넘어가야 합니다. 그럼에도 이해가 안 된다면 해당 강의는 넘어가셔도 됩니다.

노트 강의

목차


  1. 익명 클래스

  2. 인터페이스를 구현 하는 익명 클래스

  3. 인터페이스의 익명 클래스와 람다 표현식

  4. 람다 표현식의 제한

  5. 람다의 컨셉

  6. 마치며

1. 익명 클래스


Java에서는 익명 클래스라는 존재가 있습니다. 익명 클래스는 상속 받는 클래스를 명시적으로 별도의 Java 파일을 통해 클래스를 만드는 것이 아닌, 코드 내부에 이름이 존재하지 않는 클래스를 만드는 것입니다.

바로 예시로 들어가 보겠습니다.

public class Coffee {
    public void make() {
        System.out.println("Make!!");
    }
}
public class Main {

    public static void main(String[] args) {
 
        // TODO : Coffee 클래스를 상속 받는 익명 클래스
        Coffee coffee = new Coffee() {
            // make 메서드 오버라이드
            public void make() {
                System.out.println("Override Make!!");
            }
            // 새로운 메서드
            public void serve() {
                System.out.println("Serve");
            }
        }
        coffee.make();
        // Override Make!! 

        // serve() 메서드는 호출할 수 없다
        coffee.serve();
        // compile error
    }
}

Coffee 클래스를 상속받는 익명 클래스를 정의했습니다. 별도의 파일을 이용하는 것이 아니라 코드 내부에서 상속받는 클래스를 재정의 하는 방법입니다. 사실 일반적인 클래스를 익명 클래스로 코드를 작성하는 경우는 거의 없다고 보시면 됩니다. 현업에서는 별도의 파일을 통해 작성합니다.


coffee.make() 메서드와 다르게 coffee.serve() 메서드는 외부에서 호출할 수 없습니다. 그 이유는 new Coffee()를 통해서 생성하는 인스턴스는 Coffee 클래스가 아닌 Coffee 클래스를 상속받는 익명 클래스이기 때문에 Coffee 클래스 자체에는 serve() 메서드가 선언되어 있지 않기 때문입니다.


다형성의 법칙에 의해 Coffee 클래스에는 serve() 메서드가 존재하지 않습니다. 그러므로 익명 클래스 내에서 호출하는 것이 아닌 외부 스코프에서 호출할 수 없습니다.


익명 클래스는 왜 존재하는가?

이유는 추상 클래스나, 인터페이스 때문입니다. 추상 클래스나 인터페이스를 Java 파일을 별도로 만들어서 구현할 수도 있지만 코드 흐름이나 가독성을 위해 가벼운 내용의 경우는 익명 클래스로 작성하는 것이 더 나을 때도 있기 때문입니다.


추상 클래스를 상속 받는 익명 클래스

public abstract class Person {
    abstract public void eat();
    abstract public void sleep();

    public void walk() {
        System.out.println("walk!");
    }
}
public class Main {

    public static void main(String[] args) {
 
        // TODO : Person 클래스를 상속 받는 익명 클래스
        Person person = new Person() {

            public void eat() {
                System.out.println("eat!");
                // walk() 메서드는 Person 클래스의 메서드를 의미한다
                walk();
            }

            public void sleep() {
                System.out.println("sleep!");
            }
        }

        person.eat();
        // eat!
        // walk!

    }
}

Person 클래스를 상속받는 어떤 클래스든, abstract 메서드는 재정의 해야 합니다. 그러므로 익명 클래스를 정의할 때도 abstract 메서드는 반드시 재정의해야 합니다.


현재로서는 익명 클래스가 눈에 잘 안 익고 익숙하지 않을 것입니다. 당연합니다. 많은 코드를 보고 작성하다 보면 눈에 익으므로 걱정하지 않으셔도 됩니다.

코드 상으로는 new Person() 으로 Person 인스턴스를 생성하는 것처럼 보이지만 정확히는 Person 클래스를 상속받는 익명 클래스의 인스턴스를 생성하는 것과 같습니다.


2. 인터페이스를 구현 하는 익명 클래스


익명 클래스의 진정한 진가는 인터페이스부터입니다.

예시 코드를 천천히 살펴봅시다.

public interface Operate {
    int operate(int a, int b);
}
public class Plus implements Operate {
    public int operate(int a, int b) {
        return a + b;
    }
}
public class Calculator {
    private int a;
    private int b;

    public Calculator(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public int result(Operate op) {
        return op.operate(this.a, this.b);
    }
}
public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);
        Operate operate = new Plus();
 
        int result = calculator.result(operate);
        // 30

    }
}

Calculator 클래스는 생성자를 통해 두 수를 전달받습니다. 그리고 result(operate) 메서드를 통해 Operate 인터페이스를 구현하는 인스턴스를 전달합니다. result(operate) 메서드가 할 일은 별것 없습니다.


전달받은 Operate 인터페이스의 operate(int a, int b) 메서드를 호출하기만 합니다.

Operate 인터페이스를 구현하는 Plus 클래스는 두 매개 변수를 전달받아 더한 후 결괏값을 반환합니다.


Calculate 클래스의 result(operate) 메서드를 호출하면 Plus 클래스의 operate(int a, int b) 메서드를 호출하고 결괏값을 반환합니다.

public class Minus implements Operate {
    public int operate(int a, int b) {
        return a - b;
    }
}

이번엔 Operate 인터페이스를 구현하고 두 값을 빼는 Minus라는 클래스를 만들어 봅니다. 그리고 Minus 인스턴스를 생성하여 Caculate 클래스의 result(operate) 메서드로 전달해봅시다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);
        Operate operate = new Minus();
 
        int result = calculator.result(operate);
        // 10

    }
}

이렇게 result(operate) 메서드에 전달받는 인스턴스에 따라 다른 결괏값을 얻을 수 있습니다.

다만 간단한 내용을 매번 다른 클래스로 정의하여 Java 파일을 만들어서 해야 한다면 귀찮을 수도 있고 불편할 수도 있습니다.


그러나 익명 클래스 방식으로 한다면 Java 파일을 통한 클래스에 대한 정의 필요없이 간단하게 사용할 수 있습니다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a + b;
            }
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a - b;
            }
        });

        System.out.println(result2);
        // 10

    }
}

new 키워드를 통해 반환되는 값은 인스턴스의 참조 값이므로 변수를 선언하여 인스턴스의 참조 값을 저장하지 않고 곧바로 result(operate) 메서드에 전달합니다.


결론적으로 Operation 인터페이스를 상속받는 익명 클래스의 operate(int a, int b) 메서드에 따라 결과 값이 달라집니다.


익숙하지 않은 코드라서 헷갈리실 겁니다. 그러므로 그림으로 천천히 또다시 한번 살펴보겠습니다.

익명 클래스

  1. result(operate) 메서드가 호출될 때, new 키워드를 통해 Operate 인터페이스를 상속 받는 익명 클래스의 인스턴스를 생성 후 참조값을 result(operate) 메서드로 전달합니다.

  2. Operate 익명 클래스 인스턴스를 전달 받은 Calculator 인스턴스는 Operate 인스턴스의 operate(int a, int b) 메서드를 호출합니다.

  3. 익명 클래스에 정의된 operate(int a, int b) 메서드를 실행 후 결과값을 반환합니다.

  4. 실행흐름은 다시 result(operate) 메서드로 돌아오고 그대로 결과값을 반환합니다.

  5. 결론적으로 int result 변수에는 30 값이 저장되어있습니다.

실행흐름이 눈에 익숙하지 않는 것은 자명합니다. 익숙하다면 대단한겁니다.


3. 인터페이스의 익명 클래스와 람다 표현식


Java8 이후부터는 람다 표현식이 가능하게 되었습니다. 언어적인 측면에서도 상당한 발전이 이루어졌는데요. 일단 우리는 람다 표현식에 대해서 확인해봅시다.

Operate operate = new Operate() {
    public int operate(int a, int b) {
        return a + b;
    }
};

이 코드를 람다 표현식으로 하면 코드 작성수를 훨씬 줄일 수 있습니다.

Operate operate = (a, b) -> {
    return a + b;
};

코드를 작성해야 하는 글자 수가 줄어들었습니다. 해당 코드를 함수형 프로그래밍에서는 람다 또는 익명 함수라고도 부릅니다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result((a, b) -> {
            return a + b;
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result((a, b) -> {
            return a - b;
        });

        System.out.println(result2);
        // 10

    }
}

이전의 코드보다 람다를 이용한 코드작성이 코드 작성 수도 적어지고 보기도 편해졌습니다.

Operate operate = (a, b) -> {
    return a + b;
};

또한 람다는 더 간단히 작성될 수 있습니다.

Operate operate = (a, b) -> a + b;

이렇게 줄일 수 있는 경우는 반환값이 있는 람다이어야 하고, 람다 내부 구문이 코드로 한 줄로 작성이 가능해야 하며 return을 명시하지 않아도 인지할 수 있도록 연산 과정이 한 줄로 작성되어야 합니다.

// 이런 구문은 더 간단히 작성될 수 없다.
Operate operate = (a, b) -> {
    System.out.println("Operate");
    return a + b;
};

이런 람다 구문은 더 간단히 작성될 수 없습니다. 쉽게 얘기하면 세미콜론(;)이 두 번 찍히는 내부 구문은 더 줄일 수 없다고 보시면 됩니다.

람다 Lambda

해당 그림은 익명 클래스 인스턴스와 람다와의 연관 관계를 작성한 그림입니다. 변환 과정을 좀 더 한눈에 볼 수 있습니다.

앞으로 Java로 프로그래밍을 하시던가 또는 남이 만든 코드를 확인할 때 람다 표현식을 많이 볼 수 있을 겁니다. 그러므로 보시더라도 당황하지 마시고 천천히 파악하시면 큰 어려움이 없을 것이라고 생각됩니다.


4. 람다 표현식의 제한


람다 표현식을 사용하기 위해서는 제약 조건이 있는데요. 모든 조건을 만족해야지 람다 표현식을 사용할 수 있습니다.

  1. 인터페이스이어야 한다.

  2. 인터페이스에는 하나의 추상 메서드만 선언되어야 한다.

public interface Operate {

    // 오버라이드 해야 할 메서드가 두 개인 경우
    // 람다 표현식이 불가능하다.
    int operate(int a, int b);
    void print();
}
Operate operate = new Operate() {
    public int operate(int a, int b) {
        return a + b;
    }
    public void print() {
        System.out.println("출력");
    }
};

오버라이드 해야 할 추상 메서드가 두 개 이상인 경우는 람다 표현식을 사용할 수 없습니다. 람다는 하나의 행위만을 사용한다고 가정하기 때문입니다.

public interface Operate {
    // 추상 메서드가 하나이다
    int operate(int a, int b);

    // default 메서드는 추상 메서드에 포함되지 않는다
    default void print() {
        System.out.println("출력");
    }
}
Operate operate = (a, b) -> {
    print();
    return a + b;
};

그러나 추상 메서드가 아닌 default 메서드가 포함된 경우는 람다 표현식이 가능합니다. 결론적으로 오버라이드 해야 하는 추상 메서드는 하나이기 때문입니다.


5. 람다의 컨셉


람다 Lambda

우리는 항상 매개 변수로 값을 전달한다는 개념으로 배웠었습니다. 물론 상수 값이나 인스턴스의 참조 값을 전달하는 것은 맞습니다. 그러나 생각을 확장할 필요가 있습니다.

람다를 무엇을 해야 한다는 행위로 본다면, 값이 아니라 행위를 전달한다고 볼 수 있습니다.

익숙하지 않은 경우 개념 자체가 어려울 수 있습니다. 그래도 괜찮습니다. 최대한 익숙하게 사용하려고 하면 됩니다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        // a + b를 하라는 행위 전달
        int result1 = calculator.result((a, b) -> {
            return a + b;
        });

        // a - b를 하라는 행위 전달
        int result2 = calculator.result((a, b) -> {
            return a - b;
        });

        // a / b를 하라는 행위 전달
        int result3 = calculator.result((a, b) -> {
            return a / b;
        });

        // a * b를 하라는 행위 전달
        int result4 = calculator.result((a, b) -> {
            return a * b;
        });
    }
}

값을 전달한다는 개념보다는 ‘이렇게 해라’ 라는 행위를 전달하는 개념은 함수형 프로그래밍으로 가기 위한 기초입니다. 그러나 우리는 아직 함수형 프로그래밍이라는 것에 대해 뭔지도 잘 알지 못할뿐더러 익숙하지 않고 너무 많은 내용을 배우고 가기 때문에 머리속에 정리하기 힘듭니다.


6. 마치며


우리는 익명 클래스에 대해서 간략히 살펴보았습니다.

사실 익명 클래스에서 가장 중점적으로 봐야 할 부분은 인터페이스 부분과 람다입니다. 일반 클래스나, 추상 클래스를 익명 클래스로 사용하는 경우는 많지 않습니다. 그러나 인터페이스를 이용한 람다 표현식은 상당히 많이 사용합니다. 그러나 앞으로 강의에서 조금씩 람다 표현식을 보여드릴 것이기 때문에 천천히 익숙해져 봅시다.

마음 조급하게 먹지 마시고 실습 코드를 통해 천천히 정리하시길 빌겠습니다.

도전자 질문
아이콘Jake(2023-10-09 23:58 작성됨)
질문은 아니지만, 와... 진짜 댓글 안 다는데, 무료 자료 퀄리티가 너무 좋습니다. 파이썬만 하다가 자바는 그냥 어렵다고만 생각하고 계속 포기했었는데, 덕분에 자바에 재미를 느꼈습니다. 정말 감사합니다!
아이콘박준혁(2021-11-06 21:26 작성됨)
public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result((a, b) -> {
            return a + b;
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result((a, b) -> {
            return a - b;
        });

        System.out.println(result); 이 부분 result2로 수정해야 맞는거 같습니다.
        // 10

    }
}
아이콘코드라떼(2021-11-06 21:35 작성됨)
안녕하세요. 코드라떼입니다 :)

확인 후 수정하였습니다.

감사합니다 :)
아이콘resCogitans(2021-06-30 00:59 작성됨)
아, 하루 지나서 다시 읽어보니 이해가 되었습니다! 익명클래스를 매개변수 부분에서 인스턴스 생성하면서 오버라이딩 하는게 가능하군요. 덕분에 뭘 모르고 있었는지 알게 되었습니다. 다음 강의도 기대되네요!
아이콘코드라떼(2021-07-01 05:41 작성됨)
다음 강의는 '자료구조' 한 방 정리 강의로 최상위 퀄리티의 강의를 준비하고 있습니다. 기존의 여타 강의와 다르게 넓은 범위의 자료구조(Red-Black, B-트리 등)가 포함되어 있으며, 예제 코드도 굉장히 심혈을 기울여 준비하고 있습니다. 또한 어떻게 하면 도전자분들의 '진짜 실력'을 향상시킬 수 있을지 수많은 고민을 하며 준비하고 있습니다. 해당 강의가 완성되면 메일을 통해 안내해드리겠습니다. 감사합니다^^
아이콘resCogitans(2021-06-29 00:12 작성됨)
public class Plus implements Operate {
    public int operate(int a, int b) {
        return a - b;
    }
}
이번엔 Operate 인터페이스를 구현하고 두 값을 빼는 Minus라는 클래스를 만들어 봅니다. 그리고 Minus 인스턴스를 생성하여 Caculate 클래스의 result(operate) 메서드로 전달해봅시다.

----------

-> Operate 구현하는 클래스명이 Minus가 되어야함



----------

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a + b;
            }
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a - b;
            }
        });

        System.out.println(result);
        // 10

    }
}

----------

-> 익명클래스로 인터페이스 구현하는 부분에서 

        int result = calculator.result(new Operate() {
            public int operate(int a, int b) {

        int result2 = calculator.result(new Operate() {
            public int operate(int a, int b) {

calculator.result의 매개변수 부분의, Operate 인터페이스를 익명메소드로 구현하는 코드의 괄호가 잘못됨.

result(new Operate()
    ->
result(new Operate())
아이콘코드라떼(2021-06-29 02:00 작성됨)
안녕하세요. 코드라떼입니다^^

첫 번째 말씀하신 부분의 오타는 수정되었으나 두 번째 부분은 아무리 봐도 오타를 낸 부분이 없는 것 같은데 result(new Operate()) 이 부분을 말씀하신 것을 보면 저런 방식은 원래 가능한 방식입니다. 

다시 확인 부탁드립니다 감사합니다^^

ps. 이렇게 꼼꼼히 공부하시는 것에 감명이 깊어 이후의 출시되는 강의를 무료로 들으실 수 있도록 출시 후에 안내드리겠습니다. 감사합니다^^
이용약관|개인정보취급방침
알유티씨클래스|대표, 개인정보보호책임자 : 이병록
이메일 : cs@codelatte.io
사업자등록번호 : 824-06-01921
통신판매업신고 : 2021-성남분당C-0740
주소 : 경기도 성남시 분당구 대왕판교로645번길 12, 9층 24호
파일
파일파일
Root
파일

Output
root$
Lesson List button
코스자바로 배우는 프로그래밍
hamburger button
강의[심화] 익명 클래스와 람다최종수정일 2021-11-21
아이콘약 15분

익명 클래스와 람다에 대해서 알아보는 강의입니다. 내용 자체가 어려운 부분이 있으므로 차분히 이해하면서 넘어가야 합니다. 그럼에도 이해가 안 된다면 해당 강의는 넘어가셔도 됩니다.

노트 강의

목차


  1. 익명 클래스

  2. 인터페이스를 구현 하는 익명 클래스

  3. 인터페이스의 익명 클래스와 람다 표현식

  4. 람다 표현식의 제한

  5. 람다의 컨셉

  6. 마치며

1. 익명 클래스


Java에서는 익명 클래스라는 존재가 있습니다. 익명 클래스는 상속 받는 클래스를 명시적으로 별도의 Java 파일을 통해 클래스를 만드는 것이 아닌, 코드 내부에 이름이 존재하지 않는 클래스를 만드는 것입니다.

바로 예시로 들어가 보겠습니다.

public class Coffee {
    public void make() {
        System.out.println("Make!!");
    }
}
public class Main {

    public static void main(String[] args) {
 
        // TODO : Coffee 클래스를 상속 받는 익명 클래스
        Coffee coffee = new Coffee() {
            // make 메서드 오버라이드
            public void make() {
                System.out.println("Override Make!!");
            }
            // 새로운 메서드
            public void serve() {
                System.out.println("Serve");
            }
        }
        coffee.make();
        // Override Make!! 

        // serve() 메서드는 호출할 수 없다
        coffee.serve();
        // compile error
    }
}

Coffee 클래스를 상속받는 익명 클래스를 정의했습니다. 별도의 파일을 이용하는 것이 아니라 코드 내부에서 상속받는 클래스를 재정의 하는 방법입니다. 사실 일반적인 클래스를 익명 클래스로 코드를 작성하는 경우는 거의 없다고 보시면 됩니다. 현업에서는 별도의 파일을 통해 작성합니다.


coffee.make() 메서드와 다르게 coffee.serve() 메서드는 외부에서 호출할 수 없습니다. 그 이유는 new Coffee()를 통해서 생성하는 인스턴스는 Coffee 클래스가 아닌 Coffee 클래스를 상속받는 익명 클래스이기 때문에 Coffee 클래스 자체에는 serve() 메서드가 선언되어 있지 않기 때문입니다.


다형성의 법칙에 의해 Coffee 클래스에는 serve() 메서드가 존재하지 않습니다. 그러므로 익명 클래스 내에서 호출하는 것이 아닌 외부 스코프에서 호출할 수 없습니다.


익명 클래스는 왜 존재하는가?

이유는 추상 클래스나, 인터페이스 때문입니다. 추상 클래스나 인터페이스를 Java 파일을 별도로 만들어서 구현할 수도 있지만 코드 흐름이나 가독성을 위해 가벼운 내용의 경우는 익명 클래스로 작성하는 것이 더 나을 때도 있기 때문입니다.


추상 클래스를 상속 받는 익명 클래스

public abstract class Person {
    abstract public void eat();
    abstract public void sleep();

    public void walk() {
        System.out.println("walk!");
    }
}
public class Main {

    public static void main(String[] args) {
 
        // TODO : Person 클래스를 상속 받는 익명 클래스
        Person person = new Person() {

            public void eat() {
                System.out.println("eat!");
                // walk() 메서드는 Person 클래스의 메서드를 의미한다
                walk();
            }

            public void sleep() {
                System.out.println("sleep!");
            }
        }

        person.eat();
        // eat!
        // walk!

    }
}

Person 클래스를 상속받는 어떤 클래스든, abstract 메서드는 재정의 해야 합니다. 그러므로 익명 클래스를 정의할 때도 abstract 메서드는 반드시 재정의해야 합니다.


현재로서는 익명 클래스가 눈에 잘 안 익고 익숙하지 않을 것입니다. 당연합니다. 많은 코드를 보고 작성하다 보면 눈에 익으므로 걱정하지 않으셔도 됩니다.

코드 상으로는 new Person() 으로 Person 인스턴스를 생성하는 것처럼 보이지만 정확히는 Person 클래스를 상속받는 익명 클래스의 인스턴스를 생성하는 것과 같습니다.


2. 인터페이스를 구현 하는 익명 클래스


익명 클래스의 진정한 진가는 인터페이스부터입니다.

예시 코드를 천천히 살펴봅시다.

public interface Operate {
    int operate(int a, int b);
}
public class Plus implements Operate {
    public int operate(int a, int b) {
        return a + b;
    }
}
public class Calculator {
    private int a;
    private int b;

    public Calculator(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public int result(Operate op) {
        return op.operate(this.a, this.b);
    }
}
public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);
        Operate operate = new Plus();
 
        int result = calculator.result(operate);
        // 30

    }
}

Calculator 클래스는 생성자를 통해 두 수를 전달받습니다. 그리고 result(operate) 메서드를 통해 Operate 인터페이스를 구현하는 인스턴스를 전달합니다. result(operate) 메서드가 할 일은 별것 없습니다.


전달받은 Operate 인터페이스의 operate(int a, int b) 메서드를 호출하기만 합니다.

Operate 인터페이스를 구현하는 Plus 클래스는 두 매개 변수를 전달받아 더한 후 결괏값을 반환합니다.


Calculate 클래스의 result(operate) 메서드를 호출하면 Plus 클래스의 operate(int a, int b) 메서드를 호출하고 결괏값을 반환합니다.

public class Minus implements Operate {
    public int operate(int a, int b) {
        return a - b;
    }
}

이번엔 Operate 인터페이스를 구현하고 두 값을 빼는 Minus라는 클래스를 만들어 봅니다. 그리고 Minus 인스턴스를 생성하여 Caculate 클래스의 result(operate) 메서드로 전달해봅시다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);
        Operate operate = new Minus();
 
        int result = calculator.result(operate);
        // 10

    }
}

이렇게 result(operate) 메서드에 전달받는 인스턴스에 따라 다른 결괏값을 얻을 수 있습니다.

다만 간단한 내용을 매번 다른 클래스로 정의하여 Java 파일을 만들어서 해야 한다면 귀찮을 수도 있고 불편할 수도 있습니다.


그러나 익명 클래스 방식으로 한다면 Java 파일을 통한 클래스에 대한 정의 필요없이 간단하게 사용할 수 있습니다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a + b;
            }
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a - b;
            }
        });

        System.out.println(result2);
        // 10

    }
}

new 키워드를 통해 반환되는 값은 인스턴스의 참조 값이므로 변수를 선언하여 인스턴스의 참조 값을 저장하지 않고 곧바로 result(operate) 메서드에 전달합니다.


결론적으로 Operation 인터페이스를 상속받는 익명 클래스의 operate(int a, int b) 메서드에 따라 결과 값이 달라집니다.


익숙하지 않은 코드라서 헷갈리실 겁니다. 그러므로 그림으로 천천히 또다시 한번 살펴보겠습니다.

익명 클래스

  1. result(operate) 메서드가 호출될 때, new 키워드를 통해 Operate 인터페이스를 상속 받는 익명 클래스의 인스턴스를 생성 후 참조값을 result(operate) 메서드로 전달합니다.

  2. Operate 익명 클래스 인스턴스를 전달 받은 Calculator 인스턴스는 Operate 인스턴스의 operate(int a, int b) 메서드를 호출합니다.

  3. 익명 클래스에 정의된 operate(int a, int b) 메서드를 실행 후 결과값을 반환합니다.

  4. 실행흐름은 다시 result(operate) 메서드로 돌아오고 그대로 결과값을 반환합니다.

  5. 결론적으로 int result 변수에는 30 값이 저장되어있습니다.

실행흐름이 눈에 익숙하지 않는 것은 자명합니다. 익숙하다면 대단한겁니다.


3. 인터페이스의 익명 클래스와 람다 표현식


Java8 이후부터는 람다 표현식이 가능하게 되었습니다. 언어적인 측면에서도 상당한 발전이 이루어졌는데요. 일단 우리는 람다 표현식에 대해서 확인해봅시다.

Operate operate = new Operate() {
    public int operate(int a, int b) {
        return a + b;
    }
};

이 코드를 람다 표현식으로 하면 코드 작성수를 훨씬 줄일 수 있습니다.

Operate operate = (a, b) -> {
    return a + b;
};

코드를 작성해야 하는 글자 수가 줄어들었습니다. 해당 코드를 함수형 프로그래밍에서는 람다 또는 익명 함수라고도 부릅니다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result((a, b) -> {
            return a + b;
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result((a, b) -> {
            return a - b;
        });

        System.out.println(result2);
        // 10

    }
}

이전의 코드보다 람다를 이용한 코드작성이 코드 작성 수도 적어지고 보기도 편해졌습니다.

Operate operate = (a, b) -> {
    return a + b;
};

또한 람다는 더 간단히 작성될 수 있습니다.

Operate operate = (a, b) -> a + b;

이렇게 줄일 수 있는 경우는 반환값이 있는 람다이어야 하고, 람다 내부 구문이 코드로 한 줄로 작성이 가능해야 하며 return을 명시하지 않아도 인지할 수 있도록 연산 과정이 한 줄로 작성되어야 합니다.

// 이런 구문은 더 간단히 작성될 수 없다.
Operate operate = (a, b) -> {
    System.out.println("Operate");
    return a + b;
};

이런 람다 구문은 더 간단히 작성될 수 없습니다. 쉽게 얘기하면 세미콜론(;)이 두 번 찍히는 내부 구문은 더 줄일 수 없다고 보시면 됩니다.

람다 Lambda

해당 그림은 익명 클래스 인스턴스와 람다와의 연관 관계를 작성한 그림입니다. 변환 과정을 좀 더 한눈에 볼 수 있습니다.

앞으로 Java로 프로그래밍을 하시던가 또는 남이 만든 코드를 확인할 때 람다 표현식을 많이 볼 수 있을 겁니다. 그러므로 보시더라도 당황하지 마시고 천천히 파악하시면 큰 어려움이 없을 것이라고 생각됩니다.


4. 람다 표현식의 제한


람다 표현식을 사용하기 위해서는 제약 조건이 있는데요. 모든 조건을 만족해야지 람다 표현식을 사용할 수 있습니다.

  1. 인터페이스이어야 한다.

  2. 인터페이스에는 하나의 추상 메서드만 선언되어야 한다.

public interface Operate {

    // 오버라이드 해야 할 메서드가 두 개인 경우
    // 람다 표현식이 불가능하다.
    int operate(int a, int b);
    void print();
}
Operate operate = new Operate() {
    public int operate(int a, int b) {
        return a + b;
    }
    public void print() {
        System.out.println("출력");
    }
};

오버라이드 해야 할 추상 메서드가 두 개 이상인 경우는 람다 표현식을 사용할 수 없습니다. 람다는 하나의 행위만을 사용한다고 가정하기 때문입니다.

public interface Operate {
    // 추상 메서드가 하나이다
    int operate(int a, int b);

    // default 메서드는 추상 메서드에 포함되지 않는다
    default void print() {
        System.out.println("출력");
    }
}
Operate operate = (a, b) -> {
    print();
    return a + b;
};

그러나 추상 메서드가 아닌 default 메서드가 포함된 경우는 람다 표현식이 가능합니다. 결론적으로 오버라이드 해야 하는 추상 메서드는 하나이기 때문입니다.


5. 람다의 컨셉


람다 Lambda

우리는 항상 매개 변수로 값을 전달한다는 개념으로 배웠었습니다. 물론 상수 값이나 인스턴스의 참조 값을 전달하는 것은 맞습니다. 그러나 생각을 확장할 필요가 있습니다.

람다를 무엇을 해야 한다는 행위로 본다면, 값이 아니라 행위를 전달한다고 볼 수 있습니다.

익숙하지 않은 경우 개념 자체가 어려울 수 있습니다. 그래도 괜찮습니다. 최대한 익숙하게 사용하려고 하면 됩니다.

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        // a + b를 하라는 행위 전달
        int result1 = calculator.result((a, b) -> {
            return a + b;
        });

        // a - b를 하라는 행위 전달
        int result2 = calculator.result((a, b) -> {
            return a - b;
        });

        // a / b를 하라는 행위 전달
        int result3 = calculator.result((a, b) -> {
            return a / b;
        });

        // a * b를 하라는 행위 전달
        int result4 = calculator.result((a, b) -> {
            return a * b;
        });
    }
}

값을 전달한다는 개념보다는 ‘이렇게 해라’ 라는 행위를 전달하는 개념은 함수형 프로그래밍으로 가기 위한 기초입니다. 그러나 우리는 아직 함수형 프로그래밍이라는 것에 대해 뭔지도 잘 알지 못할뿐더러 익숙하지 않고 너무 많은 내용을 배우고 가기 때문에 머리속에 정리하기 힘듭니다.


6. 마치며


우리는 익명 클래스에 대해서 간략히 살펴보았습니다.

사실 익명 클래스에서 가장 중점적으로 봐야 할 부분은 인터페이스 부분과 람다입니다. 일반 클래스나, 추상 클래스를 익명 클래스로 사용하는 경우는 많지 않습니다. 그러나 인터페이스를 이용한 람다 표현식은 상당히 많이 사용합니다. 그러나 앞으로 강의에서 조금씩 람다 표현식을 보여드릴 것이기 때문에 천천히 익숙해져 봅시다.

마음 조급하게 먹지 마시고 실습 코드를 통해 천천히 정리하시길 빌겠습니다.

도전자 질문
아이콘Jake(2023-10-09 23:58 작성됨)
질문은 아니지만, 와... 진짜 댓글 안 다는데, 무료 자료 퀄리티가 너무 좋습니다. 파이썬만 하다가 자바는 그냥 어렵다고만 생각하고 계속 포기했었는데, 덕분에 자바에 재미를 느꼈습니다. 정말 감사합니다!
아이콘박준혁(2021-11-06 21:26 작성됨)
public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result((a, b) -> {
            return a + b;
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result((a, b) -> {
            return a - b;
        });

        System.out.println(result); 이 부분 result2로 수정해야 맞는거 같습니다.
        // 10

    }
}
아이콘코드라떼(2021-11-06 21:35 작성됨)
안녕하세요. 코드라떼입니다 :)

확인 후 수정하였습니다.

감사합니다 :)
아이콘resCogitans(2021-06-30 00:59 작성됨)
아, 하루 지나서 다시 읽어보니 이해가 되었습니다! 익명클래스를 매개변수 부분에서 인스턴스 생성하면서 오버라이딩 하는게 가능하군요. 덕분에 뭘 모르고 있었는지 알게 되었습니다. 다음 강의도 기대되네요!
아이콘코드라떼(2021-07-01 05:41 작성됨)
다음 강의는 '자료구조' 한 방 정리 강의로 최상위 퀄리티의 강의를 준비하고 있습니다. 기존의 여타 강의와 다르게 넓은 범위의 자료구조(Red-Black, B-트리 등)가 포함되어 있으며, 예제 코드도 굉장히 심혈을 기울여 준비하고 있습니다. 또한 어떻게 하면 도전자분들의 '진짜 실력'을 향상시킬 수 있을지 수많은 고민을 하며 준비하고 있습니다. 해당 강의가 완성되면 메일을 통해 안내해드리겠습니다. 감사합니다^^
아이콘resCogitans(2021-06-29 00:12 작성됨)
public class Plus implements Operate {
    public int operate(int a, int b) {
        return a - b;
    }
}
이번엔 Operate 인터페이스를 구현하고 두 값을 빼는 Minus라는 클래스를 만들어 봅니다. 그리고 Minus 인스턴스를 생성하여 Caculate 클래스의 result(operate) 메서드로 전달해봅시다.

----------

-> Operate 구현하는 클래스명이 Minus가 되어야함



----------

public class Main {

    public static void main(String[] args) {
 
        Calculator calculator = new Calculator(20, 10);

        int result = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a + b;
            }
        });

        System.out.println(result);
        // 30

        int result2 = calculator.result(new Operate() {
            public int operate(int a, int b) {
                return a - b;
            }
        });

        System.out.println(result);
        // 10

    }
}

----------

-> 익명클래스로 인터페이스 구현하는 부분에서 

        int result = calculator.result(new Operate() {
            public int operate(int a, int b) {

        int result2 = calculator.result(new Operate() {
            public int operate(int a, int b) {

calculator.result의 매개변수 부분의, Operate 인터페이스를 익명메소드로 구현하는 코드의 괄호가 잘못됨.

result(new Operate()
    ->
result(new Operate())
아이콘코드라떼(2021-06-29 02:00 작성됨)
안녕하세요. 코드라떼입니다^^

첫 번째 말씀하신 부분의 오타는 수정되었으나 두 번째 부분은 아무리 봐도 오타를 낸 부분이 없는 것 같은데 result(new Operate()) 이 부분을 말씀하신 것을 보면 저런 방식은 원래 가능한 방식입니다. 

다시 확인 부탁드립니다 감사합니다^^

ps. 이렇게 꼼꼼히 공부하시는 것에 감명이 깊어 이후의 출시되는 강의를 무료로 들으실 수 있도록 출시 후에 안내드리겠습니다. 감사합니다^^
이용약관|개인정보취급방침
알유티씨클래스|대표, 개인정보보호책임자 : 이병록
이메일 : cs@codelatte.io|운영시간 09:00 - 18:00(평일)
사업자등록번호 : 824-06-01921|통신판매업신고 : 2021-성남분당C-0740
주소 : 경기도 성남시 분당구 대왕판교로645번길 12, 9층 24호(경기창조혁신센터)
파일
파일파일
Root
파일

Output
root$