컴퓨터공학 💻 도서관📚

Part2. 6-1 여러 내부 클래스의 정의와 유형 본문

✅🌲강의 복습 노트/패캠 JavaSpring 강의,코드 복습

Part2. 6-1 여러 내부 클래스의 정의와 유형

들판속초록풀 2025. 7. 6. 11:46

내부 클래스란? (inner class)

  • 클래스 내부에 선언한 클래스로 이 클래스를 감싸고 있는 외부 클래스와 밀접한 연관이 있는 경우가 많고,
    다른 외부 클래스에서 사용할 일이 거의 없는 경우에 내부 클래스로 선언해서 사용함
  • 중첩 클래스라고도 함
  • 클래스 내부에 클래스를 언제 선언하느냐 --> 클래스 내부에서만 쓰려고 선언을 하는 거 , 외부에서는 쓸 일 X
    지금 많이 사용하는 거는 '익명 내부 클래스'이다.
  • 내부 클래스의 종류
    1. 인스턴스 내부 클래스,  2. 정적(static) 내부 클래스,  3. 지역(local) 내부 클래스,  4. 익명(anonymous) 내부 클래스

인스턴스 내부 클래스

  • 내부적으로 사용할 클래스를 선언 (private으로 선언하는 것을 권장)
  • 외부 클래스가 생성된 후 생성됨 ( 정적 내부 클래스와 다름 )
  • Inner클래스는 외부 클래스 안에 있기 때문에 외부 클래스의 private변수 같은 변수들을 같이 사용할 수 있다
    그 대신에, static 변수는 못 쓴다.
    인스턴스 Inner클래스는 Outer클래스가 먼저 생성되고 난 다음에 생성이 된다.
    static 변수라는 것은 Outer 클래스 생성과 상관없이 쓸 수 있다는 뜻이기에
    인스턴스 내부 클래스 안에서는 static변수를 사용할 수 없다.
    정적 변수를 사용하려면 정적 내부 클래스에서 사용해야 한다.
  • private이 아닌 내부 클래스는 다른 외부 클래스에서 생성할 수 있음
OutClass outClass = new OutClass();                   // OutClass

OutClass.InClass inClass = outClass.new InClass();    // OutClass.InClass
        // private이 아닌 Inclass를 다른 외부 클래스에서 사용할 때 그냥 new를 하지 않고 위에처럼 한다.
       //이건 거의 쓰이지 않는다 문법적으론 가능하지만

    *  인스턴스 내부 클래스 예

class OutClass {                        // OutClass

	private int num = 10;
	private static int sNum = 20;
	private InClass inClass;            // InClass
	
	public OutClass(){     
		inClass = new InClass();  // 내부 클래스 생성(Outer클래스가 만들어질 때 주로 내부 클래스를 만든다)
	}
	
	private class InClass{                   // 인스턴스 내부 클래스
		int inNum = 100;
		//static int sInNum = 200;  //에러 남
		
		void inTest(){
			System.out.println("OutClass num = " +num + "(외부 클래스의 인스턴스 변수)");
			System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 스태틱 변수)");
			System.out.println("InClass inNum = " + inNum + "(내부 클래스의 인스턴스 변수)");
		}
		
	    //static void sTest(){  //에러 남
	    	
	    //}
		
	}
	
	public void usingClass(){
		inClass.inTest(); //내부 클래스 변수를 사용하여 메서드 호출하기
	}
}

public class InnerTest {

	public static void main(String[] args) {
		OutClass outClass = new OutClass();
		System.out.println("외부 클래스 이용하여 내부 클래스 기능 호출");
		outClass.usingClass();    // 내부 클래스 기능 호출
	    System.out.println();
	    
		OutClass.InClass inClass = outClass.new InClass();   // 외부 클래스를 이용하여 내부 클래스 생성
		System.out.println("외부 클래스 변수를 이용하여 내부 클래스 생성");
		inClass.inTest();
	}

}

 

 

