[Java 응용] Nested(중첩) Class [static, inner, local, anonymous]

Kyung Jae, Cheong·2024년 9월 1일
0
post-thumbnail

중첩 클래스 (Nested Class)

  • 중첩 클래스(Nested Class)는 클래스 내부에 정의된 클래스를 의미합니다.

    • 자바에서는 코드의 논리적 구조를 좀 더 명확하게 하기 위해, 혹은 특정 클래스와 밀접한 관련이 있는 클래스를 논리적으로 그룹화하기 위해 중첩 클래스를 사용합니다.
    • 중첩 클래스는 외부 클래스의 멤버처럼 동작하며, 외부 클래스와의 관계를 통해 다양한 기능을 수행할 수 있습니다.
  • 중첩 클래스는 크게 네 가지로 나눌 수 있습니다

    • 정적 중첩 클래스 (Static Nested Class)
      • static 키워드를 사용하여 선언된 클래스입니다.
      • 외부 클래스의 인스턴스와 독립적으로 존재할 수 있습니다.
      • 외부 클래스의 static 멤버들만 접근할 수 있습니다.
      • 마치 외부 클래스의 정적 멤버 변수처럼 동작합니다.
    • 내부 클래스 (Inner Class)
      • static 키워드를 사용하지 않고 선언된 클래스입니다.
      • 외부 클래스의 인스턴스와 밀접하게 연결되어 있으며, 외부 클래스의 멤버(변수, 메서드)들을 자유롭게 사용할 수 있습니다.
      • 외부 클래스의 인스턴스가 있어야만 생성 및 사용이 가능합니다.
    • 지역 클래스 (Local Class)
      • 메서드 내부에서 정의된 클래스입니다.
      • 메서드 내에서만 사용되며, 외부에서는 접근할 수 없습니다.
      • 외부 클래스의 멤버뿐만 아니라, 메서드 내의 지역 변수에도 접근할 수 있지만, 메서드의 지역 변수는 final로 선언되어야 합니다(Java 8 이후에는 effectively final도 허용).
    • 익명 클래스 (Anonymous Class)
      • 이름이 없는 클래스입니다.
      • 일회성으로 사용되며, 보통 특정 인터페이스나 추상 클래스를 구현하거나 확장할 때 사용됩니다.
      • 즉시 인스턴스를 생성할 때 정의됩니다.
      • 간결하게 코드를 작성할 수 있지만, 코드의 가독성을 해칠 수 있으므로 신중하게 사용해야 합니다.
  • 중첩 클래스는 이러한 다양한 형태로 자바 프로그램의 구조를 더욱 효율적으로 만들 수 있으며, 특히 코드의 응집도를 높이고 외부 클래스와 관련된 작업을 모듈화할 수 있습니다.

  • 하지만 그만큼 사용 시 주의할 점도 존재하므로 적절한 용도에 맞게 활용하는 것이 중요합니다.

1. 정적 중첩 클래스 (Static Nested Class)

  • 정적 중첩 클래스(Static Nested Class)static 키워드를 사용하여 선언된 중첩 클래스입니다.
    • 외부 클래스의 인스턴스와 독립적으로 존재할 수 있으며, 외부 클래스의 static 멤버들만 접근할 수 있습니다.
    • 정적 중첩 클래스는 외부 클래스의 정적 멤버로 간주되며, 일반적으로 외부 클래스와 밀접한 관계가 있지만 독립적으로 사용 가능한 경우에 유용합니다.

특징

  • 외부 클래스의 인스턴스 없이도 객체 생성이 가능합니다.
  • 외부 클래스의 static 멤버들에만 접근할 수 있습니다.
  • 외부 클래스의 비정적(non-static) 멤버에는 직접 접근할 수 없습니다.

코드 예시

public class OuterClass {
    private int outerInstanceVar = 10;
    private static int outerStaticVar = 20;

