본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석4-3 내부클래스와 익명클래스

by 이쟝 2022. 1. 6.

내부 클래스(inner class)

클래스 내에 선언된 클래스 

 

-> 이 때 내부 클래스 B는 외부 클래스 A를 제외하고는 다른 클래스에서 잘 사용되지 않아야 함!

-> B는 A의 내부클래스(inner class), A는 B를 감싸고 있는 외부 클래스(outer class)

 

내부 클래스의 장점

- 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다. (B에서 A의 객체 생성 없어도 A의 멤버 접근 가능)

- 외부에 불필요한 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다.(캡슐화)

 

 

public class InnerClass {

	public static void main(String[] args) {
//		BBB b = new BBB();  // 내부클래스는 다른 클래스에서 접근 불가능(AAA객체 생성뒤 가능)
//		b.method();         // 참조변수 b를 통해서 BBB 객체의 method 호출
	}
}
class AAA {  // AAA는 BBB의 외부 클래스
	int i = 100;
	BBB b = new BBB();
	
	class BBB {  // BBB는 AAA의 내부 클래스(C에서는 사용 xx)
		void method() {
		AAA a = new AAA();      // 객체 생성하고 사용
		System.out.println(a.i);
		System.out.println(i);  // 객체 생생없이 외부 클래스의 멤버 접근가능(내부클래스의 장점)
		}
	}
}

class CCC {  // C클래스에서는 BBB를 사용 할 수 없음
//	BBB a = new BBB();
}

내부 클래스의 종류와 특징

- 내부 클래스의 종류와 유효범위(scope, 사용할 수 있는 범위)는 변수와 동일

- 내부 클래스의 종류 == 변수의 선언위치에 따른 종류(iv, cv, lv)

변수를 선언하듯이 내부클래스 선언 가능

 

내부 클래스 특징
인스턴스 (내부)클래스(instance class)  (iv와 같음) 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버처럼 다뤄진다. 주로 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용될 목적으로 선언된다.
스태틱 (내부)클래스(static class)
(cv와 같음)
외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static멤버처럼 다뤄진다. 주로 외부 클래스의 static 멤버, 특히 static메서드에서 사용될 목적으로 선언된다.
지역 (내부)클래스(local class) (lv와 같음) 외부 클래스의 메서드나 초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다.
익명 클래스(anonymous class) 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용)

내부 클래스의 제어자와 접근성

 

 

-> 내부클래스가 외부클래스 멤버와 같이 간주된다!!

-> 원래 class 앞에는 접근제어자 public 아니면 (default)만 올 수 이지만 내부 class 에는 private, protected도 가능함(iv, cv, lv도 접근제어자 다 사용할 수 있기 때문)

 

예제1) Static 클래스만 static 멤버를 정의할 있고, 인스턴스 클래스, 지역 내부 클래스는 static 멤버를 정의할 없다.

 

public class OuterClass {  // 외부클래스
	class InstanceInner { // 인스턴스 내부 클래스
		int iv = 100;                 // 인스턴스 변수 허용
		static int cv = 100;          // 에러! static 변수를 선언할 수 없다.
		final static int CONST = 100; // final static은 상수이므로 허용
	}

	class StaticInner { // static 내부 클래스에서는 외부 클래스의 인스턴스 멤버에 접근할 수 없다
		int iv = 200;
		static int cv = 200;         // static 클래스만 static 멤버를 정의할 수 있다.(static은 객체 생성 없이 사용 가능)
	}							

	void myMethod() { // 메서드
		class LocalInner { // 지역 내부 클래스
			int iv = 300;
			static int cv = 300;      // 에러! static 변수를 선언할 수 없다.
			final static int CONST = 300;   //  final static은 상수이므로 허용
		}
	}
	
	public static void main(String[] args) {
		System.out.println(InstanceInner.CONST);  // 100
		System.out.println(StaticInner.cv);       // 200
//		System.out.println(LocalInner.CONST);     // 에러! 지역 내부 클래스는 메서드 내에서만

	}
}

 

더보기
더보기
더보기

- staticInner 클래스에서 iv가 올 수 있는이유: staticInner 클래스도 클래스의 한 종류이기 때문에 멤버변수 정의할 때 처럼 iv, cv를 선언할 수 있다.

- 하지만 InstanceInner(인스턴스내부클래스)와 LocalInner(지역내부클래스) 에는 cv를 선언할 수 없다. (cv는 오직 staticInner(스태틱 클래스)에만!)

- 내부클래스를 정의했는데 static 멤버변수를 쓰고 싶다면 내부클래스를 static클래스로 정의해야 함!

