본문 바로가기

Java

Nested Classes(중첩 클래스)

자바 프로그래밍 언어에서는 한 클래스 내에 다른 클래스를 정의할 수 있습니다. 이러한 클래스를 Nested Class라고 하며, 여기서 그 예를 보여줍니다.

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}

 


용어: 중첩 클래스는 두 가지 범주로 분류됩니다:

non-static 과 static.

Non-static 중첩 클래스는 inner 클래스라고 합니다.

static으로 선언된 중첩 클래스들은 static nested 클래스라고 합니다.


class OuterClass {
    ...
    class InnerClass {
        ...
    }
    static class StaticNestedClass {
        ...
    }
}

 

 

중첩 클래스는 중첩 클래스를 포함하고 있는 클래스의 멤버입니다.

  • 비정적[Non-static] 중첩 클래스(내부 클래스)는 포함하고 있는 외부 클래스의 다른 멤버에 접근할 수 있으며, 그 멤버들이 private으로 선언되어 있더라도 마찬가지입니다.
  • 반면에, 정적 중첩 클래스는 포함하고 있는 외부 클래스의 다른 멤버에 접근할 수 없습니다.
  • OuterClass의 멤버로서, 중첩 클래스는 private, public, protected, 또는 package-private으로 선언될 수 있습니다. (외부 클래스는 public 또는 package-private으로만 선언될 수 있다는 점을 상기해주세요.)

 

 

Why Use Nested Classes?

중첩 클래스를 사용하는데는 다음과 같은 설득력 있는 이유들이 있습니다:

  • 한 곳에서만 사용되는 클래스들을 논리적으로 그룹화하는 방법: 만약 어떤 클래스가 다른 클래스 하나에만 유용하다면, 그 클래스를 해당 클래스 내부에 내장시키고 두 클래스를 함께 유지하는 것이 논리적입니다. 이러한 "helper 클래스"들을 중첩시키면 패키지가 더욱 간결해집니다.
  • 캡슐화 증가: B가 private로 선언될 A의 멤버에 액세스해야 하는 두 개의 최상위 클래스 A와 B를 생각해 보세요. 클래스 A 내에 클래스 B를 숨김으로써 A의 멤버를 private로 선언하고 B가 해당 멤버에 액세스할 수 있습니다. 또한 B 자체도 외부 세계로부터 숨겨질 수 있습니다.
  • 더 읽기 쉽고 유지보수하기 쉬운 코드로 이어질 수 있음: 최상위 클래스 내부에 작은 클래스들을 중첩시키면, 코드를 사용하는 곳에 더 가깝게 배치할 수 있습니다.

 

Inner Classes

인스턴스 메소드와 변수와 마찬가지로, 내부 클래스는 그것을 포함하는 클래스의 인스턴스와 연관되어 있으며, 그 객체의 메소드와 필드에 직접 접근할 수 있습니다. 또한, 내부 클래스는 인스턴스와 연관되어 있기 때문에, 스스로 어떤 정적 멤버도 정의할 수 없습니다.

내부 클래스의 인스턴스 객체들은 외부 클래스의 인스턴스 내부에 존재합니다. 다음 클래스들을 고려해보세요:

class OuterClass {
    ...
    class InnerClass {
        ...
    }
}

 

InnerClass의 인스턴스는 OuterClass의 인스턴스 내부에서만 존재할 수 있으며, InnerClass의 인스턴스를 포함하는 OuterClass 인스턴스의 메소드와 필드에 직접 접근할 수 있습니다.

내부 클래스를 인스턴스화하려면, 먼저 외부 클래스를 인스턴스화해야 합니다. 그런 다음, 이 구문을 사용하여 외부 객체 내부에 내부 객체를 생성합니다:

OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

 

두 가지의 특별한 inner 클래스들이 있습니다: local class 와 anonymous class

 

 

Static Nested Classes

클래스 메소드와 변수처럼, 정적 중첩 클래스는 정적 중첩 클래스의 외부 클래스와 연관되어 있습니다. 그리고 정적 클래스 메소드처럼, 정적 중첩 클래스는 직접적으로 정적 중첩 클래스를 포함하는 클래스에서 정의된 인스턴스 필드나 메소드를 참조할 수 없습니다: 오직 객체 참조를 통해서만 사용할 수 있습니다. Inner Class and Nested Static Class Example 에서 이를 보여줍니다.


참고: 정적 중첩 클래스는 다른 최상위 클래스들처럼 정적 중첩 클래스를 포함하는 외부 클래스의 인스턴스 멤버들과 상호작용합니다. 실제로, 정적 중첩 클래스는 행동적으로는 최상위 클래스이지만, 패키징의 편의를 위해 다른 최상위 클래스에 중첩된 클래스입니다. Inner Class and Nested Static Class Example 도 이를 보여줍니다.


정적 중첩 클래스를 인스턴스화하는 방법은 최상위 클래스를 인스턴스화하는 방법과 동일합니다:

StaticNestedClass staticNestedObject = new StaticNestedClass();

 

Inner Class and Nested Static Class Example

 

다음 예제인 OuterClass와 TopLevelClass는 외부 클래스(OuterClass)의 멤버 중 어떤 것들이 내부 클래스(InnerClass), 중첩 정적 클래스(StaticNestedClass), 그리고 최상위 클래스(TopLevelClass)에서 접근할 수 있는지를 보여줍니다:

