이것이 자바다 정리 #5 중첩 클래스와 중첩 인터페이스
이것이 자바다 책을 참고하였습니다.
여러 클래스와 관계를 맺을 필요 없이, 어떠한 클래스 내부에서만 관계를 맺어도 된다면, 중첩 클래스를 사용하여 불필요한 관계를 감추고 높은 접근성을 얻을 수 있다.
public class Class {
class NestedClass {
}
interface NestedInterface {
}
}
주로 UI 프로그래밍에서 이벤트를 처리할 목적으로 많이 활용된다. UI 내부에 삽입되는 인터페이스 혹은 클래스는 주로
View
클래스 내부에 자리잡아서View
와만 관계를 맺게 된다.
public class Class {
class MemberClass { ... }
}
public class Class {
static class MemberClass { ... }
}
public class Class {
void method() {
class LocalClass { ... }
}
}
method()
가 실행될 때만 사용할 수 있는 중첩 클래스이다.public
, static
)와 static
을 붙일 수 없다.void method() {
class DownloadThread extends Thread { ... }
DownloadThread thread = new DownloadThread();
thread.start();
}
쓰레드는 뒤에 챕터에서 자세히 설명한다.
바깥클래스명$멤버클래스명.class
바깥클래스명$1로컬클래스명.class
익명 구현 객체와 동일하다.
기본적으로 중첩 클래스는 정적 클래스 혹은 인스턴스로 생성되기 때문에 접근 제한이 걸린다.
생각해보면 당연히 정적 영역이 만들어질 때, 클래스는 객체화되지 않고 설계도 상태로만 있으므로, 클래스의 정적 필드나 정적 메소드 내부에 일반 객체가 들어갈 수 없다.
final
형태로 변한다.package com.company.nested_class;
public class OuterClass {
public String instanceField = "instanceField";
public static String staticField = "staticField";
class InnerClass {
String innerClassInstanceField = instanceField;
String innerClassStaticField = staticField;
public void innerClassMethod() {
System.out.println(instanceField);
System.out.println(staticField);
}
/**
* ERROR: 일반 중첩 클래스는 내부에서 정적인 메소드 선언이 불가능하다.
*
* WHY: 중첩 클래스는 외부 클래스 생성 전에는 생성되지 않기 때문에
* JVM 메모리 중 메소드 영역에 상주해야 하는 static 을 사용할 수 없다.
*/
// public static void innerClassStaticMethod() {
//
// }
}
static class StaticInnerClass {
/**
* ERROR: 정적 중첩 클래스에서는 인스턴스 변수의 이용이 불가능하다.
*
* WHY: 외부 클래스의 인스턴스 멤버란 것은 외부 클래스가 객체화 되어야
* 실제적인 주소를 갖는다. 그런데, static 영역은 그 주소를 갖기 전에 초기화된다.
*/
// String staticInnerClassField = instanceField;
String staticInnerClassStaticField = staticField;
public void staticInnerClassMethod() {
// System.out.println(instanceField);
System.out.println(staticField);
}
public static void staticInnerClassStaticMethod() {
// System.out.println(instanceField);
System.out.println(staticField);
}
}
void outerMethod(final int arg1, int arg2, String fs) {
final int var1 = 1;
int var2 = 2;
class LocalClass {
void method() {
// 아래의 코드로 인해 사실상 모두 final 로 변경됨
int result = arg1 + arg2 + var1 + var2;
}
}
/**
* ERROR: 로컬 클래스에서 사용된 메소드 파라미터 및 매개변수는
* final 효과를 가져 수정할 수 없다.
*
* WHY: 클래스의 메소드 내부에서 생성된 로컬 클래스의 객체는
* 메소드 실행이 끝나도 힙 메모리에 존재해서 계속 사용할 수 있다.
* 반면에 메소드 블록에 존재하는 변수나 매개변수들은 메소드 실행이 끝나면,
* 스택에서 제거되어 더이상 사용할 수 없게 된다.
* final 로 이루어진 상수를 참조하는 것은 스택 메모리 영역을 사용하지 않아 괜찮은데,
* 변수를 참조하면 해당 메모리 영역 자체가 스택에서 지워지기 때문에 안된다.
*/
// arg2 = 10000;
}
}
this
를 사용하면 중첩 클래스의 객체를 가리킨다.바깥클래스명.this
를 해야 한다.public class OuterClass {
String field = "Outter-field";
Class NestedClass {
void printOuterField() {
System.out.println(OuterClass.this.field);
}
}
}
public class Button {
OnClickListener listener; // 인터페이스 타입 필드
void setOnClickListener(OnClickListener listener) {
this.listener = listener; // 매개변수의 다형성
// OnClickListener인터페이스를 상속하는 객체를 받을 수 있다.
}
// 터치 시 구현 객체의 메소드 호출
void touch() {
listener.onClick();
}
// 중첩 인터페이스
interface OnClickListener {
void onclick();
}
}
public class CallListener implements Button.OnclickListener {
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
public class ButtonExample {
public static void main(String[] args) {
Button btn = new Button();
btn.setOnClickListener(new CallListener());
btn.touch();
btn.setOnClickListener(new MessageListener());
btn.touch();
}
}
위와 같이 객체지향의 다형성을 극대화할 수 있다.
public class Parent {
int parentField = 0;
public void parentMethod() {
System.out.println("parentMethod called");
}
}
public class Main {
public static void main(String[] args) {
Parent parent = new Parent() {
@Override
public void parentMethod() {
System.out.println("It's anonymous object");
System.out.println("parentField = " + parentField);
}
};
parent.parentMethod();
}
}
Parent
를 상속한 익명의 자식객체이다.public interface Button {
void onClick();
}
public class Main {
public static void main(String[] args) {
Button button = new Button() {
@Override
public void onClick() {
System.out.println("클릭됨");
}
};
button.onClick();
}
}
public class Main {
public void method(int arg1) {
int localVariable = 10;
Button button = new Button() {
@Override
public void onClick() {
System.out.println("localVariable = " + localVariable);
System.out.println("arg1 = " + arg1);
}
};
// localVariable = 20;
// arg1 = 20;
}
}
위와 같은 코드가 있을 때는, 이전에 로컬 클래스와 같은 원리로 로컬 변수들에 자동으로 final
이 붙게 되므로, 익명 구현 객체 내부의 필드로 복사하거나 변경하지 말아야 한다.
public class Main {
public void method(int arg1) {
int localVariable = 10;
Button button = new Button() {
// 메소드 로컬 변수를 익명 객체 내부의 필드로 복사함
int anonymousClassLocalVariable = localVariable;
@Override
public void onClick() {
// 에러
localVariable = 20;
// 정상적인 수행 가능
anonymousClassLocalVariable = 20;
System.out.println("localVariable = " + localVariable);
System.out.println("arg1 = " + arg1);
}
};
}
}