    // 정적 중첩 클래스
    public static class StaticNestedClass {
        public void display() {
            // 외부 클래스의 비정적 멤버에는 접근할 수 없음
            // System.out.println("outerInstanceVar: " + outerInstanceVar); // 오류 발생

            // 외부 클래스의 정적 멤버에만 접근 가능
            System.out.println("outerStaticVar: " + outerStaticVar);
        }
    }

    public static void main(String[] args) {
        // 정적 중첩 클래스의 인스턴스 생성
        OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
        nestedObject.display();
    }
}
  • 실행 결과
outerStaticVar: 20
  • 위 예제에서 OuterClass라는 외부 클래스가 있으며, 그 안에 StaticNestedClass라는 정적 중첩 클래스가 정의되어 있습니다.
    • StaticNestedClassstatic으로 선언되었기 때문에 외부 클래스의 인스턴스 없이도 객체를 생성할 수 있습니다.
  • StaticNestedClass 내부에서는 외부 클래스의 정적 멤버인 outerStaticVar에 접근할 수 있습니다. (접근제어자 상관없이 접근 가능합니다.)
    • 외부 클래스의 비정적 멤버(outerInstanceVar)에는 직접 접근할 수 없습니다.

2. 내부 클래스 (Inner Class)

  • 내부 클래스(Inner Class)는 외부 클래스의 멤버로 선언된 클래스로, static 키워드 없이 선언됩니다.
    • 내부 클래스는 외부 클래스의 인스턴스와 밀접하게 연결되어 있으며, 외부 클래스의 모든 멤버(필드, 메서드)에 접근할 수 있습니다.
    • 내부 클래스는 외부 클래스의 인스턴스(생성객체)가 있어야만 생성 및 사용할 수 있습니다.

특징

  • 외부 클래스의 모든 멤버(정적 및 비정적)에 접근할 수 있습니다.
  • 내부 클래스의 객체는 외부 클래스의 인스턴스를 통해 생성됩니다.
  • 외부 클래스와 밀접한 관계를 맺고, 외부 클래스의 상태를 변경하거나 외부 클래스의 메서드를 호출해야할 때 주로 사용됩니다.

코드 예시

public class OuterClass {
    private int outerInstanceVar = 10;
    private static int outerStaticVar = 20;

    // 내부 클래스
    public class InnerClass {
        public void display() {
            // 외부 클래스의 모든 멤버에 접근 가능
            System.out.println("outerInstanceVar: " + outerInstanceVar);
            System.out.println("outerStaticVar: " + outerStaticVar);
        }
    }

    public static void main(String[] args) {
        // 외부 클래스의 인스턴스 생성
        OuterClass outerObject = new OuterClass();

        // 내부 클래스의 인스턴스 생성
        OuterClass.InnerClass innerObject = outerObject.new InnerClass();
        innerObject.display();
    }
}
  • 실행결과
outerInstanceVar: 10
outerStaticVar: 20
  • 위 예제에서 OuterClass라는 외부 클래스 내부에 InnerClass라는 내부 클래스가 정의되어 있습니다.
    • 내부 클래스는 외부 클래스의 인스턴스 변수와 정적 변수 모두에 접근할 수 있습니다.
      • InnerClass는 외부 클래스의 인스턴스 변수 outerInstanceVar와 정적 변수 outerStaticVar에 접근하여 값을 출력합니다.
    • 내부 클래스의 인스턴스는 반드시 외부 클래스의 인스턴스를 통해 생성해야 합니다.
      • 이는 내부 클래스가 외부 클래스의 맥락(context) 내에서 동작하기 때문입니다.

3. 지역 클래스 (Local Class)

  • 지역 클래스(Local Class)는 메서드, 생성자, 또는 초기화 블록 내부에서 정의된 클래스를 의미합니다.
    • 지역 클래스는 선언된 블록 내에서만 사용할 수 있으며, 이 범위를 벗어나면 접근할 수 없습니다.
    • 지역 클래스는 해당 메서드나 블록 내에서만 필요할 때 유용하게 사용됩니다.

