본문 바로가기

Programming/Java

[Java] 김영한의 자바 중급 1편 #7 - 중첩 클래스, 내부 클래스 - 1

반응형

김영한 선생님의 자바 중급을 듣고 정리한 내용입니다.

 

 


 

1. 중첩 클래스, 내부 클래스 1

다음과 같이 클래스 안에 클래스를 중첩해서 정의할 수 있는데,

이것을 **중첩 클래스(Nested Class)**라 한다.

class Outer {
	...
	
	//중첩 클래스
	class Nested {
	...
	
	}
}

1.1. 중첩 클래스의 분류

 

 

  • 정적 중첩 클래스
  • 내부 클래스
    • 내부 클래스
    • 지역 클래스
    • 익명 클래스

중첩 클래스의 선언 위치

  • 정적 중첩 클래스 → 정적 변수와 같은 위치
  • 내부 클래스 → 인스턴스 변수와 같은 위치
  • 지역 클래스 → 지역 변수와 같은 위치
class Outer {
	...
	// 정적 중첩 클래스 : 정적 변수와 같이 앞에 static이 붙어 있다.
	static class StaticNested {
		...
	}
	
	// 내부 클래스 : 인스턴스 변수와 같이 앞에 static이 붙어있지 않다.
	class Inner {
		...
	}
}
class Outer {

	public void process() {
		// 지역 변수
		int localVar = 0;
		
		// 지역 클래스
		class Local {
			...
		}
				
		Local local = new Local();
	}
	
}

 

중첩과 내부라는 단어는 무슨 차이가 있는가?

  • 중첩(Nested) : 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계
  • 내부(Inner) : 나의 내부에 있는 나를 구성하는 요소

 

용어 정리

  • 중첩 클래스 : 정적 중첩 클래스 + 내부 클래스 종류 모두 포함
  • 정적 중첩 클래스 : 정적 중첩 클래스를 말함
  • 내부 클래스 : 내부 클래스, 지역 클래스, 익명 클래스를 포함해서 말함

 

중첩 클래스는 언제 사용하나요?

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

 

외부의 여러 클래스가 특정 중첩 클래스를 사용한다면 중첩 클래스로 만들면 안된다.

 

 

장점

  • 논리적 그룹화
  • 캡슐화

 

1.1. 정적 중첩 클래스

package nested;

public class NestedOuter {

    private static int outClassValue = 3;
    private int outInstanceValue = 2;

    static class Nested {

        private int nestedInstanceValue = 1;

        public void print() {

            // 1. 자신의 멤버에 접근한다.
            System.out.println("nestedInstanceValue : " + nestedInstanceValue);

            // 2. 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
            // System.out.println(outInstanceValue);

            // 3. 바깥 클래스의 클래스 멤버에는 접근할 수 있다.
            //    private도 접근 가능
            System.out.println("NestedOuter.outClassValue : " + outClassValue);
        }
    }
}

  • 정적 중첩 클래스 앞에 static이 붙는다.
  • 정적 중첩 클래스의 특징으로는
    • 자신의 멤버에는 당연히 접근할 수 있다.
    • 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
    • 바깥 클래스의 클래스 멤버에는 접근할 수 있다.
    • 중첩 클래스도 바깥 클래스와 같은 클래스 안에 있다. → 따라서 중첩 클래스는 바깥 클래스의 private 접근 제어자에 접근할 수 있다.

 

  • 정적 중첩 클래스는 new 바깥클래스.중첩클래스()로 생성할 수 있다.
  • 참고로, new NestedOuter()로 바깥 클래스의 인스턴스를 만드는 경우와 new NestedOuter.Nested()로 정적 중첩 클래스를 만드는 경우, → 서로 아무 관계가 없다. 단지 클래스를 구조상 중첩해 두었을 뿐이다.

 

 

바깥 클래스의 멤버에 접근

 

 

            // 2. 바깥 클래스의 인스턴스 멤버에는 접근할 수 없다.
            // System.out.println(outInstanceValue);

→ 정접 중첩 클래스는 바깥 클래스의 정적 필드(클래스 멤버)에는 접근할 수 있다.

