Inner Class(내부 클래스)

Agnes Park·2022년 3월 13일
0

JAVA

목록 보기
30/34

1. Inner Class(내부 클래스) > 클래스 안에서 정의 - Member Inner Class

1. Member inner class(멤버 내부 클래스) : 다른 클래스 내부에서 선언된 클래스
2. Static inner class(static 내부 클래스) : 다른 클래스의 내부에서 static으로 선언된 클래스
3. Local class(지역 클래스) > 메소드 안에서 정의
1) Local inner class(지역 내부 클래스) : 메소드 내부에서 선언된 클래스
2) Anonymous inner class(익명 내부 클래스): 이름이 없는 local class

package com.lec.java.inner01;

public class Inner01Main {

	public static void main(String[] args) {
		System.out.println("Member Inner Class(멤버 내부 클래스)");
		
		// 외부 클래스의 인스턴스 생성
		TestOuter out = new TestOuter(100);
		
		// 멤버 내부 클래스의 인스턴스 생성
		// 멤버 내부 클래스의 이름: [외부클래스 이름].[멤버 내부클래스 이름]
		// [외부클래스 이름].[내부클래스 이름] 참조변수 =
		//		[외부클래스 인스턴스].new 내부클래스 생성자();
		TestOuter.TestInner in = out.new TestInner(111);
		in.printOuterValue();
		in.printInnerValue();
		
		// 하나의 외부 클래스 인스턴스를 이용해서
		// 멤버 내부 클래스의 인스턴스는 여러개를 생성할 수 있다.
		TestOuter.TestInner in2 = out.new TestInner(123);
		in2.printOuterValue();
		in2.printInnerValue();
		
		TestOuter.TestInner in3 = out.new TestInner(999);
		in3.printOuterValue();
		in3.printInnerValue();
		
		TestOuter.TestInner in4 = new TestOuter(30).new TestInner(330);
		in4.printOuterValue();
		in4.printInnerValue();
		
	} // end main()

} // end class Inner01Main
package com.lec.java.inner01;

/*
 	Member inner class(멤버 내부 클래스)
 	
	TestOuter 클래스 내부에서 TestInner 클래스를 정의
	TestOuter: 외부 클래스(Outer Class, Enclosing Class)
	TestInner: 멤버 내부 클래스(Member Inner Class)
	1) 멤버 내부 클래스는 외부 클래스의 인스턴스가 생성된 이후에야 
	인스턴스 생성이 가능함.
	2) 멤버 내부 클래스는 외부 클래스의 모든 멤버들(private 포함)을 사용 가능
*/


// 클래스: 멤버 변수들 (+ 생성자들) + 멤버 메소드들 = 데이터 타입
public class TestOuter {
	// 멤버 변수:
	private int value;
	
	// 생성자:
	public TestOuter() {}
	public TestOuter(int value) {
		this.value = value;
	}
	
	// 메소드:
	public int getValue() {
		return value;
	}
	public void setValue(int value) {
		this.value = value;
	}

	
	// Member Inner Class 정의:
	public class TestInner {
		// 멤버변수
		private int innerValue;
		
		// 생성자
		public TestInner() {}
		public TestInner(int val) {
			this.innerValue = val;
		}
		
		// 멤버 메소드
		public void printOuterValue() {
			System.out.println("value = " + value);
			// 멤버 내부 클래스는 외부 클래스의 멤버를 직접 접근 가능
		}
		
		public void printInnerValue() {
			System.out.println("innerValue = " + innerValue);
		}
		
	}
	
	
} // end class TestOuter

[멤부 내부 클래스 활용 코드]

package com.lec.java.inner02;