특징

  • 메서드나 블록 내에서 정의되며, 해당 블록이 실행되는 동안만 유효합니다.
  • 외부 클래스의 멤버뿐만 아니라, 메서드의 지역 변수와 매개변수에도 접근할 수 있습니다.
  • 메서드의 지역 변수에 접근할 때, 해당 변수는 final (또는 Java 8 이후로는 사실상 final(Effectively final))이어야 합니다.
    • 쉽게 말하면 자바8 이후로는 final을 명시하지 않아도 final로 선언 된다는 의미입니다.

코드 예시

public class OuterClass {
    private int outerInstanceVar = 10;

    public void outerMethod() {
        int localVar = 20; // 사실상 final 변수

        // 지역 클래스
        class LocalClass {
            public void display() {
                // 외부 클래스의 인스턴스 변수에 접근 가능
                System.out.println("outerInstanceVar: " + outerInstanceVar);

                // 사실상 final인 메서드의 지역 변수에 접근 가능
                System.out.println("localVar: " + localVar);
            }
        }

        // 지역 클래스의 인스턴스 생성 및 메서드 호출
        LocalClass localObject = new LocalClass();
        localObject.display();
    }

    public static void main(String[] args) {
        OuterClass outerObject = new OuterClass();
        outerObject.outerMethod();
    }
}
  • 실행 결과
outerInstanceVar: 10
localVar: 20
  • 위 예제에서 outerMethod()라는 메서드 내에 LocalClass라는 지역 클래스가 정의되어 있습니다.
    • 지역 클래스는 해당 메서드 내에서만 사용할 수 있으며, 메서드가 종료되면 이 클래스에 접근할 수 없습니다.
  • 지역 클래스는 외부 클래스의 멤버 변수 outerInstanceVar뿐만 아니라, 메서드 내의 지역 변수 localVar에도 접근할 수 있습니다.
    • localVar는 Java 8 이후 사실상 final(Effectively final)로 간주되며, 지역 클래스 내부에서 사용할 수 있습니다.
      • "사실상 final"이란 해당 변수가 초기화된 이후로 값이 변경되지 않는 경우를 말합니다.

(참고) 지역 클래스의 캡처 (Capture) 기능

  • 지역 클래스에서 메서드의 지역 변수나 매개변수에 접근할 수 있지만, 해당 변수는 반드시 final로 선언되거나 사실상 final이어야 합니다.
    • 이는 지역 클래스가 메서드와 같은 스코프에서 실행되는 것이 아니라, 메서드가 종료된 이후에도 해당 클래스의 인스턴스가 유지될 수 있기 때문입니다.
  • 메모리적 관점에서 스택영역에 저장되는 지역변수(혹은 매개변수)가 메서드가 종료된 이후에도 어떻게 유지되는가에 대한 의문이 발생할 것입니다.
  • 지역 클래스가 메서드의 지역 변수에 접근할 때, 이 변수를 캡처(capture)한다고 말합니다.
    • 이 변수의 값은 지역 클래스 내부에 복사되어 저장되며, 메서드가 종료된 후에도 지역 클래스 내부에서 참조할 수 있게 됩니다.
    • 이때 변수는 변경될 수 없으므로 final 또는 사실상 final로 제한됩니다.
  • 자세한걸 이해하기 보단 일단 이런게 있구나 정도로 넘어가시면 됩니다.

4. 익명 클래스 (Anonymous Class)

  • 익명 클래스(Anonymous Class)는 이름이 없는 클래스로, 일반적으로 일회성 작업을 위해 사용됩니다.
    • 주로 인터페이스나 추상 클래스를 구현하거나, 특정 메서드만 재정의할 때 사용됩니다.
    • 익명 클래스는 인스턴스를 생성하는 순간에 정의되며, 재사용되지 않습니다.

