인프런 커뮤니티 질문&답변

흰머리오목눈이님의 프로필 이미지
흰머리오목눈이

작성한 질문수

나도코딩의 자바 기본편 - 풀코스 (20시간)

익명 클래스 (후반전)

추상 클래스 / 익명 클래스

작성

·

594

·

수정됨

1

강사님, 안녕하세요. 우선 많은 강의 내용 중 생기는 질문에 대해서 항상 친절하게 답변해주시는 점 감사드립니다. 😊

다름이 아니고 익명 클래스 (후반전) 에서 추상 클래스를 직접 상속받아서 객체 생성과 동시에 메서드를 오버라이드 하는 것과 객체 메서드로 반환받아 하는 것이 어떠한 기능적인 차이가 있는 것인지 궁금하여 여쭤봅니다!

개인적으로는 별도의 메서드로 객체를 반환 받는 형태가 직관적으로 와닿지 않아서 어렵게 느껴지는 듯 합니다.

단순히 코드의 유지보수나 가독성을 높이는 목적 때문에 추상 클래스의 메서드를 직접 상속받는 형태보다 익명클래스를 활용해 메서드로 객체를 반환받는 형식으로 하는걸까요?

답변 1

4

나도코딩님의 프로필 이미지
나도코딩
지식공유자

안녕하세요?
답변이 늦어 죄송합니다.
아래와 같이 코드를 간단히 줄여봤는데요. Burger 라는 추상클래스를 상속하여 cook() 메소드를 구현하는데 burger1 는 구현 코드가 작성된 getBurger() 메소드를 이용하고, burger2 는 main() 메소드 내에서 직접 내용을 작성합니다.

public class SampleClass {
    public static void main(String[] args) {
        Burger burger1 = getBurger();
        burger1.cook(); // 출력
        
        Burger burger2 = new Burger() {
            @Override
            public void cook() {
                System.out.println("Burger 2");
            }
        };
        burger2.cook(); // 출력
    }

    public static Burger getBurger() {
        return new Burger() {
            @Override
            public void cook() {
                System.out.println("Burger 1");
            }
        };
    }
}
abstract class Burger {
    public abstract void cook();
}

이 둘은 사실 동일한 동작을 하기 때문에 어떤 식으로 하건 상관이 없습니다. 다만 상속을 통해 구현해야 하는 내용이 많고 복잡하거나 생성 후에 추가적으로 작업해야 하는 부분이 있다면 main() 메소드 내에서 직접 모든 코드를 작성하는 방법 대신 getBurger() 와 같은 별도 메소드에서 처리함으로써 main() 메소드 본연의 작업에 보다 집중할 수 있게 됩니다.

가령 Burger 추상클래스가 다음과 같이 이름, 가격, 유통기한 정보가 생겨서 객체 생성 후에 각 정보를 정의해야 한다고 해볼까요?

abstract class Burger {
    public String name;
    public int price;
    public String bestBefore;

    public abstract void cook();
}

그러면 main() 메소드에서 2개의 버거를 만들기 위해서는 이렇게 할 수 있을 겁니다.

public class SampleClass {
    public static void main(String[] args) {
        Burger burger1 = new Burger() {
            @Override
            public void cook() {
                System.out.println(name + "를 만듭니다.");
            }
        };
        burger1.name = "햄버거";
        burger1.price = 5000;
        burger1.bestBefore = "2023년 2월 28일";
        burger1.cook(); // 출력

        Burger burger2 = new Burger() {
            @Override
            public void cook() {
                System.out.println(name + "를 만듭니다.");
            }
        };
        burger2.name = "치즈버거";
        burger2.price = 6000;
        burger2.bestBefore = "2023년 3월 1일";
        burger2.cook(); // 출력
    }
}