- final과 static이 동시에 붙은 변수 == 상수 -> 모든 내부 클래스에서 정의 가능

- 지역내부 클래스(LocalInner)의 static 상수는 메서드 내에서만 사용 가능!


예제2) 인스턴스 멤버는 static 멤버를 항상 허용하지만 static 멤버는 인스턴스 멤버를 허용하지 않는다. static 멤버는 인스턴스 멤버에 접근 불가능하다. 

 

public class InnerClass2 {
	class InstanceInner {}         // 인스턴스 내부클래스 선언
	static class StaticInner { }   // static 내부클래스 선언
	
	InstanceInner iv = new InstanceInner();     // 인스턴스멤버 간에는 서로 직접 접근이 가능
	static StaticInner cv = new StaticInner();  // static 멤버 간에는 서로 직접 접근이 가능
	
	static void staticMethod() { // Static 내부메서드
//		InstanceInner iv2  = new InstanceInner();   // Static 멤버는 인스턴스멤버에 직접 접근할 수 없다. 
		StaticInner cv2 = new StaticInner();
	} 
	
	void instanceMethod() {   // 인스턴스 내부메서드에서는 인스턴스멤버와 static멤버 모두 접근 가능
		InstanceInner iv3 = new InstanceInner();
		StaticInner cv3 = new StaticInner();
//		LocalInner lv = new LocalInner();  // 메서드 내에 지역적으로 선언된 내부 클래스는 외부에서 접근할 수 없다.
	}
	
	void myMethod( ) {
		class LocalInner { // 지역내부클래스
			LocalInner lv = new LocalInner();
		}
	}

 

더보기
더보기
더보기

인스턴스멤버는 같은 클래스에 있는 인스턴스멤버와 static멤버 모두 직접 호출이 가능하지만 static멤버는 인스턴스멤버를 직접 호출할 수 없음

 

iv(인스턴스변수) -> cv(클래스변수,static)는 ok / cv(클래스변수,static) -> iv(인스턴스변수)는 xxxxxxxx

인스턴스멤버 -> static멤버는 ok / static멤버 -> 인스턴스멤버 xxx

 

헷갈리면 다시 보기

https://everysmallstep.tistory.com/72


예제3) 내부 클래스에서 외부 클래스의 변수들에 대한 접근성을 보여주는 예제

-> private 내부클래스에서는 사용할 있다. 내부클래스에서는 외부클래스의 private 멤버도 접근 가능하다!

 

public class InnerClass3 {
	
	private int outerIv = 0;
	static int outerCv = 0;
	
	class InstatnceInner { // 인스턴스 클래스는 외부클래스의 인스턴스, static 멤버에 접근 가능
		int iiv = outerIv;  // 1. 외부클래스의 private 멤버도 접근 가능하다.
		int iiv2 = outerCv;  
	}
	
	static class StaticInner { // 스태틱 클래스는 외부클래스의 인스턴스멤버에 접근 불가능
		int siv = outerCv;
		static int scv = outerCv;
	}
	
	void myMethod() {
		int lv = 0;         // 값이 바뀌지 않는 변수는 상수로 간주
		final int LV = 0;   // JDK1.8부터 final 생략 가능
	
		class LocalInner { // 2. 지역 내부 클래스를 감싸고 있는 메서드의 상수만 사용가능
			int liv = outerIv;  // 외부클래스 인스턴스 변수 접근 가능
			int liv2 = outerCv; // 외부클래스 static 변수 접근 가능 
			
			//에러! 외부 클래스의 지역변수는 final이 붙은 변수(상수)만 접근 가능
			int liv3 = lv;      // 에러지만 (JDK1.8부터 에러아님)
			int liv4 = LV; 		// OK!
			
		}
	}	
}

 

더보기
더보기
더보기

-> 지역 내부 클래스에서는 변수는 안되고 상수만 가능하다!!(메서드는 지역변수이어서 메서드 종료와 함께 소멸되고 내부클래스의 객체가 더 오래 존재할 수 있기 때문에)

 

- JDK1.8부터 지역 클래스에서 접근하는 지역 변수 앞에 final을 생략해도 컴파일러가 자동으로 붙여준다. 편의상 final을 생략할 수 있게 한 것일뿐 해당 변수의 값이 바뀌는 문장이 있으면 컴파일 에러 발생!


예제4) 외부 클래스가 아닌 다른 클래스에서 내부 클래스를 생성하고 내부 클래스의 멤버에 접근하는 예제(내부 클래스로 선언해서는 안되는 클래스를 내부 클래스로 선언함)

 