OuterClass.java

public class OuterClass {

    String outerField = "Outer field";
    static String staticOuterField = "Static outer field";

    class InnerClass {
        void accessMembers() {
            System.out.println(outerField);
            System.out.println(staticOuterField);
        }
    }

    static class StaticNestedClass {
        void accessMembers(OuterClass outer) {
            // Compiler error: Cannot make a static reference to the non-static
            //     field outerField
            // System.out.println(outerField);
            System.out.println(outer.outerField);
            System.out.println(staticOuterField);
        }
    }

    public static void main(String[] args) {
        System.out.println("Inner class:");
        System.out.println("------------");
        OuterClass outerObject = new OuterClass();
        OuterClass.InnerClass innerObject = outerObject.new InnerClass();
        innerObject.accessMembers();

        System.out.println("\nStatic nested class:");
        System.out.println("--------------------");
        StaticNestedClass staticNestedObject = new StaticNestedClass();        
        staticNestedObject.accessMembers(outerObject);
        
        System.out.println("\nTop-level class:");
        System.out.println("--------------------");
        TopLevelClass topLevelObject = new TopLevelClass();        
        topLevelObject.accessMembers(outerObject);                
    }
}

 

TopLevelClass.java

public class TopLevelClass {

    void accessMembers(OuterClass outer) {     
        // Compiler error: Cannot make a static reference to the non-static
        //     field OuterClass.outerField
        // System.out.println(OuterClass.outerField);
        System.out.println(outer.outerField);
        System.out.println(OuterClass.staticOuterField);
    }  
}

 

This example prints the following output:

Inner class:
------------
Outer field
Static outer field

Static nested class:
--------------------
Outer field
Static outer field

Top-level class:
--------------------
Outer field
Static outer field

 

정적 중첩 클래스는 다른 최상위 클래스처럼 외부 클래스의 인스턴스 멤버와 상호 작용합니다. 정적 중첩 클래스인 StaticNestedClass는 외부 클래스인 OuterClass의 인스턴스 필드인 outerField에 직접 접근할 수 없습니다. 자바 컴파일러는 강조된 문장에서 오류를 생성합니다:

static class StaticNestedClass {
    void accessMembers(OuterClass outer) {
       // Compiler error: Cannot make a static reference to the non-static
       //     field outerField
       System.out.println(outerField);  // 에러가 발생하는 부분
    }
}

 

이 오류를 수정하려면 객체 참조를 통해 outerField에 접근하세요:

System.out.println(outer.outerField);

마찬가지로, 최상위 클래스인 TopLevelClass도 outerField에 직접 접근할 수 없습니다.

 

Shadowing

특정 범위(예: 내부 클래스 또는 메소드 정의) 내에서 타입(멤버 변수 또는 파라미터 이름과 같은)의 선언이 포함 범위 내의 다른 선언과 동일한 이름을 가지면, 해당 선언은 포함 범위의 선언을 가립니다(shadow). 그림자진(shadowed) 선언을 단독으로 그 이름으로 참조할 수는 없습니다. 다음 예제인 ShadowTest는 이를 보여줍니다:

public class ShadowTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

The following is the output of this example:

x = 23
this.x = 1
ShadowTest.this.x = 0

 

이 예제에서는 이름이 x:인 세 개의 변수를 정의합니다: 클래스 ShadowTest의 멤버 변수, 내부 클래스 FirstLevel의 멤버 변수, 그리고 메소드 methodInFirstLevel의 파라미터. 메소드 methodInFirstLevel의 파라미터로 정의된 변수 x는 내부 클래스 FirstLevel의 변수를 가립니다. 따라서 methodInFirstLevel 메소드에서 변수 x를 사용하면, 그것은 메소드의 파라미터를 참조합니다. 내부 클래스 FirstLevel의 멤버 변수를 참조하려면, 포함 범위를 나타내는 키워드 this를 사용하세요:

System.out.println("this.x = " + this.x);

 

더 큰 범위를 포함하는 멤버 변수는 소속된 클래스 이름을 사용하여 참조합니다. 예를 들어, 다음 문장은 methodInFirstLevel 메소드에서 클래스 ShadowTest의 멤버 변수에 접근합니다:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

 

 

Serialization

생략...

 

 

Inner Class Example

내부 클래스가 사용되는 것을 보기 위해, 먼저 배열을 고려해보겠습니다. 다음 예제에서, 배열을 생성하고, 정수 값으로 채운 다음, 오름차순으로 배열의 짝수 인덱스의 값만을 출력합니다.

이어지는 DataStructure.java 예제는 다음으로 구성됩니다:

  • DataStructure 외부 클래스는 연속된 정수 값(0, 1, 2, 3 등)으로 채워진 배열을 포함하는 DataStructure의 인스턴스를 생성하기 위한 생성자와 짝수 인덱스 값을 가진 배열 요소들을 출력하는 메소드를 포함합니다.
  • EvenIterator 내부 클래스는 DataStructureIterator 인터페이스를 구현하며, 이 인터페이스는 Iterator<Integer> 인터페이스를 확장합니다. 이터레이터는 데이터 구조를 순회하는 데 사용되며, 일반적으로 마지막 요소를 테스트하는 메소드, 현재 요소를 검색하는 메소드, 다음 요소로 이동하는 메소드를 가지고 있습니다.
  • main 메소드는 DataStructure 객체(ds)를 인스턴스화한 다음, printEven 메소드를 호출하여 짝수 인덱스 값을 가진 arrayOfInts 배열의 요소들을 출력합니다.

 