/*
 	언제 내부 클래스로 설계?  
 	
	 상속 관계로 묶을 수는 없지만,
	 A라는 객체가 생성된 이후에야 존재할 수 있는 B라는 객체가 있다고 한다면,
	 B를 A의 멤버 '내부 클래스'로 선언한다.
	 (예) 컴퓨터-CPU/메모리, 자동차-타이어
	
	 반면
	 '햄버거 세트 메뉴' 객체의 경우
	 햄버거 객체와 콜라 객체는 별개의 객체로도 존재 가능하니까
 	'햄버거' 와 '콜라' 는 '세트메뉴' 객체의 '멤버변수'로 붙도록 하는게 낳다
 	
 	is-a  : 상속관계
 	has-a (종속) : 멤버내부클래스
 	has-a (독립) : 멤버변수
 	
 */
public class Inner02Main {

	public static void main(String[] args) {
		System.out.println("멤버 내부 클래스 활용");
		
		Car myCar = new Car("Indigo Blue");
		Car.Tire myTire1 = myCar.new Tire(17);
		Car.Tire myTire2 = myCar.new Tire(19);
		
		myTire1.displayInfo();
		System.out.println();
		myTire2.displayInfo();
				
		
	} // end main()

} // end class Inner02Main
package com.lec.java.inner02;

public class Car {
	// 멤버 변수( outer )
	private String color;
	
	// 생성자
	public Car(String color) {
		this.color = color;
	}
	
	// 메소드
	public void displayCarInfo() {
		System.out.println("color: " + color);
	}
	
	// 멤버 내부 클래스
	public class Tire {
		private int radius;
		
		public Tire(int radius) {
			this.radius = radius;
		}
		
		public void displayInfo() {
			System.out.println("--- 타이어 정보 ---");
			System.out.println("차량 color: " + color);
			System.out.println("tire : " + radius);
		}
	}
	
} // end class Car

[외부/내부 클래스의 this 코드 예시]

package com.lec.java.inner03;

public class Inner03Main {

	public static void main(String[] args) {
		System.out.println("외부/내부 클래스의 this");
		
		TestOuter out = new TestOuter(100);	// 1.
		TestOuter.TestInner in1 = out.new TestInner(111); // 2.
		in1.printValue(10);	// 3.

	} // end main()

} // end class Inner03Main
package com.lec.java.inner03;

public class TestOuter {
	private int value;	// 1.
	
	public TestOuter(int value) {
		this.value = value;
	}
	
	public int getValue() {
		return value;
	}
	
	// 멤버 내부 클래스
	public class TestInner {
		private int value;	// 2.
		
		public TestInner(int value) {
			this.value = value;
		}
		
		public void printValue(int value) {	// 3.
			System.out.println("value = " + value);	// 3.
			System.out.println("this.value = " + this.value);	// 2.
			System.out.println("TestOuter.this = "  + TestOuter.this.value);
		}
	}

	
} // end class TestOuter

2. Nested Class(중첩 클래스) - Static Inner Class

Nested Class(중첩 클래스):

  • 다른 클래스의 내부에서 멤버로 정의된 클래스인데, static 키워드가 사용된 내부 클래스 (static inner class)
  • static : 클래스의 인스턴스가 생성되지 않아도 사용될 수 있는 멤버(변수, 메소드)에 사용
    따라서, nested class는 외부 클래스의 인스턴스를 생성하지 않고, 내부 클래스의 인스턴스를 생성할 수 있다.

nested(static) class는 (외부 클래스에서) static으로 선언된 변수와 메소드만 사용 가능

중첩 클래스의 인스턴스 생성:
타입 참조변수 = new 생성자()
중첩 클래스의 이름(타입): [외부클래스 이름].[내부클래스 이름]
중첩 클래스의 생성자: new [외부클래스 이름].생성자()

package com.lec.java.inner04;

public class Nested01Main {

	public static void main(String[] args) {
		System.out.println("Nested Class(중첩 클래스): static inner class");

		TestOuter.TestNested nest1 = new TestOuter.TestNested();
		nest1.displayInfo();
		
		TestOuter.TestNested.println();
		
		
	} // end main()

} // end class Nested01Main
package com.lec.java.inner04;

public class TestOuter {

	// 멤버변수
	private int value;  // 인스턴스 변수
	private static int count = 100; // 클래스 변수 (static)
	
