javaboiii의 JAVA/JAVA 요약정리(멘토씨리즈)

JAVA -13) 내부 클래스

javaboiii 2024. 7. 24. 20:17

JAVA

1. 내부 클래스

내부 클래스는 클래스 안에 만들어진 또 다른 클래스로 중첩 클래스라고도 부릅니다. 클래스에 다른 클래스를 선언하는

이유는 두 개의 클래스가 서로 긴밀한 관계를 맺고 있기 때문입니다. 내부 클래스는 다음과 같은 장점이 있습니다.

  • 두 클래스 멤버들 간에 손쉽게 접근할 수 있습니다.
  • 불필요한 클래스를 감춰서 코드의 복잡성을 줄일 수 있습니다.

보통 바깥쪽의 클래스를 '외부 클래스', 안쪽의 클래스를 '내부 클래스'라고 합니다. 외부 클래스를 한 대의 자동차로

가정한다면, 내부 클래스는 그 자동차를 구성하는 엔진, 기어, 방향지시등처럼 독립적으로 존재 하지만

자동차를 완성하는데 반드시 필요한 부품에 속합니다.

public class OuterClass{ // 외부 클래스
	...
    class InnerClass{ // 내부 클래스
    	...
    }
}

코드를 살펴보면 OuterClass가 외부 클래스가 되고 클래스 안에 선언된 InnerClass가 내부 클래스가 됩니다.

내부 클래스는 클래스 간의 긴밀한 관계가 필요할 때 사용합니다.

내부 클래스는 외부 클래스 안에 선언된다면 점만 제외하면 일반 클래스와 같은 성격과 모습을 지니고 있습니다.

2. 내부 클래스의 종류

내부 클래스는 클래스 안에서 선언된 위치에 따라 인스턴스 클래스(instance class), 정적 클래스(static class),

지역 클래스(local class), 익명 클래스(anonymous class)로 구분됩니다.

메서드 설명
인스턴스 클래스 외부 클래스의 멤버 변수와 같은 위치에 선언
주로 외부 클래스의 멤버 변수와 관련된 작업에 사용될 목적응로 선언
정적 클래스 외부 클래스의 클래스 변수와 같이 static 키워드 부여
지역 클래스 외부 클래스의 메서드 내부에서 선언하여 사용
메서드 영역에서 선언되기 때문에 메서드 내부에서만 사용 가능

인스턴스 클래스( instance class)

인스턴스 클래스는 외부 클래스 내부에서 생성하고, 선언되어 사용하는 클래스를 의미합니다. 인스턴스 변수와 같은

위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다루어집니다. 주로 외부 클래스의 인스턴스 멤버들과 관련된

작업에 사용될 목적으로 선언됩니다.

인스턴스 클래스 생성

다음과 같이 인스턴스 클래스는 인스턴스 멤버와 동일한 위치에 생성됩니다.

public class Outer{
	private String name; // 인스턴스 멤버
    ...
    
    public class Inner{	// 인스턴스 클래스
    	private String name
    	...
    }
}

내부 클래스도 외부 클래스 안에 생성되는 것 외에는 별도의 클래스이기 때문에, 파일이 컴파일되면 별도로 생성됩니다.

Outer $ Inner.class

컴파일 된 파일은 $ 기호를 기준으로 왼쪽은 외부 클래스, 오른쪽은 내부 클래스로 표현합니다.

 

인스턴스 클래스 선언

인스턴스 클래스는 기본적인 내부 클래스입니다. 외부 클래스 안에 생성되기 때문에, 클래스를 사용하려면 외부 클래스

객체가 생성된 상태에서 객체 생성을 할 수 있습니다.

 

인스턴스 클래스 객체를 선언하는 방법은 다음과 같습니다.

Outer outer = new Outer();	// 외부 클래스 객체 생성
Outer.Inner in = outer.new Inner();	// 외부 클래스를 이용해 내부 클래스 객체 생성

위와 같이 인스턴스 클래스를 생성하기 위해서는 외부 클래스가 반드시 선언된 후,

외부 클래스의 인스턴스를 이용해 선언합니다. 

 

인스턴스 클래스는 반드시 외부 클래스를 생성한 후 사용해야 합니다. 또한 외부 클래스의 인스턴스 변수를 사용할 수 있으나 정적 변수나 메서드를 호출할 수는 없습니다.

정적 내부 클래스(static class)

클래스 안에 정적 변수를 선언할 수 있는 것처럼 클래스도 정적 클래스를 만들 수 있습니다. 인스턴스 변수와 마찬가지로

static 키워드를 사용해 클래스를 선언한 후 정적 내부 클래스를 생성합니다. 주로 외부 클래스의 static 메서드에서 선언될 목적으로 선언됩니다.

 