public class DataStructure {
    
    // Create an array
    private final static int SIZE = 15;
    private int[] arrayOfInts = new int[SIZE];
    
    public DataStructure() {
        // fill the array with ascending integer values
        for (int i = 0; i < SIZE; i++) {
            arrayOfInts[i] = i;
        }
    }
    
    public void printEven() {
        
        // Print out values of even indices of the array
        DataStructureIterator iterator = this.new EvenIterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
        System.out.println();
    }
    
    interface DataStructureIterator extends java.util.Iterator<Integer> { } 

    // Inner class implements the DataStructureIterator interface,
    // which extends the Iterator<Integer> interface
    
    private class EvenIterator implements DataStructureIterator {
        
        // Start stepping through the array from the beginning
        private int nextIndex = 0;
        
        public boolean hasNext() {
            
            // Check if the current element is the last in the array
            return (nextIndex <= SIZE - 1);
        }        
        
        public Integer next() {
            
            // Record a value of an even index of the array
            Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]);
            
            // Get the next even element
            nextIndex += 2;
            return retValue;
        }
    }
    
    public static void main(String s[]) {
        
        // Fill the array with integer values and print out only
        // values of even indices
        DataStructure ds = new DataStructure();
        ds.printEven();
    }
}

The output is:

0 2 4 6 8 10 12 14

 

EvenIterator 클래스는 DataStructure 객체의 arrayOfInts 인스턴스 변수를 직접 참조한다는 점에 유의하세요.

 

이 예제에서 보여주는 것처럼, 내부 클래스를 사용하여 도우미 클래스와 같은 것들을 구현할 수 있습니다. 사용자 인터페이스 이벤트를 처리하기 위해서는 내부 클래스 사용 방법을 알아야 합니다. 왜냐하면 이벤트 처리 메커니즘은 내부 클래스를 광범위하게 사용하기 때문입니다.

 

Local and Anonymous Classes

내부 클래스에는 두 가지 추가적인 유형이 있습니다. 메소드의 본문 내에 내부 클래스를 선언할 수 있습니다. 이러한 클래스들은 로컬 클래스로 알려져 있습니다. 또한, 클래스의 이름을 지정하지 않고 메소드 본문 내에 내부 클래스를 선언할 수도 있습니다. 이러한 클래스들은 익명 클래스로 알려져 있습니다.

 

Modifiers

내부 클래스에는 외부 클래스의 다른 멤버들에 사용하는 것과 동일한 수정자(modifiers)를 사용할 수 있습니다. 예를 들어, 다른 클래스 멤버들의 접근을 제한하는 데 사용하는 것처럼, 접근 지정자인 private, public, 및 protected를 사용하여 내부 클래스에 대한 접근을 제한할 수 있습니다.

 

Local Classes

로컬 클래스는 균형 잡힌 중괄호 사이에 있는 하나 이상의 문장 그룹인 블록 내에 정의된 클래스입니다. 일반적으로 메소드 본문 내에서 로컬 클래스를 정의하는 것을 볼 수 있습니다.

 

Declaring Local Classes

어떤 블록 내에서도 로컬 클래스를 정의할 수 있습니다(자세한 내용은 Expressions, Statements, and Blocks 을 참조하세요). 예를 들어, 메소드 본문, for 루프, if 절에서 로컬 클래스를 정의할 수 있습니다.

다음 예제인 LocalClassExample은 두 개의 전화번호를 검증합니다. 이는 validatePhoneNumber 메소드 내에 PhoneNumber라는 로컬 클래스를 정의합니다:

public class LocalClassExample {
  
    static String regularExpression = "[^0-9]";
  
    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {
      
        final int numberLength = 10;
        
        // Valid in JDK 8 and later:
       
        // int numberLength = 10;
       
        class PhoneNumber {
            
            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }
            
            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
        
        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-7890", "456-7890");
    }
}

 

이 예제는 먼저 전화번호에서 0부터 9까지의 숫자를 제외한 모든 문자를 제거함으로써 전화번호를 검증합니다. 그 후에, 전화번호가 북미 지역의 전화번호 길이인 정확히 10자리를 포함하고 있는지 확인합니다. 이 예제는 다음과 같은 결과를 출력합니다:

First number is 1234567890
Second number is invalid

 

 

Accessing Members of an Enclosing Class

로컬 클래스는 포함하는 클래스의 멤버에 접근할 수 있습니다. 이전 예제에서, PhoneNumber 생성자는 LocalClassExample.regularExpression 멤버에 접근합니다.

또한, 로컬 클래스는 로컬 변수에도 접근할 수 있습니다. 그러나 로컬 클래스는 final로 선언된 로컬 변수에만 접근할 수 있습니다. 로컬 클래스가 포함 블록의 로컬 변수나 파라미터에 접근할 때, 그 변수나 파라미터를 캡처합니다. 예를 들어, PhoneNumber 생성자는 final로 선언된 numberLength 로컬 변수에 접근할 수 있으며, numberLength는 캡처된 변수입니다.

