중첩 class
01_ 중첩 클래스, 내부 클래스
- 클래스 내부에 또 다른 클래스를 정의 하는 것
- 중첩 클래스는 4종류가 있고 크게 두 가지로 분류
* static : 정적 중첩 클래스
* non static : 내부,지역,익명 클래스
1) 중첩 클래스의 선언 위치
2) 중첩과 내부의 개념
- 중첩 (Nested) : 나의 안에 있지만 내것이 아닌 것
ex) 큰 나무 상자안에 다른 나무 상자가 들어 있는 것 (두 상자 내부에 존재하는 요소가 다르다!!)
- 내부 (Inner) : 나의 내부에서 나를 구성하는 요소
ex) 나의 심장,간,콩팥은 나의 내부를 구성 하는 요소이다.
※ 중첩과 내부를 구분하는 기준은 인스턴스(같은 인스턴스 or 다른 인스턴스)에 있다.
- 정적 중첩 클래스 인스턴스 소속 x
- 내부 클래스는 바깥 클래스 구성 함 인스턴스 소속 o
2-1) 중첩 클래스
- static 이 붙는다
- 바깥 클래스의 인스턴스에 소속 되지 않음
ex ) NestedOuter (바깥 클래스) 와 Nested (정적 중첩 클래스) 를 생성 후, main() 메서드 생성 시:
- main() 에서 중첩 클래스 Nested 접근: NestedOuter(바깥).Nested(중첩) nested (객체) = new .NestedOuter.Nested(); 와 같이 바깥 클래스.중첩클래스 로 접근 할 수 있다.
( Nested nested = new Nested(); 같이 단독으로는 객체 생성 불가능 !!) 무조건 바깥 매서드 . 붙여야함!!
중첩 클래스 생성 방법
code 1-1)
package nested.nested;
public class NestedOuter {
private static int outClassValue = 3; // static 필드 생성
private int outInstanceValue = 2; // instance value 멤버 생성
static class Nested { // 정적 중첩 클래스 Nested 생성
private int nestedInstanceValue = 1; // 멤버 변수 생성
public void print() { // 메소드 생성
// 자신의 멤버에 접근
System.out.println(nestedInstanceValue);
// 바깥 클래스의 인스턴스 멤버에는 접근에는 접근 할 수 없다. (static 일때만 접근 가능)
// System.out.println(outInstanceValue);
// 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private 도 접근 가능
System.out.println(outClassValue);
}
}
}
(※ 정리 : NestedOuter 와 Nested 는 아무런 관계가 없다 ( 단지, 두 클래스 모두 private 에 접근 할 수 있는 점) )
** 단, 나의 클래스 내에서 사용되는 중첩 클래스의 경우 main() 객체 생성 시 바깥클래스.중첩클래스 안해도 된다
그냥 중첩클래스만 선언
code 1-2) 바깥 클래스: Network / 중첩 클래스: NetworkMessage
package nested.nested.ex2;
public class Network {
// NetworkMessage 클래스를 Network 클래스 안에 중첩해서 생성 (ex1 리팩토링)
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}
// 중첩 클래스 내부에서 선언
private static class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
}
>> 중첩 클래스 용도 : 자신이 소속된 바깥 클래스 안에서 사용되는 것.
자신이 소속된 바깥 클래스 외부에서 중첩 클래스를 사용할 경우, 해당 중첩클래스를 바깥으로 빼야 적절함.
2-2) 내부 클래스
- static 이 붙지 않는다
- 바깥 클래스의 인스턴스에 소속 됨
- 내부 클래스's 특징 (포함 관계 : 내부클래스 < 지역 클래스 < 익명 클래스)
> 내부 클래스 (Inner Class) : 바깥 클래스의 인스턴스 멤버에 접근
> 지역 클래스 (Local Class) : 내부 클래스의 특징 + 지역 변수에 접근
> 익명 클래스 (Anonymous Class) : 지역 클래스의 특징 + 클래스의 이름이 없는 특별한 클래스
내부 클래스 생성 방법
code 1-1 ) 바깥 클래스 : InnerOuter / 내부 클래스 : Inner
package nested.inner;
public class InnerOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
class Inner { // 내부 클래스 생성
private int innerInstanceValue = 1;
public void print() {
// 자기 자신에 접근
System.out.println(innerInstanceValue);
// 외부 클래스의 인스턴스 멤버에 접근 가능, (private 도 접근 가능)
System.out.println(outInstanceValue);
// 외부 클래스 클래스멤버에 접근 가능
System.out.println(outClassValue);
}
}
}
code 1-2) 내부클래스 생성 방법
package nested.inner;
public class InnerOuterMain {
public static void main(String[] args) {
// 내부 클래스 접근 방법
InnerOuter outer = new InnerOuter(); // step1. 바깥 클래스 참조 값을 먼저 생성
InnerOuter.Inner inner = outer.new Inner(); // step2. 해당 바깥 클래스의 내부 클래스 참조 생성
}
}
** main() 메서드에서 내부 클래스 생성 방법
1. 먼저 바깥 클래스 InnerOuter의 참조값을 불러온다. new InnerOuter(); + ( Ctrl + Alt + v)
(outer 로 객체 이름 생성)
2. 바깥 클래스 참조 값을 알게되야 내부 클래스인 Inner 를 생성 할 수 있다. outer.new Inner(); + ( Ctrl + Alt + v)
정리 : 바깥 클래스의 참조값을 알아야 내부 클래스를 생성/사용 가능 !!
(>내부 클래스가 바깥 클래스의 인스턴스를 참조 하기 때문)
※ 개념 : 바깥클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성 된다
내부 인스턴스는 바깥 인스턴스를 알기 떄문에 바깥 인스턴스 멤버에 접근 가능한 것이다.
03_ 쉐도윙 (Shadowing)
package nested;
public class ShadowingMain {
// 마지막으로 내부클래스 바깥에 있는 value = 1
public int value = 1;
class Inner { // 두번째로 근접한 value = 2
public int value = 2;
void go() { // 내부 클래스와 가장 근접한 value = 3
int value = 3;
System.out.println("value = " + value);
System.out.println("this.value = " + this.value);
System.out.println("ShadowingMain.value = " + ShadowingMain.this.value);
}
}
public static void main(String[] args) {
ShadowingMain main = new ShadowingMain();
Inner inner = main.new Inner();
inner.go();
}
}
- 인스턴스 값 호출 시, 가장 근접한 값 부터 .(dot) 으로 접근 한다.
>> value
>> this.value
>> ShawdowingMain.this.value
04_ 지역 클래스
- 지역 클래스는 지역 변수처럼 코드 블럭 안에 클래스를 선언 한다.- 지역 클래스는 지역 변수에 접근 가능하다.
1) 지역 변수 캡처
- 변수의 생명 주기
지역 변수는 값이 변경 되면 안된다 (사실상 final)
05_ 익명 클래스
익명 클래스 생성 예)
public interface Printer {
void print();
}
package nested.anonymous;
import nested.local.Printer;
public class AnonymousOuter {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1; // 지역 변수
Printer printer = new Printer() { // 익명 클래스 : 선언과 동시에 생성
int value = 0;
@Override
public void print() {
System.out.println("value = " + value);
System.out.println("localVar = " + localVar);
System.out.println("paramVar = " + paramVar);
System.out.println("outInstanceVar = " + outInstanceVar);
}
};
printer.print();
System.out.println("printer.class = " + printer.getClass());
}
public static void main(String[] args) {
AnonymousOuter main = new AnonymousOuter();
main.process(2);
}
}
위의 익명클래스 선언하는 코드 라인만 보면 Printer 인터페이스를 생성하는 것 처럼 보이지만 사실
Printer 인터페이스를 구현한 익명클래스를 생성하는 것
특징 :
- 익명클래스는 클래스의 body를 정의하면서 동시에 생성 가능
- 익명클래스는 부모 클래스를 상속 받거나, 인터페이스를 구현해야 한다. (익명 클래스 사용 시 상위 클래스 or 인터페이스 필요)
- 익명클래스는 생성자를 가질 수 없음 (이름이 없기 때문)
- 익명클래스는 바깥클래스 + $ +n 으로 정의됨 ($1, $2, $3 .... )
지역클래스로 만들면 main() 에서 생성자 생성할 필요 없어짐code)
package nested.anonymous.ex;
import java.util.Random;
public class Ex1RefMain3 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
process.run();
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
Process dice = new Process() { // 익명 클래스 생성
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
}
};
Process sum = new Process() { // 익명 클래스 생성
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
}
};
System.out.println("Hello 실행");
hello(dice); // 바로 run() 메서드를 통해 dice 와 hello 호출 가능
hello(sum);
}
}
>> 리팩토링 (익명클래스를 hello 메서드 내부로 보냄 = 인수로 전달)
package nested.anonymous.ex;
import java.util.Random;
public class Ex1RefMain4 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
process.run();
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
System.out.println("Hello 실행");
hello(new Process() { // 익명 클래스를 hello 에다가 넣어버림
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
}
});
hello(new Process() { // 익명 클래스를 hello 에다가 넣어버림
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
}
});
}
}