특징

  • 이름이 없고, 한 번만 사용할 수 있는 클래스입니다.
  • 주로 인터페이스를 구현하거나 추상 클래스를 확장할 때 사용됩니다.
  • 코드가 간결해지지만, 가독성이 떨어질 수 있으므로 신중하게 사용해야 합니다.

코드 예시

public class OuterClass {
    public void outerMethod() {
        // 익명 클래스 - Runnable 인터페이스 구현
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Anonymous class implementation of Runnable");
            }
        };

        // 스레드에서 익명 클래스 실행
        new Thread(runnable).start();
    }

    public static void main(String[] args) {
        OuterClass outerObject = new OuterClass();
        outerObject.outerMethod();
    }
}
  • 실행 결과
Anonymous class implementation of Runnable
  • 위 예제에서 Runnable 인터페이스를 구현하는 익명 클래스가 정의되어 있습니다.
    • 익명 클래스는 new Runnable() 구문에서 직접 구현되었으며, 해당 클래스는 run() 메서드를 재정의(오버라이딩)하여 특정 작업을 수행합니다.
  • 익명 클래스는 Runnable 인터페이스를 구현한 다른 클래스 없이도 일회성으로 인터페이스를 구현하여 사용할 수 있습니다.

(참고) 람다 표현식 (Lambda Expression)

  • 익명 클래스를 사용하는 대표적인 경우는 함수형 인터페이스(메서드가 하나만 존재하는 인터페이스)를 구현할 때입니다.
    • 이 경우 자바 8에서 도입된 람다 표현식(Lambda Expression)을 사용하면 더 간결하게 코드를 작성할 수 있습니다.
  • 람다 표현식은 익명 클래스의 대체제이며, 주로 함수형 인터페이스를 구현할 때 사용됩니다.
    • 람다 표현식의 기본 형태 : (인수 목록) -> { 실행 코드 }
  • 람다 표현식에 대해 다뤄야하는 내용이 너무 많아지기 때문에, 이번 포스팅에서는 람다표현식을 익명 클래스에서 어떻게 활용하는지를 간단하게 살펴볼 것입니다.

람다 표현식 예제

  • 위의 Runnable 예제를 람다 표현식으로 변환하면 다음과 같습니다.
public class OuterClass {
    public void outerMethod() {
        // 람다 표현식 - Runnable 인터페이스 구현
        Runnable runnable = () -> System.out.println("Lambda implementation of Runnable");

        // 스레드에서 람다 표현식 실행
        new Thread(runnable).start();
    }

    public static void main(String[] args) {
        OuterClass outerObject = new OuterClass();
        outerObject.outerMethod();
    }
}
  • Runnable 인터페이스는 메서드가 하나만 있는 함수형 인터페이스이므로, 람다 표현식을 사용할 수 있습니다.
  • 람다 표현식을 사용하면 코드가 더 간결해지고, 가독성이 향상됩니다.

상황에 따른 적절한 중첩 클래스 사용

  • 자바에서 중첩 클래스(Nested Class)는 외부 클래스와의 관계, 코드의 응집도, 재사용성 등의 측면에서 고려하여 선택해야 합니다.
  • 각 중첩 클래스 유형은 특정한 상황에서 최적의 선택이 될 수 있으며, 아래에서 각 상황에 맞는 중첩 클래스의 사용 사례를 정리해 보겠습니다.

1. 정적 중첩 클래스 (Static Nested Class)

  • 사용 상황: 외부 클래스와 밀접하게 관련되지만, 외부 클래스의 인스턴스 없이 독립적으로 동작할 수 있는 기능이 필요할 때.
  • 예시:
    • 외부 클래스의 유틸리티 클래스 또는 내부 데이터 구조 정의.
    • 외부 클래스의 정적 멤버와 관련된 작업.
    • 예를 들어, 외부 클래스가 다양한 유형의 데이터를 처리해야 하고, 각 유형의 데이터를 처리하는 독립적인 로직이 필요할 때 정적 중첩 클래스를 사용할 수 있습니다.
  • 적합한 예시: Map.Entry 인터페이스의 구현체들처럼, 외부 클래스의 특정 자료구조의 노드 역할을 하는 경우.