class Outer2 {
	class InstanceInner { // 내부 인스턴스 클래스
		int iv = 100;
	}
	static class StaticInner { // 내부 스태틱 클래스
		int iv = 200;
		static int cv = 300;
	}
	void myMethod() {
		class LocalInner {  // 지역 내부 클래스
			int iv = 400;
		}
	}
}
public class InnerClass4 {
	public static void main(String[] args) {
    	// 외부 클래스의 인스턴스를 먼저 생성해야 인스턴스 클래스의 인스턴스를 생성 가능
		Outer2 oc = new Outer2();
		Outer2.InstanceInner li = oc.new InstanceInner(); 
		
		System.out.println("li.iv: " + li.iv); // li. iv: 100
		System.out.println("Outer2.StaticInner.cv: " + Outer2.StaticInner.cv);  // Outer2.StaticInner.cv: 300
		// 스태틱 내부 클래스의 인스턴스는 외부 클래스를 먼저 생성하지 않아도 된다. 
        
		Outer2.StaticInner si = new Outer2.StaticInner();
		System.out.println("si.iv: " + si.iv); // si.iv: 200
	}
}

 

컴파일시 생성되는 클래스 파일

 

InnerClass4.class  main메서드를 담고 있는 public class Outer2.class  내부클래스가 있는 외부클래스
Outer$InstanceInner.class
Outer$StaticInner.class
Outer$1LocalInner.class
외부클래스명$내부클래스명.class”

예제5) 외부 클래스와 내부 클래스에 선언된 변수의 이름이 같을 변수 앞에 ‘this’ 또는 외부 클래스명.this’ 붙여서 서로 구별할 있다.

 

public class InnerClass5 {
	public static void main(String[] args) {
		Outer3 outer = new Outer3();            // 참조변수outer로 Outer3객체 생성
		Outer3.Inner in = outer. new Inner();   // Outer3의 Inner클래스의 참조변수 in으로 Inner 객체 생성
		in.method1();						    // 참조변수in으로 method1 호출
	}
}
class Outer3 {
	int value = 10; // Outer3.this.value 외부 클래스의 iv

	class Inner {
		int value = 20; // this.value 내부 클래스의 iv

		void method1() {
			int value = 30; // value lv(지역변수) 
			System.out.println("value: " + value);  // value: 30
			System.out.println("this.value: " + this.value); // this.value: 20
			System.out.println("Outer3.this.value: " + Outer3.this.value); //Outer3.this.value: 10
		}
	} // Inner 클래스의 끝
} // Outer3 클래스의 끝

익명 클래스(anonymous class)

이름이 없는 일회용 클래스. 정의와 생성을 동시에

이름이 없어서 생성자도 가질 수 없고 단 하나의 클래스를 상속받거나 단 하나의 인터페이스 만을 구현할 수 있다.

 

 

예제1) 익명클래스의 사용 예

class Anonymous {
	Object iv = new Object() { void method() {} }; // 익명클래스
    
	static Object cv = new Object() { void method() {} }; // 익명클래스

	void myMethod() {
		Object lv = new Object() { void method() {} }; // 익명클래스
	}
}

 

컴파일시 생성되는 클래스 파일

Anonymous.class

Anonymous$1.class

Anonymous$2.class

Anonymous$3.class

-> 익명클래스는 이름이 없기 때문에 “외부클래스명$숫자.class”의 형식


예제2) 일반 클래스 -> 익명 클래스

 

// 일반 클래스
public class AnonymousClass {
	public static void main(String[] args) {
		Button b = new Button("start");
		b.addActionListener(new EventHandler());
	}
}

class EventHandler implements ActionListener {
	public void actionPerformed(ActionEvent e) {
		System.out.println("ActionEvent occurred!!!");
	}
}

// 익명 클래스
public class AnonymousClass {
	public static void main(String[] args) {
		Button b = new Button("start");
		b.addActionListener(new ActionListener() { //클래스의 정의와 객체 생성을 동시에
			public void actionPerformed(ActionEvent e) {
				System.out.println("ActionEvent occurred!!!");
			}
		});
	}
}

 

더보기
더보기
더보기

1) b.addActionListener(new EventHandler) -> b.addActionListener(new ActionListener( ) { }

class의 이름인 EventHandler가 아닌 객체인 ActionListener를 넣기(인터페이스)

2) class EventHandler의 내용을 그대로 b.addActionListener(new ActionListener( ) { } 안에 넣기