클래스가 여러 클래스와 관계를 맺는 경우는 독립적으로 선언하는 것이 좋으나, 특정 클래스와 관계를 맺을 경우에는 관계 클래스를 클래스 내부에 선언하는 것이 좋다. 또한 인터페이스도 클래스 내부에 선언할 수 있다. 이런 인터페이스를 중첩 인터페이스라고 한다.
class ClassName {
class NestedClassName {
}
}
class ClassName {
interface NestedInterfaceName {
}
}
중첩 인터페이스는 주로 UI프로그래밍에서 이벤트를 처리할 목적으로 많이 활용된다. 예를 들어 안드로이드에서는 다음과 같이 View 클래스의 클릭 이벤트를 처리하는 구현 클래스를 만들 수 있다.
중첩 클래스
중첩 클래스는 클래스 내부에 선언되는 위치에 따라서 두 가지로 분류된다. 클래스의 멤버로서 선언되는 중첩 클래스를 멤버 클래스라고 하고, 메소드 내부에서 선언되는 중첩 클래스를 로컬 클래스라고 한다. 멤버 클래스는 클래스나 객체가 사용 중이라면 언제든지 재사용이 가능하지만, 로컬 클래스는 메소드 실행 시에만 사용되고, 메소드가 실행 종료되면 없어진다.
또한 멤버 클래스도 하나의 클래스이기 때문에 컴파일되면 바이트 코드 파일(.class)이 별도로 생성된다.
A $ B.class
로컬 클래스인 경우에는 $1이 포함된 바이트 코드 파일이 생성된다.
A $1 B.class
인스턴스 멤버 클래스
class A {
/** 인스턴스 멤버 클래스 **/
class B {
B() {}
int field1 // 인스턴스 필드
// static int field2 // 정적 필드 (x)
void method1() {} // 인스턴스 메소드
// static void method2() {} // 정적 메소드 (x)
}
}
또한 A클래스 외부에서 인스턴스 멤버 클래스 B의 객체를 생성하려면 먼저 A객체를 생성하고 B객체를 생성해야 한다.
A a = new A();
A.B b = a.new B();
b.field1 = 3;
b.method1();
< 하 엄청 많이 썼는데 날라갔다... >
중첩 클래스 Example
package com.company.double2;
public class A {
A() {
System.out.println("A 객체가 생성됨");
}
/** 인스턴스 멤버 클래스 **/
class B {
B() {
System.out.println("B 객체가 생성됨");
}
int field1;
void method1() {}
}
/** 정적 멤버 클래스 **/
static class C {
C() {
System.out.println("C 객체가 생성됨");
}
int field1;
static int field2;
void method1() {}
static void method2() {}
}
void method() {
/** 로컬 클래스 **/
class D {
D() {
System.out.println("D 객체가 생성됨");
}
int field1;
void method1() {}
}
D d = new D();
d.field1 = 3;
d.method1();
}
public static void main(String[] args) {
A a = new A();
// 인스턴스 멤버 클래스 객체 생성
A.B b = a.new B();
b.field1 = 3;
b.method1();
// 정적 멤버 클래스 객체 생성
A.C c = new A.C();
c.field1 = 3;
c.method1();
A.C.field2 = 3;
A.C.method2();
// 로컬 클래스 객체 생성을 위한 메소드 호출
a.method();
}
}
//A 객체가 생성됨
//B 객체가 생성됨
//C 객체가 생성됨
//D 객체가 생성됨
바깥 클래스에 대한 중첩 클래스에서의 참조 Example
package com.company.double2;
public class B {
int field1;
void method1() {}
static int field2;
static void method2() {}
class C {
void method() {
field1 = 10;
method1();
field2 = 10;
method2();
}
}
static class D {
void method() {
// field1 = 10;
// method1();
field2 = 10;
method2();
}
}
}
바깥 클래스를 this로 참조하기
package com.company.double2;
public class Outter2 {
String field = "Outter-field";
void method() {
System.out.println("Outter-method");
}
class Nested {
String field = "Nested-field";
void method() {
System.out.println("Nested-method");
}
void print() {
// 중첩 객체 참조
System.out.println(this.field);
this.method();
// 바깥 객체 참조
System.out.println(Outter2.this.field);
Outter2.this.method();
}
}
public static void main(String[] args) {
Outter2 outter = new Outter2();
Outter2.Nested nested = outter.new Nested();
nested.print();
}
}
//Nested-field
//Nested-method
//Outter-field
//Outter-method
로컬 클래스에서 사용 제한
package com.company.double2;
public class Outter {
public void method2(int arg) {
int localVariable = 1;
// arg = 100 (x)
// localVariable = 100 (x)
class Inner {
public void method() {
int result = arg + localVariable;
}
}
}
}
중첩 인터페이스
package com.company.double2;
public class Button {
OnClickListener listener;
void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
void touch() {
listener.onClick();
}
interface OnClickListener {
void onClick();
}
}
package com.company.double2;
public class CallListener implements Button.OnClickListener {
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
package com.company.double2;
public class MessageListener implements Button.OnClickListener{
@Override
public void onClick() {
System.out.println("메시지를 보냅니다.");
}
}
다음은 버튼을 클릭했을 때 두 가지 방법으로 이벤트를 처리하는 방법을 보여줍니다. 어떤 구현 객체를 생성해서 Button객체의 SetOnClickListener()메소드로 세팅하느냐에 따라서 Button의 touch() 메소드의 실행 결과가 달라집니다.
익명 객체
익명 객체는 단독으로 생성할 수 없고 클래스를 상속하거나 인터페이스를 구현해야만 생성할 수 있다. 익명 객체는 필드의 초기값이나 로컬 변수의 초기값, 매개 변수의 매개값으로 주로 대입된다.
우선 부모 클래스를 상속해서 자식 클래스를 선언하고 new연산자로 자식 객체를 생성한 후, 필드나 로컬 변수에 대입하는 것이 기본이다.
그러나 자식 클래스가 재사용되지 않고, 오로지 해당 필드와 변수의 초기값으로만 사용할 경우라면 익명 자식 객체를 생성해서 초기값으로 대입하는 것이 좋은 방법입니다.
또한 익명 자식 객체에 새롭게 정의된 필드와 메소드는 익명 자식 객체 내부에서만 정의되고, 외부에서는 필드와 메소드에 접근할 수 없다. 왜냐하면 익명 자식 객체는 부모 타입 변수에 대입되므로 부모 타입에 선언된 것만 사용할 수 있기 때문이다.
package com.company.double2.Anonymous;
public class Person {
void wake() {
System.out.println("7시에 일어납니다.");
}
}
package com.company.double2.Anonymous;
public class Anonymous {
// 필드 초기값으로 대입
Person field = new Person() {
void work() {
System.out.println("출근합니다.");
}
@Override
void wake() {
System.out.println("6시에 일어납니다.");
this.work();
}
};
void method1() {
// 로컬 변수값으로 대입.
Person localVar = new Person() {
void walk() {
System.out.println("산책합니다.");
}
@Override
void wake() {
System.out.println("7시에 일어납니다.");
this.walk();
}
};
localVar.wake();
}
void method2(Person person) {
person.wake();
}
}
package com.company.double2.Anonymous;
public class AnonymousExample {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
anony.field.wake();
anony.method1();
anony.method2(new Person() {
void study() {
System.out.println("공부합니다.");
}
@Override
void wake() {
System.out.println("8시에 일어납니다.");
this.study();
}
});
}
}
//6시에 일어납니다.
//출근합니다.
//7시에 일어납니다.
//산책합니다.
//8시에 일어납니다.
//공부합니다.
익명 구현 객체 생성
package com.company.double2.Anonymous;
public interface RemoteControl {
public void turnOn();
public void turnOff();
}
package com.company.double2.Anonymous;
public class Anonymous2 {
RemoteControl field = new RemoteControl() {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
};
void method1() {
RemoteControl localVar = new RemoteControl() {
@Override
public void turnOn() {
System.out.println("Audio를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("Audio를 끕니다.");
}
};
localVar.turnOn();
}
void method2(RemoteControl rc) {
rc.turnOn();
}
}
package com.company.double2.Anonymous;
public class AnonymousExample2 {
public static void main(String[] args) {
Anonymous2 anony = new Anonymous2();
anony.field.turnOn();
anony.method1();
anony.method2(new RemoteControl() {
@Override
public void turnOn() {
System.out.println("Smart TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("Smart TV를 끕니다.");
}
});
}
}
//TV를 켭니다.
//Audio를 켭니다.
//Smart TV를 켭니다.
익명 객체의 로컬 변수의 사용
익명 객체 내부에서는 바깥 클래스의 필드나 메소드는 제한없이 사용할 수 있다. 하지만 메소드의 매개 변수나 로컬 변수를 익명 객체에서 사용할 때이다.
익명 객체 내부에서 메소드의 매개 변수나 로컬 변수를 사용할 경우, 이 변수들은 final특성을 가져야 합니다. 자바 7이전까지는 반드시 final 키워드로 이 변수들을 선언해야 했지만, 자바 8이후부터는 final키워드 없이 선언해도 좋다. final키워드를 하지 않아도 여전히 값을 수정할 수 없는 final특성을 갖기 때문이다. 컴파일 시 final키워드가 있다면 메소드 내부에 지역변수로 복사되지만 final키워드가 없다면 익명 클래스의 필드로 복사된다.
void outMethod(final int arg1, int arg2) {
final int var1 = 1;
int var2 = 2;
인터페이스 변수 = new 인터페이스() {
void method() {
int result = arg1 + arg2 + var1 + var2;
}
}
}
인터페이스 변수 = new 인터페이스() {
int arg2 = 매개값;
int avar2 = 2;
void method() {
int arg1 = 매개값;
int var1 = 1;
int result = arg1 + arg2 + var1 + var2;
}
}
'School > Java Programming' 카테고리의 다른 글
Java Programming - Basic API Class (0) | 2022.03.24 |
---|---|
Java Programming - Exception (0) | 2022.03.17 |
Java Programming - Interface (0) | 2022.03.10 |
Java Programming - Inheritance (0) | 2022.03.09 |
Java Programming - Class (0) | 2022.03.09 |