→ 하지만 바깥 클래스가 만든 인스턴스 필스에는 바로 접근할 수 없다. 왜냐하면 바깥 인스턴스의 참조값이 없기 때문이다.

 

1.2. 정적 중첩 클래스의 활용

1.2.1. 리팩토링 전

 

 

NetworkMain 클래스에서는 Network 클래스만 사용된다.

그리고 NetworkMessage 클래스는 단순히 Network 안에서만 사용된다.

 

 

1.2.2. 정적 중첩 클래스로 리팩터링

 

 

1.3. 중첩 클래스의 접근

나의 클래스에 포함된 중첩 클래스가 아니라 다른 곳에 있는 중첩 클래스에 접근할 때는 바깥클래스.중첩클래스 로 접근해야 한다.

NestedOuter.Nested nested = new NestedOuter.Nested();

나의 클래스에 포함된 중첩 클래스에 접근할 때는 바깥 클래스 이름을 적지 않아도 된다.

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

 

 


2. 내부 클래스

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

 

 

  • 내부 클래스는 바깥 클래스의 인스턴스에 소속된다. → 따라서 바깥 클래스의 인스턴스 정보를 알아야 생성할 수 있다.
  • 클래스는 바깥 클래스의 인스턴스 참조.new 내부클래스() 로 생성할 수 있다. → 따라서 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스를 생성할 수 있다.

개념 - 내부 클래스의 생성

 

 

실제 - 내부 클래스의 생성

 

 

  • 실제로 내부 인스턴스가 바깥 인스턴스 안에 생성되는 것은 아님. 하지만 개념상 인스턴스 안에 생성된다고 이해하면 충분하다.
  • 실제로는 내부 인스턴스는 바깥 인스턴스의 참조를 보관한다. 이 참조를 통해 바깥 인스턴스의 멤버에 접근할 수 있다.

 

2.1. 내부 클래스의 활용

2.1.1. 내부 클래스로 리팩터링 전

 

  • 엔진은 Car 클래스에서만 사용된다.
  • 엔진을 시작하기 위해서는 차의 충전 레벨과 차량의 이름이 필요하다.
    • Car 인스턴스의 참조를 생성자에서 보관한다.
    • 엔진은 충전 레벨을 확인하기 위해 Car.getChargeLevel() 메서드가 필요하다.
    • 엔진은 차량의 이름을 확인하기 위해 Car.getModel() 메서드가 필요하다.
  • Car 클래스는 엔진에 필요한 메서드들을 제공해야 한다. (다른 곳에서는 사용되지 않는다.)
    • getModel()
    • getChargeLevel()

결과적으로 Car 클래스는 엔진에서만 사용하는 기능을 위해 메서드를 추가해서, 모델 이름과 충전 레벨을 외부에 노출해야 한다.

 

2.1.2. 내부 클래스로 리팩터링 후

 

 

  • 엔진을 내부 클래스로 만들었다.
    • Car의 인스턴스 변수인 chargeLevel에 직접 접근할 수 있다.
    • Car의 인스턴스 변수인 model에 직접 접근할 수 있다.
  • 내부 클래스 생성 방법
    • 바깥 클래스에서 내부 클래스의 인스턴스를 생성할 때 바깥 클래스의 이름을 생략할 수 있다.
      • new Engine()

 

2.1.3. 리팩터링 후기

리팩터링 전에는 모델 이름과 충전 레벨을 외부에 노출했다. → Car 클래스의 정보들이 불필요하게 노출되며 캡슐화를 떨어뜨린다.

 

리팩터링 후에는 getModel(), getChargeLevel()과 같은 메서드들을 모두 제거할 수 있게 되었다.

 

결과적으로 꼭 필요한 메서드만 외부에 노출함으로써 Car 캡슐화를 더 높일 수 있었다.

 

 

2.1.4. 중첩 클래스 사용하는 이유

중첩 클래스는 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있을 때 사용한다.

외부 여러곳에서 특정 클래스를 사용한다면 중첩 클래스로 사용하면 안된다.

 

반응형