[Java] 중첩 클래스와 내부 클래스_1

szlee·2024년 8월 20일
0

Java

목록 보기
29/34

< 김영한의 실전 자바 - 중급 1편 > 강의를 보고 이해한 내용을 바탕으로 합니다.





중첩 클래스와 내부 클래스

  • 정적 중첩 클래스 : static 붙음. 정적 변수와 같은 위치에 선언
  • 내부 클래스
    • 내부 클래스 : 인스턴스 변수와 같은 위치에 선언
    • 지역 클래스 : 지역 변수와 같은 위치에 선언
    • 익명 클래스 : 지역 클래스의 특별한 버전.
class Outer {
   ...
   //정적 중첩 클래스
   static class StaticNested {
   ...
   }
   
   //내부 클래스
   class Inner {
   ...
   }
}
class Outer {
   public void process() {
   
   //지역 변수
   int lcoalVar = 0;
   
   //지역 클래스
   class Local {...}
   
   Local local = new Local();
   }
}

중첩과 내부의 차이?

핵심 : 바깥 클래스 입장에서 안에 있는 클래스가 나의 인스턴스에 소속이 되는가 되지 않는가.

  • 중첩 : 바깥 클래스의 안에 있지만 바깥 클래스와 관계가 없는 전혀 다른 클래스.
  • 내부 : 바깥 클래스의 내부에 있으면서 바깥 클래스를 구성하는 요소. 바깥 클래스의 인스턴스에 소속됨.

정적 중첩 클래스

  • static이 붙는다.
  • 바깥 클래스의 인스턴스에 소속되지 않는다.

내부 클래스

  • static 붙지 않는다.
  • 바깥 클래스의 인스턴스에 소속된다.

내부 클래스 종류

  • 내부 클래스 : 바깥 클래스의 인스턴스의 멤버에 접근
  • 지역 클래스 : 내부 클래스의 특징 + 지역 변수에 접근
  • 익명 클래스 : 지역 클래스의 특징 + 클래스의 이름이 없는 특별한 클래스

중첩 클래스 사용하는 곳?

내부 클래스를 포함한 모든 중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나 둘이 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용해야 한다.

중첩 클래스 사용 이유?

  • 논리적 그룹화 : 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화된다. 패키지를 열었을 때 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않도록 하는 장점도 있다.
  • 캡슐화 : 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있다. 이렇게 둘을 긴밀하게 연결하고 불필요한 public 메서드를 제거할 수 있다.



정적 중첩 클래스

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);
            
   			// 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
   			//System.out.println(outInstanceValue);
            
   			// 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
   			System.out.println(NestedOuter.outClassValue);
   		}
   }
}

정적 중첩 클래스는 앞에 static이 붙는다.
정적 중첩 클래스는

  • 자신의 멤버에는 당연히 접근할 수 있다.
  • 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
  • 바깥 클래스의 클래스 멤버에는 접근할 수 있다.

private 접근 제어자

  • private 접근 제어자는 같은 클래스 안에 있을 때만 접근할 수 있다.
  • 중첩 클래스도 바깥 클래스와 같은 클래스 안에 있으므로 바깥 클래스의 private 접근 제어자에 접근할 수 있다.
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()로 만든 정적 중첩 클래스의 인스턴스는 서로 아무런 관계가 없다. (단지 클래스 구조상 중첩. 아무런 관련이 없으므로 정적 중첩 클래스의 인스턴스만 따로 생성할 수도 있다.)

정적 중첩 클래스 활용

public class NetworkMessage {
 	private String content;
    
 	public NetworkMessage(String content) {
 		this.content = content;
 	}
    
 	public void print() {
 		System.out.println(content);
 	}
}

NetworkMessage는 Network 객체 안에서만 사용되는 객체이다.

public class Network {
 	public void sendMessage(String text) {
 		NetworkMessage networkMessage = new NetworkMessage(text);
 		networkMessage.print();
 	}
}

Network는 text를 입력받아서 NetworkMessage를 생성하고 출력하는 단순한 기능을 제공한다.

public class NetworkMain {
 	public static void main(String[] args) {
 	Network network = new Network();
 	network.sendMessage("hello java");
 	}
}

Network를 생성하고 network.sendMessage()를 통해 메시지를 전달한다.
NetworkMain은 오직 Network 클래스만 사용하고, NetworkMessage 클래스는 전혀 사용하지 않는다.
NetworkMessage는 오직 Network 내부에서만 사용된다.