그런데 main() 메소드의 주 목적이 햄버거를 만드는 것보다는 어떤 주소로 배달하는 것이라면 어떻게 될까요? 아마 deliver() 와 같은 형태의 배달 메소드를 만들고 햄버거와 주소 정보를 전달받도록 한 뒤 main() 메소드에서는 이를 호출하면 되겠죠.

public class SampleClass {
    public static void main(String[] args) {
        ... // 생략

        deliver(burger1, "101호");
        deliver(burger2, "302호");
    }

    // 배달
    public static void deliver(Burger burger, String address) {
        System.out.println(burger.name + "를 " + address + "로 배달합니다.");
    }
}

실행결과는 이렇습니다.

햄버거를 만듭니다.
치즈버거를 만듭니다.

햄버거를 101호로 배달합니다.
치즈버거를 302호로 배달합니다.

main() 메소드의 전체 코드만 다시 적어보면 이렇게 되는데요. 보시다시피 배달은 2줄이면 되지만 햄버거를 만드는 부분의 코드 비중이 상당히 깁니다. 실무에서는 이보다 훨씬 더 길 수도 있지요.

public static void main(String[] args) {
    Burger burger1 = new Burger() {
        @Override
        public void cook() {
            System.out.println(name + "를 만듭니다.");
        }
    };
    burger1.name = "햄버거";
    burger1.price = 5000;
    burger1.bestBefore = "2023년 2월 28일";
    burger1.cook();

    Burger burger2 = new Burger() {
        @Override
        public void cook() {
            System.out.println(name + "를 만듭니다.");
        }
    };
    burger2.name = "치즈버거";
    burger2.price = 6000;
    burger2.bestBefore = "2023년 3월 1일";
    burger2.cook();

    deliver(burger1, "101호");
    deliver(burger2, "302호");
}

이때 메소드를 이용한 방식으로 햄버거 객체를 만든다면 main() 메소드는 이렇게 간소화 될 수 있을 겁니다. 먼저 이름, 가격, 유통기한 정보를 받는 getBurger() 메소드를 이렇게 만들어주고요.

public static Burger getBurger(String name, int price, String bestBefore) {
    Burger burger = new Burger() {
        @Override
        public void cook() {
            System.out.println(name + "를 만듭니다.");
        }
    };
    burger.name = name;
    burger.price = price;
    burger.bestBefore = bestBefore;
    return burger;
}

main() 메소드에서 이를 호출해줍니다.

public static void main(String[] args) {
    Burger burger1 = getBurger("햄버거", 5000, "2023년 2월 28일");    
    burger1.cook();

    Burger burger2 = getBurger("치즈버거", 6000, "2023년 3월 1일");    
    burger2.cook();

    deliver(burger1, "101호");
    deliver(burger2, "302호");
}

이전 코드보다는 이해하기가 많이 수월해보이네요. 이렇게 해서 Burger 클래스를 상속하여 객체를 만들고 정보를 입력하는 작업은 getBurger() 에서, 이를 통해 햄버거를 요리하고 배달하는 작업은 main() 메소드에서 처리할 수 있게 되었습니다.

다시 정리하면, 추상 클래스를 상속받아 구현해야 하는 작업량이 많고 복잡하거나 후속작업이 필요한 경우라면 메소드를 이용하는 것을 고려해볼 수 있으며, 몇 줄 되지 않는 간단한 작업이라면 익명 클래스를 이용하여 직접 구현하는 편이 권장됩니다 😊

그리고 이런 형태의 익명 클래스는 보통 어떤 이벤트가 발생했을 때 처리되어야 하는 동작을 정의하는 데 많이 사용됩니다. 자바 안드로이드의 예시라면 버튼을 눌렀을 때 메시지를 보여주기 위해서 이런 식으로 쓰곤 한답니다.

Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(getApplicationContext(), "안녕하세요", Toast.LENGTH_SHORT).show();
    }
});

감사합니다.

이번에도 큰 도움 됐습니다! 감사합니다!

흰머리오목눈이님의 프로필 이미지
흰머리오목눈이

작성한 질문수

질문하기