그러나 Java SE 8부터 로컬 클래스는 포함 블록의 final 또는 실질적으로 final인 로컬 변수와 파라미터에 접근할 수 있습니다. 초기화된 후에 결코 변경되지 않는 변수나 파라미터는 실질적으로 final입니다. 예를 들어, numberLength 변수가 final로 선언되지 않았다고 가정하고, PhoneNumber 생성자에 하이라이트된 할당 문을 추가하여 유효한 전화번호의 길이를 7자리로 변경한다면:

PhoneNumber(String phoneNumber) {
    numberLength = 7;
    String currentNumber = phoneNumber.replaceAll(
        regularExpression, "");
    if (currentNumber.length() == numberLength)
        formattedPhoneNumber = currentNumber;
    else
        formattedPhoneNumber = null;
}

 

이 할당 문으로 인해, 변수 numberLength는 더 이상 실질적으로 final이 아닙니다. 결과적으로, 내부 클래스 PhoneNumber가 numberLength 변수에 접근하려고 할 때, 자바 컴파일러는 "내부 클래스에서 참조되는 로컬 변수는 final이거나 실질적으로 final이어야 합니다"와 유사한 오류 메시지를 생성합니다:

if (currentNumber.length() == numberLength)

 

 

Starting in Java SE 8, if you declare the local class in a method, it can access the method's parameters. For example, you can define the following method in the PhoneNumber local class:

 

Java SE 8부터 메소드 내에서 로컬 클래스를 선언하면, 해당 클래스는 메소드의 파라미터에 접근할 수 있습니다. 예를 들어, PhoneNumber 로컬 클래스 내에 다음과 같은 메소드를 정의할 수 있습니다:

 

public void printOriginalNumbers() {
    System.out.println("Original numbers are " + phoneNumber1 +
        " and " + phoneNumber2);
}

메소드 printOriginalNumbers는 validatePhoneNumber 메소드의 파라미터인 phoneNumber1과 phoneNumber2에 접근합니다.

 

Shadowing and Local Classes

로컬 클래스에서 타입(예: 변수)의 선언은 같은 이름을 가진 포함 범위의 선언을 가립니다(shadow). 자세한 내용은 Shadowing을 참조하십시오.

 

Local Classes Are Similar To Inner Classes

로컬 클래스는 어떠한 정적 멤버도 정의하거나 선언할 수 없기 때문에 inner 클래스와 유사합니다. static 메소드 내의 로컬 클래스, 예를 들어 static 메소드 validatePhoneNumber 내에 정의된 PhoneNumber 클래스는 PhoneNumber 클래스 를 포함하는 클래스의 static 멤버에만 참조할 수 있습니다. 예를 들어, 멤버 변수 regularExpression을 static으로 정의하지 않으면, 자바 컴파일러는 "static context에서 non-static 변수 regularExpression을 참조할 수 없습니다"와 유사한 오류를 생성합니다.

로컬 클래스는 로컬 클래스를 포함하는 블록의 인스턴스 멤버에 접근할 수 있기 때문에 non-static입니다. 따라서 대부분의 종류의 static 선언을 포함할 수 없습니다.

블록 내에서 인터페이스를 선언할 수 없습니다인터페이스는 본질적으로 static입니다. 예를 들어, 다음 코드 조각은 greetInEnglish 메소드 본문 내에 인터페이스 HelloThere가 정의되어 있기 때문에 컴파일되지 않습니다:

public void greetInEnglish() {
        interface HelloThere {
           public void greet();
        }
        class EnglishHelloThere implements HelloThere {
            public void greet() {
                System.out.println("Hello " + name);
            }
        }
        HelloThere myGreeting = new EnglishHelloThere();
        myGreeting.greet();
    }

 

로컬 클래스 내에서는 static 초기화 블록이나 멤버 인터페이스를 선언할 수 없습니다. 

다음 코드 조각은 EnglishGoodbye.sayGoodbye 메소드가 static으로 선언되어 있기 때문에 컴파일되지 않습니다. 

컴파일러는 이 메소드 정의를 만나면 "수정자 'static'은 상수 변수 선언에서만 허용됩니다"와 유사한 오류를 생성합니다:

public void sayGoodbyeInEnglish() {
    class EnglishGoodbye {
        public static void sayGoodbye() {
            System.out.println("Bye bye");
        }
    }
    EnglishGoodbye.sayGoodbye();
}

로컬 클래스는 상수 변수인 경우에 한해 정적 멤버를 가질 수 있습니다. (상수 변수는 기본 타입이거나 String 타입으로, final로 선언되고 컴파일 타임 상수 표현식으로 초기화된 변수입니다. 컴파일 타임 상수 표현식은 일반적으로 컴파일 시간에 평가될 수 있는 문자열이나 산술 표현식입니다. 자세한 내용은 Understanding Class Members 를 참조하세요.) 다음 코드 조각은 정적 멤버 EnglishGoodbye.farewell이 상수 변수이기 때문에 컴파일됩니다:

public void sayGoodbyeInEnglish() {
    class EnglishGoodbye {
        public static final String farewell = "Bye bye";
        public void sayGoodbye() {
            System.out.println(farewell);
        }
    }
    EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye();
    myEnglishGoodbye.sayGoodbye();
}

 

Anonymous Classes

생략

 

 

Lambda Expressions

생략

 

 

When to Use Nested Classes, Local Classes, Anonymous Classes, and Lambda Expressions

Nested 클래스 섹션에서 언급했듯이,

중첩 클래스는 한 곳에서만 사용되는 클래스를 논리적으로 그룹화하고,

캡슐화의 사용을 증가시키며,