	// 생성자
	public TestOuter(int value) {
		this.value = value;
	}
	
	public static class TestNested {
		
		public void displayInfo() {
//			System.out.println(value);	static 클래스에서 외부의 non-static 사용 못함
			System.out.println("count = " + count);
		}
		
	public static void println() {}
	}
	
} // end class TestOuter


// TestOuter: 외부 클래스(outer class, enclosing class)
// TestNested: 중첩 클래스(nested class, static inner class)

3. Local Inner Class

Local Inner Class: 블록({ ... }) 내부에서 정의된 클래스
1. 정의가 된 블록(메소드) 내부에서만 사용 가능 - 참조변수 선언, 인스턴스 생성
2. 접근 수식어(public, protected, private)는 쓸 수 없다.
3. 외부 클래스의 멤버 변수(private 포함)는 모두 사용 가능
4. effectively final인 지역변수나 매개변수만 사용 가능

effectively final 변수란?
1) final로 선언된 변수, 또는
2) 한 번 초기화가 된 이후로 값이 변경되지 않은 변수(Java 8에서 도입)

package com.lec.java.inner05;

public class Local01Main {

	public static void main(String[] args) {
		System.out.println("Local Inner Class(지역 내부 클래스)");

		// 외부 클래스 생성
		TestOuter out = new TestOuter();
		out.localMethod();
		
		System.out.println();
		TestOuter2 out2 = new TestOuter2();
		out2.localMethod();
		
	} // end main()

} // end class Local01Main
package com.lec.java.inner05;

public class TestOuter {
	// TestOuter 클래스의 멤버 변수
	private int num1 = 100;
	
	// TestOuter 클래스의 멤버 메소드
	public void localMethod() {
	
		int num2 = 200;
		
		// localMethod() 내부에서 정의된 Local inner class
		class TestLocal {
			
			private int num3 = 300;
			
			public void showNumbers() {
				// 외부 클래스의 멤버변수 출력
				System.out.println("num1 = " + num1);
				
				// 지역클래스의 동일 scope 의 지역변수 출력
				System.out.println("num2 = " + num2);
				
				// 로컬 내부 클래스 (자신)의 멤버변수 출력
				System.out.println("num3 = " + num3);
			}
			
		} // end local inner class
		
		// 지역 내부 클래스 인스턴스 생성은 클래스가 정의된 메소드(블럭) 안에서만 가능
		TestLocal local = new TestLocal();
		
		// num2 = 400;	// num2 값을 변경하면.. 아래 showNumbers()에선
					// 200 이 찍혀야 하나? 400이 찍혀야 하나?
					// 그래서 로컬내부클래스에서 사용 가능한 지역의 변수는
					// 반드시 effectively final 이어야 한다
					//	즉 한번 초기화 된 후 값이 변경되지 않거나, final 이어야 한다
		
		local.showNumbers();
		
		
	} // end localMethod()
	

} // end class TestOuter
package com.lec.java.inner05;

public class TestOuter2 {
	 
	//TestOuter 클래스의 멤버 변수
	private int num = 100; // ① 
	
	// TestOuter 클래스의 멤버 메소드
	public void localMethod() {
		
		int num = 200;	// ②
		
		class TestLocal {
			
			private int num = 300;	// ③
			
			public void showNumber() {
				
				int num = 400;	// ④
				
				// ①, ②, ③, ④ 출력 가능?
				
				System.out.println("TestOuter2.this.num = " + TestOuter2.this.num); // ①
				System.out.println("this.num = " + this.num);	// ③
				System.out.println("num = " + num);	// ④
				
				// ②는 사용 불가!
				
			}
			
		}	// end local inner class
		
		TestLocal local = new TestLocal();
		local.showNumber();
		
	} // end localMethod()
	

} // end class TestOuter

[Local 내부 클래스의 활용 코드]

package com.lec.java.inner06;

public class Local02Main {