하지만 패키지를 열어보면 Network, NetworkMessage 두 클래스가 보일 것이고 여기를 처음 확인한 개발자는 둘 다 사용해야 하는가 라고 생각할 것이다.
아니면 NetworkMessage가 다른 여러 클래스에서 사용되는구나 라고 생각할 것이다.
두 클래스의 코드를 모두 확인하고 나서야 알게될 것이다.

정적 중첩 클래스로 리팩토링

public class Network {
 	public void sendMessage(String text) {
 		NetworkMessage networkMessage = new NetworkMessage(text);
 		networkMessage.print();
 	}
    
    private static class NetworkMessage {
      private String content;

      public NetworkMessage(String content) {
          this.content = content;
      }

      public void print() {
          System.out.println(content);
      }
	}
}
  • NetworkMessage 클래스를 Network 클래스 안에 중첩해서 만들었다.
  • NetworkMessage의 접근 제어자를 private 설정했다. --> 외부에서 접근할 수 없다.

이제 개발자는 패키지에 Network 클래스만 확인해볼 것이고, NetworkMessage가 중첩 클래스에 private 접근 제어자로 되어 있는 것을 보고 Network 내부에서만 단독으로 사용하는 클래스라고 바로 알 수 있다.

중첩 클래스의 접근

나의 클래스에 포함된 중첩 클래스가 아니라 다른 곳에 있는 중첩 클래스에 접근할 때는 바깥클래스.중첩클래스로 접근해야한다.
NestedOuter.Nested nested = new NestedOuter.Nested()
(나의 클래스에 포함된 중첩 클래스 접근 시에는 바깥 클래스 이름을 안적어도 된다.)
그런데, 중첩 및 내부 클래스의 용도는 자신이 소속된 바깥 클래스의 안에서만 사용되는 것이므로, 외부에서 생성하고 사용하고 있다면 용도에 맞지 않을 수 있으므로 중첩 클래스를 밖으로 빼는 것이 더 낫다.



내부 클래스

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

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 접근 제어자

private 접근 제어자는 같은 클래스 안에 있을 때만 접근할 수 있다.
내부 클래스도 바깥 클래스와 같은 클래스 안에 있으므로 바깥 클래스의 private 접근 제어자에 접근할 수 있다.

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는 바깥 클래스의 인스턴스 참조를 가진다.
  • outer.new Inner()로 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성된다. => 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스를 생성할 수 있다.

    바깥 클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성된다.
    따라서 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스의 멤버에 접근할 수 있다.
    하지만 실제로 내부 인스턴스가 바깥 인스턴스 안에 생성되는 것은 아니다!
    내부 인스턴스는 바깥 인스턴스의 참조를 보관한다. 이 참조를 통해 바깥 인스턴스의 멤버에 접근할 수 있다.
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); //Car의 인스턴스에 직접 접근할 수 있다.
 			System.out.println(model + "의 엔진을 구동합니다."); //Car의 인스턴스에 직접 접근할 수 있다.
 		}
 	}
}

같은 이름의 바깥 변수 접근

바깥 클래스의 인스턴스 변수 이름과 내부 클래스의 인스턴스 변수 이름이 같다면?

public class ShadowingMain {
 	public int value = 1; //이거랑
    
 	class Inner {
 		public int value = 2; //이거 변수명이 같다
        
 		void go() {
 			int value = 3;
 			System.out.println("value = " + value); //3
 			System.out.println("this.value = " + this.value); //2
 			System.out.println("ShadowingMain.value = " + ShadowingMain.this.value); //1
 		}
 	}
    
 	public static void main(String[] args) {
 		ShadowingMain main = new ShadowingMain();
 		Inner inner = main.new Inner();
 		inner.go();
 	}
}

메서드 go()의 경우 지역 변수인 value가 가장 가까우므로 우선순위가 가장 높다.
this.value는 내부 클래스의 인스턴스에 접근하고, 바깥클래스이름.this는 바깥 클래스의 인스턴스에 접근할 수 있다.
=> 더 가깝거나 구체적인 것이 우선권을 가진다. 그러나 처음부터 이름을 서로 다르게 지어서 명확하게 구분하는게 좋다.

profile
🌱

0개의 댓글