정적 내부 클래스 선언

다음과 같이 정적 내부 클래스는 인스턴스 멤버와 동일한 위치에 선언합니다.

public class Outer{
	private String name; // 인스턴스 멤버
    
    public static class Inner{ // 정적 내부 클래스
    	private String name;
        ...
    }
}

클래스 앞에 static 키워드를 사용해 정적 내부 클래스를 선언했습니다. 정적 변수와 마찬가지로 클래스에 속하지만

독립적으로 존재합니다. 또한 외부 클래스의 존재와 상관없이 정적 변수를 사용할 수 있습니다. 그러나 외부 클래스의

인스턴스 변수 또는 메서드를 정적 내부 클래스 안에서는 사용할 수 없습니다.

public class OuterClass{ // 외부 클래스	
	private int val1; // 인스턴스 변수
    
    static class innerClass{ // 정적 내부 클래스
    	public void add(){
        	int result = val1 +10; // 오류 사용불가
        }
    }
}

위와 같이 정적 내부 클래스는 정적 변수 또는 정적 메서드를 호출하는 것은 가능하지만 외부 클래스의 인스턴스 변수나

메서드를 사용할 수 없습니다.

 

정적 내부 클래스 선언

정적 내부 클래스는 인스턴스 클래스와 다른게 외부 클래스 객체를 생성하지 않아도 선언할 수 있습니다.

정적 내부 클래스 객체를 선언하는 방법은 다음과 같습니다.

Outer.Inner in = new Outer.Inner(); // 외부 클래스 없이 바로 객체 생성

위와 같은 정적 내부 클래스는 외부 클래스의 선언 없이 바로 클래스의 객체를 선언하여 사용할 수 있습니다.

지역 클래스( local class)

지역 클래스는 외부 클래스의 메서드 내에서 선언되어 사용하는 클래스입니다. 메서드 내에서 선언되기 때문에

해당 클래스는 메서드 내에서만 사용할 수 있습니다. 또한 메서드의 실행이 끄나면 해당 클래스도 사용이 종료됩니다.

 

지역 클래스 생성

지역 클래스는 다음과 같이 메서드 내에서 선언하고 사용합니다.

public class LocalClass{
	...
    public void print(){
    	...
    	class A{ // 지역 클래스 선언
        	...
        }
        A a = new A(); // 메서드 내에서 사용
    }
}

이처럼 메서드 내에서 선언하여 사용하기 때문에 접근 제한자와 정적 키워드(static)를 붙일 수 없습니다.

 

유닛을 생산하는 메서드를 만들고, 그 안에 Unit클래스를 선언하여 구현했습니다. 지역 클래스인 유닛 안에는 이동

메서드가 존재합니다. 지역 클래스는 메서드 내에서 만든 것이므로 메서드 내에서 선언하여 사용해야 합니다.

3. 내부 클래스의 접근 제한

멤버 클래스 내부에서 외부 클래스의 필드와 메서드에 접근할 때는 제한이 따릅니다. 또한 내부 클래스의 선언 위치에

따라서 생기는 제약들도 존재합니다.

접근 제한자

내부 클래스도 클래스이기 때문에 접근 제한자를 붙여서 사용할 수 있습니다.

지역 클래스의 접근 제한

지역 클래스는 메서드 내에서 선언되어 사용합니다. 보통 메서드가 종료되면 클래스도 함께 종료되지만 메서드와

실행되는 위치가 다르기 때문에 종료되지 않고 남아 있을 수도 있습니다. 그래서 지역 클래스에서 메서드 내의 변수를

사용할 때는 변수를 복사해 사용합니다. 이러한 이유로 지역 클래스에서 메서드의 변수를 사용할 때 해당 변수가 변경되면

오류가 발생합니다.

4. 익명 클래스(anonymous class)

익명 클래스는 다른 내부 클래스와 달리 이름이 없는 클래스를 의미합니다. 익명 클래스는 클래스의 선언과 객체의 생성을

동시에 하므로 단 한 번만 사용할 수 있스며 오직 하나의 객체만을 생성할 수 있는 일회용 클래스입니다. 따라서, 생성자를 선언할 수도 없으며, 둘 이상의 인터페이스를 구현할 수도 없습니다. 오직 단 하나의 클래스를 상속받거나 단 하나의 인터페이스를 구현해야하만 합니다. 

보통 부모 클래스를 상속받아 처리하려면 다음과 같이 부모 클래스를 상속받는 자식 클래스를 만들어 처리합니다.

 

[ 부모 클래스 생성 ]

public class Person{
	public void mySelf(){
    	System.out.println("나는 인간입니다.");
    }
}

 