정적 내부 클래스

  • 외부 클래스 생성과 무관하게 사용할 수 있음
  • 정적 변수, 정적 메서드 사용
  • 외부 클래스의 인스턴스 변수는 사용할 수 없고  본인의 인스턴스 변수, 외부 클래스의 static 변수는 사용할 수 있다.
  • 정적 내부 클래스 예
class OutClass {

	private int num = 10;
	private static int sNum = 20;
	private InClass inClass;
	
	public OutClass(){
		inClass = new InClass(); // 내부 클래스 생성
	}
	
	class InClass{
		
		int inNum = 100;
		//static int sInNum = 200;  //에러 남
		
		void inTest(){
			System.out.println("OutClass num = " +num + "(외부 클래스의 인스턴스 변수)");
			System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 스태틱 변수)");
			System.out.println("InClass inNum = " + inNum + "(내부 클래스의 인스턴스 변수)");
		}
		
	    //static void sTest(){  //에러 남  --> static 메서드임
	    	
	    //}
		
	}
	
	public void usingClass(){
		inClass.inTest(); //내부 클래스 변수를 사용하여 메서드 호출하기
	}
	
	static class InStaticClass{               // 정적 내부 클래스
		
		int inNum = 100;
		static int sInNum = 200;
		
		void inTest(){   //정적 클래스의 일반 메서드
			//num += 10;    // 외부 클래스의 인스턴스 변수는 사용할 수 없음.
			System.out.println("InStaticClass inNum = " + inNum + "(내부 클래스의 인스턴스 변수 사용)"); 
			System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 스태틱 변수 사용)");
			System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 스태틱 변수 사용)");
		}
		
		static void sTest(){  // 정적 클래스의 static 메서드
			//num += 10;   // 외부 클래스의 인스턴스 변수는 사용할 수 없음.
			//inNum += 10; // 내부 클래스의 인스턴스 변수는 사용할 수 없음
			
			System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 스태틱 변수 사용)");
			System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 스태틱 변수 사용)");
			
		}
	}	
}

public class InnerTest {

	public static void main(String[] args) {

	   .....
	   	
       //외부 클래스 생성하지 않고 바로 정적 내부 클래스 생성
		OutClass.InStaticClass sInClass = new OutClass.InStaticClass();  //정적 내부 클래스 생성
		System.out.println("정적 내부 클래스 일반 메서드 호출");
		sInClass.inTest();
		System.out.println();
		
		System.out.println("정적 내부 클래스의 스태틱 메소드 호출");
		OutClass.InStaticClass.sTest();
	}

}

   *  정적 내부 클래스 일반 메서드와 정적 메서드에서의 변수 사용

 

 

지역 내부 클래스

  • 지역 변수와 같이 메서드 내부에서 정의하여 사용하는 클래스
  • 메서드의 호출이 끝나면 메서드에 사용된 지역변수의 유효성은 사라짐 (기본적인 얘기)
  • 메서드 호출 이후에도 사용해야 하는 경우가 있을 수 있으므로 지역 내부 클래스에서 사용하는 메서드의 지역 변수나 매개 변수는 final로 선언된다

    Runnable 인터페이스는 자바 lang 패키지에 선언되는 인터페이스이고
    이게 하는 일은 클래스를 스레드화할 때 필요한 run 메서드를 구현할 때 사용하는 인터페이스이다.

    원래 클래스를 스레드화할 때 두 가지 방법이 있는데
    하나는 스레드 클래스에서 extedns 상속을 받는 방법이 있고
    다음은 Runnable 인터페이스를 implement 하는 방법이 있다.
    implement Runnable을 했으면 Runnable  인터페이스가 제공하는 run 이라는 메서드를 구현해야 한다.

    매개변수, 지역변수를 그냥 가져다가 출력할 때는 오류가 나지 않는다.
    그런데 매개변수, 지역변수의 값을 중간에 변경하려 하면 오류가 난다.

    지역변수는 스택메모리에 잡히기 때문에 메서드 호출이 끝나고 나면 없어지는데
    run 이라는 메서드를 인스턴스 변수를 사용해서 나중에 또 호출해야 하는 경우에 문제가 발생한다. 
    메서드를 또 호출하면 그때 지역변수들은 없기 때문에  값 변경이 안 된다.

    결국 무슨 얘기냐면  저 변수들은 스택에 잡히면 안 된다.
    그래서 저 변수들을 final로 선언한다.
    예전에는 직접 final로 선언하라고 했지만 요즘은 컴파일러가 알아서 final로 바꾼다.
    final로 바꾸기 때문에  스택 메모리에 잡히지 않고  상수 메모리에 잡힌다.  그래서 값을 못 바꾼다.

    결론 :  애초에 매개변수, 지역변수는 final로 변경되기 때문에 값 못 바꾼다.

