Java의 여러 내부 클래스 정의, 유형

    Java의 내부 클래스란?


    • 클래스 내부에 다시 한번 클래스가 정의 되는 경우가 있다. 이렇게 클래스 내부에 정의되는 클래스를 내부 클래스라고 한다.
    • 내부 클래스는 인스턴스 내부 클래스, Static 내부 클래스, 지역 내부클래스, 익명 내부 클래스가 있다.

     

    인스턴스 내부 클래스


    • 내부적으로만 사용될 클래스다.
    • 인스턴스 내부 클래스는 외부 클래스가 생성된 후에 내부 클래스가 생성된다.
    • Private이 아닌 내부 클래스는 외부에서 선언이 가능하다. 하지만 그렇게 사용하지는 않는다.
    • 외부 클래스 내부에 있기 때문에 외부 클래스의 Private 변수, 메서드도 사용 가능하다. 
    package ch01;
    
    
    class OutClass{
    	
    	private int oNum = 10;
    	private static int sNum = 20; 
    	public InClass inClass;
    	
    	//outClass의 default 생성자는 아래와 같음.
    	//outClass가 생성된 후에 inclass가 생성됨.
    	public OutClass(){
    		inClass = new InClass();
    	}
    	
    	public class InClass{
    		private int iNum = 30;
    		private static int isNum = 40;
    		
    		public void showInfo() {
    			// inner class에서는 out, inner 클래스의 모든 변수에 접근할 수 있음
    			// outer class가 생성된 후에 inner 클래스가 생성되기 때문임.
    			System.out.println("내부 클래스가 외부 인스턴스 변수 "+oNum);
    			System.out.println("내부 클래스가 외부 스태틱 변수 " + sNum);
    			System.out.println("내부 클래스가 내부 인스턴스 변수 " + iNum);
    			System.out.println("내부 클래스가 내부 스태틱 변수 " + isNum);
    		}
    		
    		static void sTest() {
    			System.out.println("내부 스태틱 함수");	
    		}
    	}
    	
    	
    	public void usingClass() {
    		System.out.println("외부 클래스 메서드로 내부 클래스 매소드 콜");
    		inClass.showInfo();
    	}
    	
    	public void usingClass2() {
    		System.out.println("내부 인스턴스 선언 없이, 내부 스태틱 메서드 실행");
    		InClass.sTest();
    	}
    	
    	
    	public void outshowInfo() {
    		System.out.println(oNum);
    		System.out.println(sNum);
    		//내부 클래스의 인스턴스는 선언되지 않을 수도 있기 때문에 확정적으로 접근할 수 없음
    		//System.out.println(iNum); // error 발생
    		//System.out.println(isNum); // error 발생
    	}
    	
    }
    	
    public class InnerClassTest {
    
    	public static void main(String[] args) {
    	
    		OutClass.InClass.sTest();
    		OutClass aa = new OutClass();
    		
    		System.out.println();
    		System.out.println("외부 클래스 인스턴스로 내부 메서드 접근 가능");
    		aa.usingClass();
    		
    		System.out.println();
    		System.out.println("내부 클래스 인스턴스 선언 없이도 내부 Static 메서드 접근 가능 ");
    		OutClass.InClass.sTest();
    		aa.usingClass2();}
    
    }

    인스턴스 내부 클래스의 변수 접근


    내부 클래스는 외부 클래스가 생성된 후, 내부 클래스가 생성된다. 따라서 내부 클래스는 외부 클래스의 멤버변수 + 스태틱 변수, 내부 클래스의 멤버변수 + 스태틱 변수에 접근 및 수정할 수 있다. 반면 외부 클래스는 내부 클래스의 멤버변수 + 스태틱 변수에 접근할 수 없다. 왜냐하면 내부 클래스의 인스턴스는 생성되지 않을 수 있기 때문이다.

     

    Static 내부 클래스


    정적 내부 클래스는 외부 클래스의 선언과 관계없이 접근이 가능하다.

    package ch01;
    
    
    class OutClass1{
    	
    	private int oNum = 10;
    	private static int sNum = 20; 
    	public StaticInClass inClass;
    	
    	//내부 정적 클래스.
    	OutClass1(){
    		inClass = new StaticInClass();
    	}
    	
    	public static class StaticInClass{
    		//oNum = 100; // error 발생
    		//sNum = 100; // error 발생
    		int siNum = 100;
    		static int ssNum = 1000;
    		
    		
    		void showInfo() {
    			//내부 Static Class는 외부 인스턴스와 상관없이 항상 사용가능하다.
    			//외부 인스턴스 변수는 생성 되지 않을 수 있기 때문에 사용 불가능.
    			//System.out.println("내부 정적 클래스 메서드 + 외부 인스턴스 변수 " + oNum); // error 발생
    			System.out.println("내부 정적 클래스 메서드 + 외부 스태틱 변수 " + sNum);
    			System.out.println("내부 정적 클래스 메서드 + 내부 인스턴스 변수 " + siNum);
    			System.out.println("내부 정적 클래스 메서드 + 외부 인스턴스 변수 " + ssNum);
    		}
    		
    		static void showstatic() {
    			System.out.println("내부 스태틱 함수 ");
    		}
    		
    		
    	}
    	
    	public void usingClass() {
    		System.out.println("외부 클래스 함수를 이용한 내부 클래스 함수 실행");
    		inClass.showInfo();
    		System.out.println();
    		StaticInClass.showstatic();
    	}
    	
    }
    	
    	
    public class InnerStaticClassTest {
    
    	public static void main(String[] args) {
    		
    		
    		OutClass1.StaticInClass.showstatic();
    		OutClass1 a = new OutClass1();
    		a.usingClass();
    		
    		
    		
    	}
    
    }
    1. Static 내부 클래스가 선언되면 상위 클래스와 분리되었다고 표현한다. Static 내부 클래스가 외부 클래스의 멤버변수에 접근할 수 없다는 것을 뜻한다. Static 내부 클래스의 일반 메서드는 외부 클래스의 멤버변수를 제외한 변수(외부 클래스 Static 변수, 내부 클래스 인스턴스 변수, 내부 클래스 Static 변수)에 접근 가능하다.
    2. Static 내부 클래스의 Static 메서드는 Inner + Outer Class의 Static 변수에만 접근이 가능하다. 왜냐하면 Static 내부 클래스의 인스턴스가 만들어지지 않고도 사용할 수 있기 때문이다.


    Local Inner Class (지역 내부 클래스)


    • 외부 클래스의 메서드 내에 선언된 클래스다.
    • 메서드 호출이 끝나면 사라지기 때문에 메서드에 사용된 지역변수의 유효성이 사라진다.
    • 메서드 호출이 끝난 후에도 사용해야 하는 경우가 있을 수 있다. 따라서 Local Inner Class에서 사용되는 메서드의 Local 변수, 매개 변수는 Final로 선언되어 상수화된다. 따라서 Static에 저장되어 접근 및 조회는 가능하지만 수정은 불가능하다.
    class OutClass{
    	
    	int oNum = 10;
    	static int sNum = 200; 
    	
    	// Runnable 타입으로 어떤 결과를 return 받고 싶음.
    	// Runnable 메서드 내에서 클래스를 정의한다.
    	// 클래스를 정의 후에, 그 인스턴스를 return 해준다.
    	// 매개변수는 메서드를 호출할 때 생성되고, Stack 영역에 저장된다.
    	Runnable doing(int i) {
    		int lNum = 33333;
    		
    		class MyRunnable implements Runnable{
    
    			int cNum = 100;
    
    			/*// 메서드의 매개변수, 지역변수는 스택 메모리에 잡힌다
    			// 함수가 끝나면 사라진다. 그런데 나중에 이것들이 사용될 필요가 있다.
    			// 따라서 이 값들은 final로 선언되어 Static 영역에 상수화되어 저장된다.
    			// 따라서 수정 불가능하다.
    			lNum = 9999;
    			i = 9999;
    			*/
    			
    			@Override
    			public void run(){
    
    				System.out.println("i = " + i );
    				System.out.println("local Num = " + lNum);
    				System.out.println("Method Local Num = " + cNum);
    				System.out.println("Outclass num = " + oNum);
    				System.out.println("Outclass static num = " + sNum);
    				
    				
    			}
    		}
    		
    		return new MyRunnable();
    			
    		}
    }
    	
    
    public class AnonumousInnerTest {
    
    	public static void main(String[] args) {
    		OutClass outClass = new OutClass();
    		Runnable runner = outClass.doing(9999999);
    		runner.run();
    	}
    
    }
    1. 지역 내부 클래스는 메서드 내부에 선언된 클래스다. 주로 지역 내부 클래스를 만들고, 지역 내부 클래스 타입으로 변수를 반환될 때 사용한다.
    2. 메서드가 호출되면 지역 내부 클래스가 만들어진다. 이 때, 메서드에 대한 지역변수들의 값과 메서드에 들어온 매개변수는 스택에 있다. 즉, 이 메서드 호출이 완료되면 증발되는 값이다. 단, 지역 내부 클래스로 선언된 인스턴스는 언젠가 다시 쓰일 수 있다. 따라서, 메서드가 호출될 때 사용된 지역 변수와 매개변수는 나중에 사용될 것을 대비해 Final로 선언된다. 이런 이유 때문에 지역 내부 클래스에서 매개변수나 지역 변수에 접근은 가능하지만, 수정은 불가능하다.

    익명 클래스


    • 익명 클래스는 이름이 없는 클래스다. 이름이 없기 때문에 생성자를 가질 수 없다.
    • 익명 클래스는 클래스의 이름은 생략되고, 대부분 하나의 인터페이스 혹은 추상 클래스를 구현하여 반환하는 역할을 한다.
    • 익명 클래스는 지역 내부 클래스에서 이름만 없어진 클래스라고 볼 수 있다. 
    • 인터페이스나 추상 클래스 자료형에 인스턴스를 직접 대입하여, 클래스를 생성해 반환할 수 있다. 
    • 익명 클래스는 한 두 가지 기능을 굳이 클래스를 구현하지 않고, 간편하게 만드는 클래스다. 또한 Extends나 Implements 없이 구현되기 때문에 코드의 간결성에서도 좋다.
    // 익명함수를 위한 인터페이스 제공
    interface Anomy{
    	void showInfo();
    	void showInfo2();
    }
    
    
    class Outer10{
    	int oNum = 100;
    	static int sNum = 200;
    	
        // Anomy 타입을 반환 받는다고 한다.
    	Anomy testCode() {
        // 바로 Anomy() 생성자로 생성을 해서 return 해주는데
        // Anomy Interface를 구현한 인스턴스를 return 해준다.
    		return new Anomy() {
    
    			@Override
    			public void showInfo() {
    				System.out.println("Say Hello!");
    			}
    
    			@Override
    			public void showInfo2() {
    				System.out.println("Say Hi");
    			}
    		};
    	}
    	
    }
    
    public class AnomyTest {
    	public static void main(String[] args) {
    		Outer10 outer = new Outer10();
            // anomy type을 반환받아서 저장한다.
    		Anomy anomy = outer.testCode();
    		anomy.showInfo();
    		anomy.showInfo2();
    		
    	}	
    
    }
    1. 먼저 익명클래스 구현을 위한 인터페이스를 도입하고, 필요한만큼의 메서드를 선언한다.
    2. 인터페이스 타입을 Return 값으로 가지는 외부 클래스 메서드를 선언한다. Return 값에는 인터페이스의 생성자를 활용해서 생성을 해서 돌려주는 것으로 표기한다. 이후 바로 중괄호로 인터페이스를 구현한다 (익명 클래스 구현 완료)

     

    지역 내부 클래스와 익명 클래스의 코드상 차이


    • 지역 내부 클래스는 이름이 있으나, 익명함수는 이름이 없다.
    • 익명함수는 이름이 없기 때문에 생성자가 없다. 따라서 가상 인터페이스의 생성자를 이용한다. 지역 내부 클래스는 이름이 있기 때문에 본인 자신의 생성자를 활용해 타입을 반환한다.
    package ch02;
    
    
    interface Anomy1{
    	
    	void showInfo();
    	void showInfo2();
    	
    }
    
    class Outer101{
    	int oNum = 100;
    	static int sNum = 200;
    	
    	
    	Anomy1 testCode() {
    		return new Anomy1() {
    
    			@Override
    			public void showInfo() {
    				System.out.println("Say Hello!, Anomy Class");
    			}
    
    			@Override
    			public void showInfo2() {
    				System.out.println("Say Hi, Anomy Class");
    			}
    		};
    	}
    	
    	Anomy1 testCode2() {
    		
    		class MyAnomy implements Anomy1{
    
    			@Override
    			public void showInfo() {
    				System.out.println("Say Hello!, Local Inner Class");
    				
    			}
    
    			@Override
    			public void showInfo2() {
    				System.out.println("Say Hi, Local Inner Class");
    				
    			}
    		}
    		return new MyAnomy();
    	}
    	
    }
    
    public class AnomTest {
    	public static void main(String[] args) {
    		Outer101 outer = new Outer101();
    		Anomy1 anomy = outer.testCode();
    		anomy.showInfo();
    		anomy.showInfo2();
    		
    		Outer101 outer2 = new Outer101();
    		Anomy1 anomy2 = outer2.testCode2();
    		anomy2.showInfo();
    		anomy2.showInfo2();
    		
    	}	
    
    }

    지역 내부 클래스와 익명 클래스 모두 인터페이스를 구현했다. 거의 유사하다고 볼 수 있으나, 생성자의 차이가 가장 큰 것 같다. 익명은 이름이 없으니 자기 이름으로 된 생성자를 쓸 수가 없다.


    클래스 주요 내용
    인스턴스 내부 클래스 1. 인스턴스 내부 클래스는 외부 클래스 생성 후 만들어짐. 따라서, 외부 클래스로 변수에 자유롭게 접근 가능함.
    2. 외부 클래스에서 인스턴스 내부 클래스 변수 접근은 불가능함. 내부 클래스 인스턴스는 선언되지 않을 수 있기 때문이다.
    Static 내부 클래스 1. Static 내부 클래스는 외부 클래스와 분리됨.
    2. Static 내부 클래스에서 외부 인스턴스 변수, 내부 인스턴스 변수로는 접근이 불가능하다. 왜냐하면 두 클래스의 인스턴스가 모두 선언되지 않을 경우가 있기 때문.
    지역 내부 클래스 1. 메서드 내부에서 사용되고, 메서드의 반환값이 지역 내부 클래스일 때 사용됨.
    2. 메서드는 Stack에 적재됨. 따라서, 지역 내부 클래스를 구현해 사용된 메서드의 지역변수 및 매개변수는 Final로 선언되어, 지역 내부 클래스에서 수정이 불가능함.
    익명 클래스 1. 지역 내부 클래스에서 이름이 없어짐.
    2. 익명함수를 위한 인터페이스, 추상 메서드가 필요함.
    3. 이름이 없기 때문에 클래스명으로 생성자 사용 불가능. 

    댓글

    Designed by JB FACTORY