더 읽기 쉽고 유지보수가 용이한 코드를 생성하는 데 도움이 됩니다.

로컬 클래스, 익명 클래스, 람다 표현식도 이러한 장점을 제공하지만, 더 특정한 상황에 사용되도록 의도되어 있습니다:

  • local class : 클래스의 인스턴스를 여러 개 생성해야 하거나, 생성자에 접근해야 하거나, 새로운 이름이 있는 타입을 도입해야 할 경우(예를 들어, 나중에 추가적인 메소드를 호출해야 하는 경우)에 사용하세요.
  • Anonymous class : 필드를 선언하거나 추가 메소드를 정의해야 할 경우에 사용하세요.
  • Lambda expression 1. 다른 코드에 전달하고자 하는 단일 행동 단위를 캡슐화할 경우 사용하세요. 예를 들어, 컬렉션의 각 요소에 대해 특정 작업을 수행하거나, 프로세스가 완료되었을 때, 또는 프로세스가 오류를 만났을 때 람다 표현식을 사용할 수 있습니다.
  • 2. 함수형 인터페이스의 간단한 인스턴스가 필요하고 앞서 언급된 기준들이 해당되지 않는 경우에 사용하세요. 예를 들어, 생성자, 명명된 타입, 필드, 추가 메소드가 필요하지 않은 경우에 해당합니다.
  • Nested class : 요구 사항이 로컬 클래스와 유사하고 타입을 더 넓게 사용하고자 하며, 로컬 변수나 메소드 파라미터에 접근할 필요가 없는 경우에 사용하세요.
  • 1. non-public 필드와 메소드에 접근해야 하는 포함 인스턴스가 필요한 경우 비정적 중첩 클래스(또는 내부 클래스)를 사용하세요. 이러한 접근이 필요 없다면 정적 중첩 클래스를 사용하세요.

외부 클래스의 인스턴스 멤버에 접근할 필요가 없을 때 사용합니다.

public class OuterClass {
    private static String staticValue = "정적 값";

    // 외부 클래스의 비공개 멤버에 접근할 필요가 없음
    static class StaticNestedClass {
        public void showStaticValue() {
            // 외부 클래스의 정적 필드에 접근
            System.out.println("Static Value: " + staticValue);
        }
    }

    public static void main(String[] args) {
        StaticNestedClass nested = new StaticNestedClass();
        nested.showStaticValue();
    }
}

 

외부 클래스의 비공개 멤버에 접근해야 할 때 사용합니다.

public class OuterClass {
    private String secret = "비밀 메시지";

    class InnerClass {
        public void showSecret() {
            // 외부 클래스의 비공개 필드에 접근
            System.out.println("비밀: " + secret);
        }
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        InnerClass inner = outer.new InnerClass();
        inner.showSecret();
    }
}

 

 

 

 

 

 


자바에서 Inner 클래스는 다른 클래스 내에 선언된 클래스를 의미합니다. Inner 클래스는 외부 클래스의 멤버로 간주되며, 외부 클래스의 인스턴스와 직접적으로 상호 작용할 수 있습니다. Inner 클래스는 외부 클래스 내에서만 사용되며, 외부 클래스의 멤버에 대한 접근 권한을 가지고 있습니다. 이러한 특성 때문에 Inner 클래스는 외부 클래스와 강한 결합을 가지며, 외부 클래스의 내부 구현을 보완하거나 보호하는 데 사용될 수 있습니다.

Inner 클래스의 주요 특징은 다음과 같습니다:

1. 내부 클래스의 종류:

  • 인스턴스 내부 클래스 (Instance Inner Class): 외부 클래스의 인스턴스와 관련된 내부 클래스로, 외부 클래스 인스턴스 생성 시 생성됩니다.
  • 정적 내부 클래스 (Static Inner Class): 외부 클래스와 연결되지만, 인스턴스 생성과는 독립적으로 사용할 수 있는 내부 클래스입니다.
  • 지역 내부 클래스 (Local Inner Class): 메서드나 블록 내부에서 선언되는 내부 클래스로, 해당 블록 내에서만 유효합니다.
  • 익명 내부 클래스 (Anonymous Inner Class): 이름이 없는 내부 클래스로, 인터페이스나 추상 클래스를 구현하거나 확장한 클래스를 생성할 때 사용됩니다.

2. 접근성:

  • Inner 클래스는 외부 클래스의 멤버이기 때문에, 외부 클래스의 private 멤버에도 접근할 수 있습니다.
  • 반대로, 외부 클래스는 Inner 클래스의 private 멤버에도 접근할 수 있습니다.

3. 인스턴스 생성:

  •  Inner 클래스의 인스턴스를 생성하기 위해서는 먼저 외부 클래스의 인스턴스를 생성한 후, 그 인스턴스를 사용하여 Inner 클래스의 인스턴스를 생성합니다.
  • Static Inner 클래스의 경우, 외부 클래스의 인스턴스 없이도 직접적으로 인스턴스를 생성할 수 있습니다.

 

인스턴스 내부 클래스 (Instance Inner Class)

public class OuterClass {
    private int outerData;

    public OuterClass(int outerData) {
        this.outerData = outerData;
    }

    public void outerMethod() {
        System.out.println("Outer Method");
    }

    public class InnerClass {
        private int innerData;

        public InnerClass(int innerData) {
            this.innerData = innerData;
        }