class Outer{
	
	int outNum = 100;
	static int sNum = 200;
	
		
	Runnable getRunnable(int i){

		int num = 100;
		
		class MyRunnable implements Runnable{     // 지역 내부 클래스
                                                          // Runnable 인터페이스를 구현한 클래스를 만듦
			int localNum = 10;
				
			@Override
			public void run() {
				//num = 200;   //에러 남. 지역변수는 상수로 바뀜
				//i = 100;     //에러 남. 매개 변수 역시 지역변수처럼 상수로 바뀜
				System.out.println("i =" + i); 
				System.out.println("num = " +num);  
				System.out.println("localNum = " +localNum);
					
				System.out.println("outNum = " + outNum + "(외부 클래스 인스턴스 변수)");
				System.out.println("Outter.sNum = " + Outer.sNum + "(외부 클래스 정적 변수)");
				}
			}
		 return new MyRunnable();
	}
}

public class LocalInnerTest {

	public static void main(String[] args) {

		Outer out = new Outer();
		Runnable runner = out.getRunnable(10);
		runner.run();
	}
}

   *  MyRunnable 클래스를 사용하려면 직접 생성하는 것이 아닌 getRunnable() 메서드를 호출하여 생성된 객체를 반환 받아야
       함

 

 

 

익명 내부 클래스

  • 이름이 없는 클래스 (위 지역 내부 클래스MyRunnable 클래스 이름은 실제로 호출되는 경우가 없음,
                                           메서드 안에서만 쓰임
    )
  • 클래스의 이름을 생략하고 주로 하나의 인터페이스나 하나의 추상 클래스를 구현(implementation)하여 반환
  • 인터페이스나 추상 클래스 자료형의 변수에 직접 대입하여 클래스를 생성하거나 지역 내부 클래스의 메서드 내부에서 생성하여 반환 할 수 있음.
  • widget(위젯)의 이벤트 핸들러에 활용됨  (안드로이드 프로그래밍)

   *  익명 클래스의 예

class Outter2{
		
	Runnable getRunnable(int i){

		int num = 100;
		
		return new Runnable() {   // 이름 필요 없으니까 클래스 이름 없애버리고 바로 return 함
				
		@Override
		public void run() {            // 여기가 implementation 부분
			//num = 200;   //에러 남
			//i = 10;      //에러 남
			System.out.println(i);
			System.out.println(num);
			}
		};                    // 그리고 이 implementation의 끝이 여기까지다라는 표시로 ; 해줌
	}
	
    
	Runnable runner = new Runnable() {     // 이렇게 쓸 수도 있다.
		                               // runner 변수를 이용해서 run 메서드를 호출할 수 있다.
		@Override
		public void run() {
			System.out.println("Runnable 이 구현된 익명 클래스 변수");
			
		}
	};
}

public class AnonymousInnerTest {

	public static void main(String[] args) {
		Outter2 out = new Outter2();
	
		Runnable runnerble = out.getRunnable(10);
		runnerble.run();
		
		out.runner.run();
	}
}
Comments