중첩클래스

이동건 (불꽃냥펀치)·2024년 11월 19일
0

중첩 클래스, 내부 클래스의 의미

  • 중첩 클래스: 클래스 안에 클래스를 중첩해서 쓸 수 있는데 이를 중첩 클래스라한다.
  • 중첩 클래스의 종류
    • 정적 중첩 클래스
    • 내부 클래스 종류
      • 내부 클래스
      • 지역 클래스
      • 익명 클래스
    • 중첩 vs 내부
      • 중첩: 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계
      • 내부: 내부에서 나를 구성하는 요소이다.
    • 클래스의 범위
      • 정적 중첩 클래스: static이 붙어 바깥 클래스의 인스턴스에 소속되지 않는다.
      • 내부 클래스:static이 붙지않아 바깥 클래스의 인스턴스에 소속된다.

중첩 클래스를 사용하는 때와 이유

  • 사용 시기: 내부 클래스를 포함한 모든 중첩 클래스는 특정 클래스가 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용해야 한다.
  • 사용이유
    • 내부 클래스를 포함한 모든 중첩 클래스는 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스안에 포함되는 것이 논리적으로 더 그룹화 될 뿐만이 아니라 중첩 클래스가 외부에 노출되지 않는 장점도 있다.
    • 캡슐화: 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거할 수 있다.

정적 중첩 클래스

package nested.nested;
 public class NestedOuter {
     private static int outClassValue = 3;
     private int outInstanceValue = 2;
     
     static class Nested {
     
     private int nestedInstanceValue = 1;
	 public void print() { // 자신의 멤버에 접근
		System.out.println(nestedInstanceValue); 
    // 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.

   // 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
        System.out.println(NestedOuter.outClassValue);
        
        }
    } 
}
  • 정적 중첩 클래스는 앞에 static이 붙는다.
    • 정척 중첩 클래스는 자신의 멤버변수에 접근이 가능하다.
    • 바깥 클래스의 인스턴스 멤버에는 접근이 불가능하다.
    • 바깥 클래스의 클래스 멤버에는 접근이 가능하다.
  • private 접근 제어자는 같은 클래스 안에 있을 때만 접근 할 수 있다.
  • 중첩 클래스도 바깥 클래스와 같은 클래스 안에 있다. 따라서 중첩 클래스는 바깥 클래스의 private 접근제어자에 접근 할 수 있다.
package nested.nested;
 public class NestedOuterMain {
     public static void main(String[] args) {
         
         NestedOuter outer = new NestedOuter();
         NestedOuter.Nested nested = new NestedOuter.Nested();
         nested.print();
         System.out.println("nestedClass = " + nested.getClass());
         
    }
}
  • 정적 중첩 클래스는 new 바깥클래스.중첩클래스()로 생성이 가능하다.
  • 중첩 클래스는 NestedOuter.Nested와 같이 접근했다.
  • 여기서 new NestedOuter()로 만든 바깥 클래스의 인스턴스와 new NestedOuter.Nested()로 만든 정적 중첩 클래스의 인스턴스는 서로 아무 관계가 없는 인스턴스이다.
  • 정적 중첩 클래스는 바깥 클래스의 정적 필드에는 접근이 가능하지만, 바깥 인스턴스의 참조가 없어 바깥 클래스가 만든 인스턴스 필드에는 접근 할 수 없다.



내부 클래스

정적 중첩 클래스는 바깥 클래스와 서로 관계가 없다. 하지만 내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소가 되어 내부 클래스는 바깥 클래스의 인스턴스에 소속된다.

 package nested.inner;
 public class InnerOuter {
 
     private static int outClassValue = 3;
     private int outInstanceValue = 2;
     
     class Inner {
        
        private int innerInstanceValue = 1;
		
        public void print() {
        // 자신의 멤버에 접근 System.out.println(innerInstanceValue);
        // 외부 클래스의 인스턴스 멤버에 접근 가능, private도 접근 가능 				
        System.out.println(outInstanceValue);
        // 외부 클래스의 클래스 멤버에는 접근 가능. private도 접근 가능 
        System.out.println(InnerOuter.outClassValue);
        
		} 
	}
}
  • 내부 클래스 앞에는 static이 붙지 않아 인스턴스 멤버가 된다.
  • 내부 클래스는 자신의 멤버에 접근 가능하다.
  • 바깥 클래스의 인스턴스 멤버에 접근 가능하다.
  • 바깥 클래스의 클래스 멤버에 접근 가능하다.
  • 바깥 클래스의 private 접근제어자에도 접근 가능하다.
 package nested.inner;
 public class InnerOuterMain {
     public static void main(String[] args) {
     
         InnerOuter outer = new InnerOuter();
         InnerOuter.Inner inner = outer.new Inner();
         inner.print();
         System.out.println("innerClass = " + inner.getClass());
         
     }
}
  • 내부 클래스는 바깥 클래스의 인스턴스에 소속되어, 바깥 클래스의 인스턴스 정보를 알아야 생성 할 수 있다.
  • 내부 클래스는 바깥 클래스의 인스턴스 참조.new 내부클래스()로 생성 할 수 있다.
    • outer.new Inner()에서 outer는 바깥 클래스의 인스턴스 참조값을 가진다.

  • 개념상 바깥 클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성된다.
  • 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스의 멤버에 접근 할 수 있다.