        public void innerMethod() {
            System.out.println("Inner Method");
            System.out.println("Outer Data: " + outerData);
            outerMethod();
        }
    }

    public static void main(String[] args) {
        OuterClass outerObj = new OuterClass(10);
        OuterClass.InnerClass innerObj = outerObj.new InnerClass(20);
        innerObj.innerMethod();
    }
}

위의 코드에서 OuterClass는 외부 클래스로, InnerClass는 내부 클래스입니다.

OuterClass에는 outerData라는 private 멤버 변수와 outerMethod()라는 메서드가 있습니다.

InnerClass에는 innerData라는 private 멤버 변수와 innerMethod()라는 메서드가 있습니다.

InnerClass의 innerMethod()에서는 OuterClass의 멤버 변수인 outerData에 접근하고 outerMethod()를 호출합니다. 이렇게 내부 클래스에서 외부 클래스의 멤버 변수와 메서드에 접근할 수 있습니다.

메인 메서드에서는 OuterClass의 인스턴스를 생성한 후에, OuterClass.InnerClass의 인스턴스를 생성합니다. 이렇게 내부 클래스의 인스턴스를 생성하기 위해서는 외부 클래스의 인스턴스가 먼저 필요합니다. 마지막으로 내부 클래스의 인스턴스에서 innerMethod()를 호출하여 결과를 출력합니다.


Inner 클래스는 외부 클래스의 멤버에 접근할 수 있으므로, Inner 클래스의 메서드에서 외부 클래스의 멤버에 접근하거나 수정하는 것도 가능합니다. 이를 통해 내부 클래스는 외부 클래스의 구현을 보완하거나 외부 클래스의 정보에 접근할 수 있습니다.

 

public class OuterClass {
    private int outerData;
    private InnerClass innerObj;

    public OuterClass(int outerData) {
        this.outerData = outerData;
    }

    public void outerMethod() {
        System.out.println("Outer Method");
    }

    public void createInnerObject() {
        innerObj = new InnerClass(20);
        innerObj.innerMethod();
    }

    public class InnerClass {
        private int innerData;

        public InnerClass(int innerData) {
            this.innerData = innerData;
        }

        public void innerMethod() {
            System.out.println("Inner Method");
            System.out.println("Outer Data: " + outerData);
            outerMethod();
        }
    }

    public static void main(String[] args) {
        OuterClass outerObj = new OuterClass(10);
        outerObj.createInnerObject();
    }
}

위의 코드에서 OuterClass에는 innerObj라는 필드가 추가되었습니다. 이 필드는 InnerClass의 객체를 참조하는 역할을 합니다.

createInnerObject() 메서드 내에서는 InnerClass의 객체를 생성하고 innerObj 필드에 대입합니다. 이렇게 외부 클래스의 메서드에서 내부 클래스의 객체를 생성하여 필드에 대입할 수 있습니다.

메인 메서드에서는 OuterClass의 인스턴스를 생성한 후에 createInnerObject()를 호출하여 내부 클래스의 객체를 생성하고 필드에 대입합니다.

이 코드를 실행하면 OuterClass의 createInnerObject() 메서드에서 InnerClass의 객체를 생성하고 해당 객체의 innerMethod()를 호출하여 결과를 출력합니다. 또한, 외부 클래스의 필드인 innerObj를 통해 내부 클래스의 객체에 접근할 수 있습니다.

 

정적 내부 클래스 (Static Inner Class)

정적(Static) 클래스는 다양한 상황에서 다양한 목적으로 사용될 수 있습니다. 주요 목적 및 사용 사례는 다음과 같습니다:

1. 유틸리티 클래스: 정적 클래스는 주로 유틸리티 기능을 제공하는 데 사용됩니다. 예를 들어, Math 클래스는 자바의 내장 정적 클래스로, 수학 연산과 관련된 메서드를 제공합니다. 이러한 유틸리티 기능은 객체 지향 프로그래밍에서 자주 사용되는 기능을 모듈화하고, 코드 재사용성을 높이는 데 유용합니다.

2. 네임스페이스 분리: 정적 클래스는 클래스의 네임스페이스를 확장하여 별도의 이름공간을 제공합니다. 이는 클래스 이름 충돌을 방지하고, 서로 관련된 기능을 그룹화하여 코드를 구조화하는 데 도움을 줍니다. 예를 들어, 여러 유틸리티 클래스를 정적 클래스로 구현하면, 각 클래스의 이름을 분리하여 구분되는 기능을 제공할 수 있습니다.

3. Factory Method: 정적 클래스는 팩토리 메서드 패턴을 구현하는 데 사용될 수 있습니다. 팩토리 메서드 패턴은 객체의 생성을 서브클래스에 위임하는 방식으로, 객체를 생성하기 위한 별도의 클래스나 메서드를 제공합니다. 이때 정적 클래스는 팩토리 메서드를 구현하고, 객체 생성을 담당하는 역할을 수행할 수 있습니다.

4. Helper 클래스: 정적 클래스는 다른 클래스의 보조 역할을 수행하는 Helper 클래스로 사용될 수 있습니다. 이는 주로 객체의 생성, 변환, 유효성 검사 등과 같은 작업을 수행하는 메서드를 정적으로 제공하는 클래스입니다. Helper 클래스를 사용하면 관련된 기능을 한 곳에 모아놓고 재사용성을 높일 수 있습니다.