	public static void main(String[] args) {
		System.out.println("Local 내부 클래스의 활용");
		
		Person person = new Person("ABC");
		person.readAge(10);

		MyReadable r = person.createInstance(16);
		r.readInfo();	// 다른 클래스에서도 로컬클래스가 정의한 메소드를 사용 가능
		
	} // end main()

} // end class Local02Main
package com.lec.java.inner06;

public class Person {
	// Person 외부 클래스의 멤버 변수
	private String name;
	
	// Person 외부 클래스의 생성자
	public Person(String name) {
		this.name = name;
	}
	
	public void readAge(final int age) {
		
		// age = 10;
		
		// local inner class
		class PersonWithAge {
			public void readInfo() {
				System.out.println("이름: " + name);
				System.out.println("나이: " + age);
			}
		}
		
		PersonWithAge p = new PersonWithAge();
		p.readInfo();
		
	}
	
	/*
	public PersonWithAge createInstance(int age) {
		class PersonWithAge {
			public void readInfo() {
				System.out.println("이름: " + name);
				System.out.println("나이: " + age);
			}
		}
		
		PersonWithAge p = new PersonWithAge();
		return p;
	}
	*/
	
	/*
	 지역 클래스는 메소드 실행이 끝나게 되면 자체가 사라지게 되는 클래스임.
	 메소드 내부에 정의된 지역 클래스 타입을 리턴하는 메소드는 만들 수 없다.
	 경우에 따라서는, 지역 클래스에 정의된 메소드를
	 외부에서 직접 사용하고자 하는 경우가 발생할 수도 있습니다.
	 그걸 가능하게 하는 방법이
	 
	 인터페이스(interface) + 다형성(polymorphism):
	 
	 1. 외부에서 사용하고 싶은 메소드를 선언한 인터페이스를 작성
	 2. 메소드의 리턴타입은 정의한 인터페이스 타입으로 정의
	 3. 로컬 클래스는 인터페이스를 구현(implements)하도록 정의
	 4. 로컬 클래스의 인스턴스를 생성하고 리턴해줌
	 */
	
	
	// 2. 메소드의 리턴타입은 정의한 인터페이스 타입으로 정의
	public MyReadable createInstance(int age) {
		
		// 3. 로컬 클래스는 인터페이스를 구현(implements)하도록 정의
		class PersonWithAge implements MyReadable {
			@Override
			public void readInfo() {
				System.out.println("이름: " + name);
				System.out.println("나이: " + age);
			}
		}
		
		// 4. 로컬 클래스의 인스턴스를 생성하고 리턴해줌
		MyReadable person = new PersonWithAge();	// 다형성
		
		return person;
	}
	
} // end class Person


// 1. 외부에서 사용하고 싶은 메소드를 선언한 인터페이스를 작성

interface MyReadable {
	public abstract void readInfo();
}

4. Anonymous inner class(익명 내부 클래스)

Anonymous inner class(익명 내부 클래스):

  • 이름이 없는 local inner class
  • 이름이 없기 때문에 생성자로 만들 수가 없습니다.
  • 클래스의 정의와 동시에 인스턴스를 생성합니다.

익명 내부 클래스:

  • 인스턴스 생성과 동시에 이름없는 클래스가 정의됨.
    • new 인터페이스() { 익명 클래스 내부 작성 };
    • new 부모클래스() { 익명 클래스 내부 작성 };
  • 익명 내부 클래스 (Anonymous Inner class) 는 인터페이스 뿐 아니라, 일반 클래스, 추상클래스 등도 가능하다.
  • 상속 받은 (이름없는) 클래스의 인스턴스를 생성.
    • 멤버 변수/ 메소드 선언
    • 메소드 오버라이딩
package com.lec.java.inner07;

public class Anonymous01Main {