내부 클래스의 활용

내부클래스 활용 전

package nested.inner.ex1; //Car에서만 사용
 public class Engine {
     private Car car;
     public Engine(Car car) {
         this.car = car;
	}
    
	public void start() {
		System.out.println("충전 레벨 확인: " + car.getChargeLevel()); 	
        System.out.println(car.getModel() + "의 엔진을 구동합니다.");
   } 
}


package nested.inner.ex1;

public class Car {
    private String model;
    private int chargeLevel;
    private Engine engine;
    public Car(String model, int chargeLevel) {
    this.model = model;
    this.chargeLevel = chargeLevel;
    this.engine = new Engine(this);
    }
//Engine에서만 사용하는 메서드 
public String getModel() {
        return model;
    }
//Engine에서만 사용하는 메서드 
public int getChargeLevel() {
        return chargeLevel;
    }
    public void start() {
        engine.start();
		System.out.println(model + " 시작 완료"); }
   }
}

내부 클래스 활용 후

package nested.inner.ex2;
 public class Car {
     private String model;
     private int chargeLevel;
     private Engine engine;
     public Car(String model, int chargeLevel) {
         this.model = model;
         this.chargeLevel = chargeLevel;
         this.engine = new Engine();
	}
     public void start() {
        engine.start();
		System.out.println(model + " 시작 완료"); 
        }
    
    private class Engine {
    
     	public void start() {
              System.out.println("충전 레벨 확인: " + chargeLevel);
              System.out.println(model + "의 엔진을 구동합니다."); }
          }		
	}
 }
  • 내부 클래스 활영 후
    • Car 클래스는 엔진에 필요한 메서드들을 제공하지만, 엔진에서만 사용하고 다른 곳에서는 사용하지 않음
    • 내부 클래스를 활용해 모델 이름과 충전 레벨을 get방식으로 외부에 노출되는 것을 막음.
    • 이는 불필요한 Car클래스의 정보들이 외부에 노출되는 것을 막아 캡슐화를 진행한다고 볼 수 있다.
  • Engine.start()
    • Car의 인스턴스 변수의 chargeLevel에 직접 접근 할 수 있다.
    • Car의 인스턴스 변수인 model에 직접 접근 할 수 있다.




지역 클래스

지역 클래스의 특징

  • 지역 클래스는 내부 클래스의 종류 중 하나로 내부 클래스의 특징을 그대로 가진다.
  • 지역 클래스는 지역 변수와 같이 코드 블럭 안에서 정의된다.
class Outer {
	public void process() { 
    		//지역 변수
		 int localVar = 0; 
         //지역 클래스
         class Local {...}
         Local local = new Local();
	}
}
  • 지역 클래스는 지역 변수처럼 코드 블럭안에 클래스를 선언한다.
  • 지역 클래스는 지역 변수에 접근이 가능하다.

지역 클래스의 활용

내부 클래스를 포함한 중첩 클래스들도 일반 클래스처럼 인터레이스를 구현하거나 부모 클래스를 상속 할 수 있다.

 package nested.local;
 public interface Printer {
     void print();
}

package nested.local;
 public class LocalOuterV2 {
     private int outInstanceVar = 3;
     public void process(int paramVar) {
     int localVar = 1;
     
     class LocalPrinter implements Printer {
			 int value = 0;
             
             @Override
             public void print() {
				 System.out.println("value=" + value);
                 System.out.println("localVar=" + localVar);
                 System.out.println("paramVar=" + paramVar);
                 System.out.println("outInstanceVar=" + outInstanceVar);
			} 
  	 }
     Printer printer = new LocalPrinter();
     printer.print();
     }
     
     public static void main(String[] args) {
         LocalOuterV2 localOuter = new LocalOuterV2();
         localOuter.process(2);
	} 
}

지역 변수 캡처

* 변수의 생명 주기

  • 클래스 변수: 프로그램 종료까지 가장 길다.
    • 클래스변수(static 변수)는 메서드영역에 존재하고 자바가 클래스 정보를 읽어 들이는 순간부터 프로그램 종료까지 존재한다.
  • 인스턴스 변수: 힙영역
  • 인스턴스 변수는 본인이 소속된 인스턴스가 GC되기 전까지 존재한다.
  • 지역변수: 메서드 호출이 끝나면 사라진다(스택영역).
    • 지역 변수는 스택영영의 스택 프레임안에 존재한다. 따라서 메서드가 호출되면 생성되고 메서드 호출이 종료되면 스택 프레임이 제거되며 그 안에 있는 지역 변수도 모두 제거된다.