5. 상수 클래스: 정적 클래스는 상수를 정의하는 데 사용될 수 있습니다. 상수 값들을 정적 필드로 선언하여 다른 클래스에서 사용할 수 있게 하거나, 열거 타입(enum)을 사용하여 상수 값을 그룹화할 수도 있습니다.

정적 클래스는 위와 같은 목적으로 사용되며, 코드의 구조화와 모듈화를 도와주고 재사용성을 높여주는 기능을 제공합니다. 하지만 모든 클래스를 정적 클래스로 정의하는 것은 적절하지 않으며, 사용 시에는 해당 클래스의 목적과 적합성을 고려하여 적절하게 활용하는 것이 중요합니다.

public class OuterClass {
    private static int outerField;

    public static void outerMethod() {
        // Static Inner 클래스의 인스턴스 생성
        InnerClass inner = new InnerClass();
        inner.innerMethod();
    }

    public static class InnerClass {
        private int innerField;

        public void innerMethod() {
            outerField = 10; // 외부 클래스의 정적 멤버 접근
            innerField = 20; // 내부 클래스의 멤버 접근
            System.out.println("Outer Field: " + outerField);
            System.out.println("Inner Field: " + innerField);
        }
    }

    public static void main(String[] args) {
        // Static Inner 클래스의 인스턴스 생성
        OuterClass.InnerClass inner = new OuterClass.InnerClass();
        inner.innerMethod();
    }
}

위의 코드에서 OuterClass는 외부 클래스이고, InnerClass는 정적 내부 클래스입니다. InnerClass는 OuterClass의 멤버로 선언되었으며, outerMethod()에서 InnerClass의 인스턴스를 생성하고 innerMethod()를 호출합니다.

innerMethod()에서는 외부 클래스의 정적 멤버인 outerField와 내부 클래스의 innerField에 접근하여 값을 출력합니다.

정적 내부 클래스는 외부 클래스의 인스턴스에 의존하지 않고 직접적으로 인스턴스를 생성할 수 있습니다. 따라서, main() 메서드에서도 OuterClass.InnerClass의 형태로 정적 내부 클래스의 인스턴스를 생성하고 innerMethod()를 호출할 수 있습니다.

public class OuterClass {
    private static int outerData = 10;
    private int instanceData = 20;

    public static void outerMethod() {
        System.out.println("Outer Method");
    }

    public void instanceMethod() {
        System.out.println("Instance Method");
    }

    public static class StaticInnerClass {
        private int innerData;

        public StaticInnerClass(int innerData) {
            this.innerData = innerData;
        }

        public void innerMethod() {
            System.out.println("Inner Method");
            System.out.println("Outer Data: " + outerData);
            outerMethod();

            // 정적 내부 클래스에서는 외부 클래스의 인스턴스 멤버에 직접 접근할 수 없습니다.
            // System.out.println("Instance Data: " + instanceData); // 컴파일 에러

            // 정적 내부 클래스에서는 외부 클래스의 인스턴스 메서드를 직접 호출할 수 없습니다.
            // instanceMethod(); // 컴파일 에러
        }
    }

    public static void main(String[] args) {
        OuterClass outerObj = new OuterClass();
        OuterClass.StaticInnerClass innerObj = new OuterClass.StaticInnerClass(30);
        innerObj.innerMethod();
    }
}

위의 코드에서 OuterClass는 외부 클래스로, StaticInnerClass는 정적 내부 클래스입니다. OuterClass에는 outerData라는 정적 멤버 변수와 outerMethod()라는 정적 메서드가 있습니다. StaticInnerClass에는 innerData라는 인스턴스 멤버 변수와 innerMethod()라는 메서드가 있습니다.

StaticInnerClass의 innerMethod()에서는 OuterClass의 정적 멤버 변수인 outerData에 접근하고 outerMethod()를 호출합니다. 정적 내부 클래스에서는 외부 클래스의 정적 멤버 변수와 메서드에 접근할 수 있습니다.

메인 메서드에서는 OuterClass의 인스턴스를 생성한 후에, OuterClass.StaticInnerClass의 인스턴스를 생성합니다. 이렇게 정적 내부 클래스의 인스턴스를 생성할 때는 외부 클래스의 인스턴스가 필요하지 않습니다. 마지막으로 정적 내부 클래스의 인스턴스에서 innerMethod()를 호출하여 결과를 출력합니다.

 

 

지역 내부 클래스 (Local Inner Class)

public class OuterClass {
    private int outerField;