	public static void main(String[] args) {
		System.out.println("Anonymous Inner Class(익명 내부 클래스)");
		
		Person p = new Person("QWERTY");
		MyReadable r = p.createInstance(20);
		r.readInfo();
		
		
		//------------------------------------------------
		System.out.println();
		MyReadable r2 = new MyReadable() {
			
			int a = 10;	// 익명클래스의 멤버변수 추가
			
			@Override
			public void readInfo() {
				System.out.println("readInfo()");
				System.out.println("a = " + a);
			}
		};
		r2.readInfo();
		
		System.out.println(r2);
		
		new MyReadable() {
			
			int a = 200;
			
			@Override
			public void readInfo() {
				System.out.println(a + 100);
			}
		}.readInfo();
		
		//------------------------------------------------
		System.out.println();
		System.out.println(new MyClass().methodA(30));
		System.out.println(new MyClass() {
			int d = 400;
			
			@Override
			int methodA(int c) {
				return a + b + c + d;
			}
		}.methodA(30));

		
		int k = 300;
//		k= 150;	// 익명클래스는 기본적으로 local inner class 이기에
			// 동일 scope 의 지역변수를 사용할때는
			// 그 지역변수는 effective final 이어야 한다
		int result = new MyAbstract() {

			@Override
			int methodA(int a) {
				return a + n + d + k;
			}
			
		}.methodA(100);
		
		System.out.println("result = " + result);

		System.out.println("\n프로그램 종료");
	} // end main()

} // end class

//익명 내부 클래스 (Anonymous Inner class) 는
//인터페이스 뿐 아니라, 일반 클래스, 추상클래스 등도 가능하다.
//상속 받은 (이름없는) 클래스의 인스턴스를 생성.
//	- 멤버 변수/ 메소드 선언
//	- 메소드 오버라이딩 

abstract class MyAbstract{

	int n = 10;
	int d = 2;
	
	abstract int methodA(int a);

} // end class

class MyClass {

	int a = 10;
	int b = 20;
	
	int methodA(int c) {
		return a + b + c;
	}
	
} // end class
package com.lec.java.inner07;

public class Person {
	
	// 외부 클래스 멤버변수
	private String name;
	
	// 외부 클래스 생성자
	public Person(String name) {
		this.name = name;
	}
	
	public MyReadable createInstance(int age) {
		
		// 익명 내부 클래스:
		// 인스턴스 생성과 동시에 클래스가 정의됨.
		// new 인터페이스() { 익명 클래스 내부 작성 };
		// new 부모클래스() { 익명 클래스 내부 작성 };
		
		// MyReadable 을 implement 한 이름없는 익명 class 를 정의하고 그 instance 생성
		// 익명클래스 구문 사용!!
		
		return new MyReadable() {
			
			@Override
			public void readInfo() {
				System.out.println("이름: " + name);
				System.out.println("나이: " + age);
			}
		};
		
	}


} // class Person

interface MyReadable {
	public abstract void readInfo();
}

[익명 내부 클래스 활용 코드]

package com.lec.java.inner08;

public class Anonymous02Main {

	public static void main(String[] args) {
		System.out.println("익명 내부 클래스 활용");
		
		System.out.println();
		System.out.println("1. 이름있는 클래스를 사용하여 구현");
		Calculable tc1 = new TestMyMath();
		double result = tc1.operate(1.0, 2.0);
		System.out.println("result = " + result);
		
		System.out.println();
		System.out.println("2. 익명 클래스 사용");
		Calculable tc2 = new Calculable() {
			
			@Override
			public double operate(double x, double y) {
				return x - y;
			}
		};
		result = tc2.operate(1.0, 2.0);
		System.out.println("result = " + result);
		
		
	} // end main()

} // end class Anonymous02Main

interface Calculable {
	public abstract double operate(double x, double y);
	
}

/*
위와 같이 특정 추상 메소드만 implement 하는 목적으로 설계되는 인터페이스의 이름은
보통 ~ able 로 작명 경우가 많다.  
자바에서 제공하는 대표적으로 많이 사용하는 이러한 인터페이스들.
Serializable, Cloneable, Readable, Appendable, Closeable,  
AutoCloseable, Observable, Iterable, Comparable, Runnable,
Callable, Repeatable, 
*/


class TestMyMath implements Calculable {
	@Override
	public double operate(double x, double y) {
		return x + y;
	}
	
}

0개의 댓글