[ 자식 클래스 생성 ]

public class Student extends Person{
	@Override
    public void mySelf(){
    	System.out.println("I'm child");
    }
}

 

위 코드처럼 Person 클래스를 확장하기 위해 자식 클래스를 만들어 사용합니다. 그런데 만약 Person을 상속받아 처리해야

하는 클래스가 또 필요하다면 매번 하위 클래스를 만들어야합니다. 하위 클래스를 한 번만 사용하고 말 것이라면 굳이

상속할 필요가 없는데 이때, 유용하게 사용할 수 있는 것이 바로 익명 클래스입니다.

익명 클래스를 선언하고 기존에 객체를 생성하는 방법은 다음과 같습니다.

Person p = new Person(){
    @Override
    void method(){
    }
    ...
}

클래스 생성자 뒤에 코드 블록 {...}이 추가되고, 해당 클래스가 가진 메서드들을 Override하여 구현하는 방식입니다.

해달 클래스 자체를 재정의하여 구현합니다. 구현된 문법 마지막에는 세미콜론(;)을 붙입니다.

익명 클래스는 보통 인터페이스의 기능을 구현할 때 사용합니다. 인터페이스를 상속하여 하위 클래스를 통해 구현하는

것이 아니라 인터페이스를 익명 클래스로 선언하여 기능을 직접 구현합니다.

 

익명 클래스는 일반적인 클래스보다는 추상 클래스 또는 인터페이스를 상속을 통해 구현하지 않고 직접 구현할 때

주로 사용합니다. 추상 클래스와 인터페이스는 구현되지 않은 추상 메서드가 있기 때문에 객체 선언을 할 수 없습니다.

그러나 익명 클래스의 선언 형식을 이용하면 따로 상속 없이 구현할 수 있습니다.

응용문제

1. 다음 중 중첩 클래스에 대한 설명으로 틀린 것은 무엇일까요 ?

① 인스턴스 클래스는 외부 클래스 안에 선언됩니다.

② 인스턴스 클래스의 위치는 인스턴스 변수와 같습니다.

③ 정적 내부 클래스는 인스턴스 필드를 사용할 수 없습니다.

④ 정적 내부 클래스는 외부 클래스 없이 객체를 선언할 수 있습니다.

정답 : ④

2. 다음 중 로컬 클래스에 대한 설명으로 틀린 것은 무엇일까요 ?

① 로컬 클래스는 메서드 내부에 선언된 클래스를 말합니다.

② 로컬 클래스는 외부 클래스의 모든 필드와 메서드를 사용할 수 있습니다.

③ 로컬 클래스는 static 키워드를 이용해서 정적 클래스로 만들 수 없습니다.

④ 로컬 클래스는 메서드에서 선언된 변수의 값을 변경하여 사용할 수 있습니다.

정답 : ④

3. 다음 빈칸에 알맞은 코드를 작성해 보세요.

public class MyCar{
	private int price;
    private String myName;
    
    public MyCar(String myName, int price){
    	this.myName = myName;
        this.price = price;
    }
    
    public String getInfo(){
    	String str = "차량: "+myName+", 가격: "+price;
        return str;
    }
    public class Promotion{
    	public int discount(){
        	int discount = 0;
            discount = price / 100;
            return discount;
        }
    }
}
public class MycarMain{
	public static void main(String[] args){
   		
        System.out.println(mycar.getInfo()+", 할인금액 : "+promotion.discount());
    }
}

정답:

public class MycarMain{
	public static void main(String[] args){
		MyCar mycar = new MyCar("Toyota", 20000);  // MyCar 객체 생성
		MyCar.Promotion promotion = mycar.new Promotion();  // Promotion 객체 생성
		System.out.println(mycar.getInfo()+", 할인금액 : "+promotion.discount());
	}
}

4. 다음 코드를 실행하면 오류가 발생합니다. 오류의 원인을 찾아 수정해 보세요.

public class InnerExam{
	public int plus(int value){
    	class Cal{
        	public int add(){
            	return value++;
            }
        }
        Cal cal = new Cal();
        return cal.add();
    }
    
    public static void main(String[] args){
    	InnerExam ie = new InnerExam();
        System.out.println(ie.plus(10));
    }
}

정답 :

public class InnerExam {
    public int plus(int value) {
        class Cal {
            public int add() {
                int newValue = value + 1;  // value를 증가시키기 위해 새로운 변수 사용
                return newValue;
            }
        }
        Cal cal = new Cal();
        return cal.add();
    }

    public static void main(String[] args) {
        InnerExam ie = new InnerExam();
        System.out.println(ie.plus(10));  // 11 출력
    }
}