2. 내부 클래스 (Inner Class)

  • 사용 상황: 외부 클래스의 인스턴스와 강한 결합을 가지며, 외부 클래스의 인스턴스 변수를 직접 사용해야 할 때.
  • 예시:
    • 외부 클래스의 상태를 조작하거나 보조 작업을 수행하는 로직.
    • 외부 클래스의 상태에 강하게 의존하는 복잡한 GUI 구성 요소나 이벤트 핸들러.
    • 예를 들어, GUI 어플리케이션에서 버튼 클릭 이벤트 핸들러를 내부 클래스로 정의하여 외부 클래스의 여러 멤버 변수에 쉽게 접근할 수 있게 할 때 적합합니다.
  • 적합한 예시: 외부 클래스의 데이터를 처리하기 위해 외부 클래스의 여러 필드와 메서드에 접근해야 하는 경우.

3. 지역 클래스 (Local Class)

  • 사용 상황: 특정 메서드 내에서만 필요하며, 해당 메서드의 지역 변수나 매개변수에 접근해야 하는 간단한 작업이 있을 때.
  • 예시:
    • 특정 메서드에서만 사용되는 일회성 데이터 구조 또는 알고리즘 구현.
    • 외부 클래스의 멤버뿐만 아니라 메서드의 지역 변수에 접근할 필요가 있는 경우.
    • 예를 들어, 특정 계산을 수행하고 그 결과를 메서드 내에서만 활용하는 간단한 로직을 구현할 때 지역 클래스를 사용하면 좋습니다.
  • 적합한 예시: 메서드의 범위 내에서만 사용하는 작은 클래스, 예를 들어 특정 정렬 로직이나 임시 데이터 저장소.

4. 익명 클래스 (Anonymous Class)

  • 사용 상황: 인터페이스나 추상 클래스를 구현하여 일회성 작업을 수행할 때, 클래스 이름을 정의할 필요가 없고 간단하게 작성하고자 할 때.
  • 예시:
    • 이벤트 리스너 또는 콜백을 간단히 정의할 때.
    • 바로 사용되는 인터페이스의 구현체가 필요할 때.
    • 예를 들어, 버튼 클릭 리스너를 정의할 때 또는 일회성 스레드를 생성할 때 익명 클래스를 사용하면 코드가 간결해집니다.
  • 적합한 예시: 즉석에서 간단하게 인터페이스 구현체가 필요할 때, 예를 들어, Runnable 인터페이스를 구현하여 스레드를 생성하거나, 이벤트 리스너를 정의할 때.

마무리

  • 중첩 클래스는 외부 클래스와의 관계 및 사용 목적에 따라 적절하게 선택해야 합니다.
    • 정적 중첩 클래스는 외부 클래스와 독립적으로 동작할 수 있는 경우에 유용하며, 재사용 가능한 유틸리티 클래스나 특정 기능을 모듈화할 때 사용됩니다.
    • 내부 클래스는 외부 클래스의 인스턴스와 긴밀하게 상호작용해야 할 때 사용됩니다.
    • 지역 클래스는 특정 메서드 내에서만 필요한 복잡한 로직을 구현할 때 적합합니다.
    • 익명 클래스는 간단한 일회성 인터페이스 구현체가 필요할 때, 코드의 간결성을 위해 사용됩니다.
  • 각 클래스의 사용 사례를 이해하고, 필요에 맞게 적절한 중첩 클래스를 선택하는 것이 중요합니다.
profile
일 때문에 포스팅은 잠시 쉬어요 ㅠ 바쁘다 바빠 모두들 화이팅! // Machine Learning (AI) Engineer & BackEnd Engineer (Entry)

0개의 댓글