package nested.local;
 public class LocalOuterV3 {
 
     private int outInstanceVar = 3;
     
     public Printer process(int paramVar) {
     
		int localVar = 1; 
		//지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.
		class LocalPrinter implements Printer {
			   int value = 0;
               
               @Override
              public void print() {
              		System.out.println("value=" + value);
					//인스턴스는 지역 변수보다 더 오래 살아남는다. 
					System.out.println("localVar=" + localVar); 
                    System.out.println("paramVar=" + paramVar);       
                    System.out.println("outInstanceVar=" + outInstanceVar);
             }
		}
        
		Printer printer = new LocalPrinter();
		//printer.print()를 여기서 실행하지 않고 Printer 인스턴스만 반환한다. 
        return printer;
	}
    
	public static void main(String[] args) {
		LocalOuterV3 localOuter = new LocalOuterV3();
		Printer printer = localOuter.process(2);
		//printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행 
        printer.print();
	} 
}
  • 지역 클래스로 만든 객체도 인스턴스이므로 힙영역에 존재한다. 따라서 GC 전까지 생존한다.
    • LocalPrinter인스턴스는 process() 메서드 안에서 생성된다. 그리고 process() 에서 main()으로 생성한 LocalPrinter인스턴스를 반환하고 변수에 참조를 보관한다.
    • LocalPrinter인스턴스는 main()이 종료될 때 까지 생존한다.
  • 지역변수는 메서드를 실행하는 동안에만 스택 영역에서 생존한다. process()메서드가 종료되면 스택프레임이 제거되면서 사라진다.
  • 여기서 문제는 process()메서드가 종료되어도 LocalPrinter인스턴스는 계속 생존한다는 점이다.
  • 위의 코드를 보면 process()가 종료된 후에 main()메서드 안에서 LocalPrinter.print() 메서드를 호출한다.
  • LocalPrinter인스턴스에 있는 print()메서드는 지역변수에 접근해야 하지만 메서드가 종료되어 해당 지역 변수들도 이미 제거된 상태이다.
  • 하지만 그럼에도 실행을 해보면 인스턴스가 정상적으로 지역변수 값을 보고 값을 출력한다.

지역 변수 캡처 원리

앞서 예제에서 본 인스턴스가 제거되는 시기와 지역변수가 제거되는 시기가 맞지않아 문제가 생길 수 있는데 이를 막기 위해 지역 변수 캡처가 실행된다.

  • LocalPrinter 인스턴스 생성 시도: 지역 클래스의 인스턴스를 생성 할 때 지역 클래스가 접근하는 지역 변수를 확인한다.
  • 사용하는 지역 변수 복사: 지역 클래스가 사용하는 지역 변수를 복사한다.
  • 지역 변수 복사 완료: 복사한 지역 변수를 인스턴스에 포함한다.
  • 인스턴스 생성 완료: 복사한 지역 변수를 포함해서 인스턴스 생성이 완료된다, 이제 복사한 지역변수를 인스턴스를 통해 접근할 수 있다.

지역 변수 캡처 주의점

캡처 변수의 값을 변경하면 안된다

  • 지역 변수의 값을 변경하면 인스턴스에 캡처한 변수의 값도 변경해야 한다.
  • 반대로 인스턴스에 있는 캡ㅊ 변수의 값을 변경하면 해당 지역 변수의 값도 다시 변경해야 한다.
  • 개발자 입장에서 보면 예상치 못한 곳에서 값이 변동될 수 있어 디버깅을 어렵게한다..

    .

익명 클래스

package nested.local;
 public class LocalOuterV2 {
     private int outInstanceVar = 3;
     
     public void process(int paramVar) {
         int localVar = 1;
         
         Printer printer = new Printer(){
			 int value = 0;
             @Override
             public void print() {
                 System.out.println("value=" + value);
                 System.out.println("localVar=" + localVar);
                 System.out.println("paramVar=" + paramVar);
                 System.out.println("outInstanceVar=" + outInstanceVar);
			} 
		}
         Printer printer = new LocalPrinter();
         printer.print();
     }
     
     public static void main(String[] args) {
         LocalOuterV2 localOuter = new LocalOuterV2();
         localOuter.process(2);
	} 
}

new Printer(){body}
익명 클래스는 클래스의 본문을 정의하면서 동시에 생성한다. 이 코드는 마치 인터페이스 Printer를 생성 한 것처럼 보인다. 하지만 자바에서는 인터페이스를 생성하는 것이 불가능 해, Printer라는 이름의 인터페이스를 구현한 익명클래스를 생성하는 것이다. 쉡게 말해 Printer 인터페이스를 상속(구현)하면서 바로 생성하는 것이다.


익명 클래스의 특징

  • 익명 클래스는 이름 없는 지역 클래스를 선언하면서 동시에 생성한다.
  • 익명 클래스는 부모 클래스를 상속 받거나 또는 인터페이스를 구현해야 한다.

익명 클래스의 장점
익명 클래스를 사용하면 클래스를 별도로 정의하지 않고도 인터페이스나 추상 클래스를 즉석에서 구현할 수 있어 코드 가 더 간결해진다. 하지만, 복잡하거나 재사용이 필요한 경우에는 별도의 클래스를 정의하는 것이 좋다.








출처: https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-1/dashboard

profile
자바를 사랑합니다

0개의 댓글

관련 채용 정보