    public void outerMethod() {
        int localVariable = 10;

        // 지역 내부 클래스 정의 및 인스턴스 생성
        class LocalInnerClass {
            public void innerMethod() {
                outerField = 20; // 외부 클래스의 멤버 접근
                System.out.println("Outer Field: " + outerField);
                System.out.println("Local Variable: " + localVariable);
            }
        }

        LocalInnerClass inner = new LocalInnerClass();
        inner.innerMethod();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}

위의 코드에서 OuterClass는 외부 클래스이고, LocalInnerClass는 지역 내부 클래스입니다. outerMethod()에서는 LocalInnerClass를 지역 변수로 선언하고, 해당 클래스의 인스턴스를 생성하여 innerMethod()를 호출합니다. innerMethod()에서는 외부 클래스의 멤버인 outerField와 지역 변수인 localVariable에 접근하여 값을 출력합니다.

지역 내부 클래스는 메서드나 블록 내에서 선언되는 클래스로, 선언된 블록 내에서만 유효합니다. 따라서, outerMethod() 내에서만 LocalInnerClass의 인스턴스를 생성하고 사용할 수 있습니다. 이를 통해 지역 내부 클래스는 외부 메서드의 지역 변수에 접근하거나 메서드의 동작을 보완하는 용도로 사용될 수 있습니다.

 

 

로컬 내부 클래스(Local Inner Class)는 메서드 내부에 정의되는 내부 클래스로, 특정 메서드에서만 사용할 수 있습니다. 로컬 내부 클래스는 다음과 같은 경우에 사용될 수 있습니다:

1. 캡슐화와 정보 은닉: 메서드 내에서 로컬 내부 클래스를 정의하면, 해당 클래스는 메서드 내부에서만 접근 가능합니다. 이로써 클래스의 범위를 제한하여 정보 은닉과 캡슐화를 달성할 수 있습니다. 메서드 외부에서는 로컬 내부 클래스에 직접 접근할 수 없으므로, 클래스의 내부 구현을 감추고 메서드의 로직을 단순화할 수 있습니다.

2. 메서드 지역 변수와의 상호작용: 로컬 내부 클래스는 정의된 메서드 내부에서만 접근 가능한 로컬 변수에 쉽게 접근할 수 있습니다. 이를 통해 메서드 내부에서 사용되는 데이터에 접근하고 조작하는 용도로 로컬 내부 클래스를 활용할 수 있습니다.

3. 콜백 구현: 로컬 내부 클래스는 이벤트 처리나 콜백 구현에 활용될 수 있습니다. 메서드 내에서 인터페이스를 구현하는 로컬 내부 클래스를 정의하여 이벤트 핸들링 등의 작업을 수행할 수 있습니다.

로컬 내부 클래스는 메서드 내에서 선언되고 사용되므로, 해당 메서드가 호출되기 전까지는 로컬 내부 클래스에 대한 인스턴스를 생성할 수 없습니다. 로컬 내부 클래스는 메서드 내에서 지역 변수처럼 동작하며, 메서드의 실행이 끝나면 로컬 내부 클래스의 인스턴스도 사라지게 됩니다.

로컬 내부 클래스는 클래스의 범위를 제한하고 캡슐화를 통해 코드의 가독성과 유지보수성을 높일 수 있는 유용한 기능입니다. 하지만 필요에 따라 적절하게 사용해야 하며, 클래스의 범위를 제한하는 것이 코드의 명확성과 유연성을 개선하는 데 도움이 되는지 신중하게 고려해야 합니다.

 

 

익명 내부 클래스 (Anonymous Inner Class)

자바에서 익명 내부 클래스(Anonymous Inner Class)는 이름 없이 선언과 동시에 객체를 생성하는 클래스입니다. 이 클래스들은 일회성으로 사용되거나 간단한 구현을 위해 주로 사용됩니다. 익명 내부 클래스는 인터페이스나 다른 클래스의 확장으로 즉시 구현될 수 있습니다.

아래에는 자바의 익명 내부 클래스 사용 예제를 보여드리겠습니다. 이 예제에서는 `Runnable` 인터페이스를 구현하는 익명 내부 클래스를 생성합니다. `Runnable`은 단일 메소드 `run`을 가진 함수형 인터페이스이므로, 익명 클래스를 사용하여 간단하게 구현할 수 있습니다.

예제 코드:

public class AnonymousInnerClassExample {
    public static void main(String[] args) {
        // Runnable 인터페이스를 구현하는 익명 내부 클래스의 인스턴스 생성
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("익명 내부 클래스의 run 메소드 실행");
            }
        };

        // 쓰레드 생성 및 실행
        Thread t = new Thread(r);
        t.start();
    }
}



이 코드에서:

1. Runnable 인터페이스의 익명 내부 클래스 인스턴스를 생성합니다. run 메소드는 익명 내부 클래스의 run 메소드 실행이라는 메시지를 출력합니다.

2. 생성된 익명 내부 클래스 인스턴스 r Thread 객체에 전달합니다.

3. 쓰레드 `t`를 시작하여 run 메소드를 실행합니다.

익명 내부 클래스의 특징:

  • 일회성 사용: 익명 내부 클래스는 주로 일회성으로 사용되며, 다시는 재사용되지 않습니다.
  • 코드의 간결함: 작은 기능 변경이나 간단한 구현을 위해 사용되어 코드를 간결하게 만들 수 있습니다.
  • 스코프 제한: 익명 내부 클래스는 선언된 스코프 내에서만 사용됩니다.
  • 자원 접근: 외부 클래스의 멤버에 접근할 수 있으며, final 또는 사실상 final인 변수에 접근할 수 있습니다.

익명 내부 클래스는 주로 GUI 이벤트 처리, 스레드 객체 생성, 간단한 인터페이스 구현 등에서 유용하게 사용됩니다. 그러나 클래스가 너무 복잡해지거나 코드의 재사용성이 중요한 경우에는 별도의 클래스나 람다 표현식을 사용하는 것이 더 바람직할 수 있습니다.

'Java' 카테고리의 다른 글

try-catch-finally  (0) 2024.04.08
Anonymous Class  (0) 2024.04.08
Method Class  (0) 2024.04.08
Java Collection Framework  (0) 2024.04.08
Lambda Expressions  (0) 2024.04.08