Programming Language/자바(JAVA)
[JAVA] 중첩 클래스, 중첩 인터페이스, 익명 객체
토자맨
2024. 11. 25. 03:09
중첩 클래스
- 클래스 내부에 선언한 클래스
- 특정 클래스만 관계를 맺을 경우 중첩 클래스로 선언 -> 유지보수 도움
- 클래스 멤버 쉽게 사용할 수 있고 외부에는 중첩 관계 클래스 감춤으로써 코드 복잡성↓
- 중첩 클래스도 컴파일하면 바이트코드 파일(.class)이 별도로 생성된다.
- 파일 이름
- 멤버 클래스: A(바깥 클래스) $ B(멤버 클래스) .class
- 로컬 클래스: A(바깥 클래스) $1 B(로컬 클래스) .class
- 파일 이름
인스턴스 멤버 클래스
- 인스턴스 멤버 클래스 B는 주로 A 클래스 내부에서 사용되므로 private 접근 제한자 갖는다.
- B 객체는 A 객체가 있어야 사용 가능하기 때문에 A 클래스 내부 아무데서나 사용 불가능
- 필드, 생성자, 메소드에서 B 객체 생성 가능
public class A {
// ★ 인스턴스 멤버 클래스 ★
class B {
// 인스턴스 필드
int field1 = 1;
// 정적 필드(Java 17부터 허용)
static int field2 = 2;
// 생성자
B() {
System.out.println("B-생성자 실행");
}
// 인스턴스 메소드
void method1() {
System.out.println("B-method1 실행");
}
// 정적 메소드(Java 17부터 허용)
static void method2() {
System.out.println("B-method2 실행");
}
}
// 인스턴스 메소드
void useB() {
// B 객체 생성 및 인스턴스 필드 및 메소드 사용
B b = new B();
System.out.println(b.field1);
b.method1();
// B 클래스의 정적 필드 및 메소드 사용
System.out.println(B.field2);
B.method2();
}
// 필드, 생성자, 메소드에서만 B 객체 생성 가능
/*
// 인스턴스 필드 값으로 B 객체 대입
B field = new B();
// 생성자
A() {
B b = new B();
}
// 인스턴스 메소드
void method() {
B b = new B();
}
*/
}
// B 객체를 A 클래스 외부에 생성
// - A 객체 생성 후 B 객체 생성. default 또는 public 접근 제한을 가져야 한다.
public class AExample {
public static void main(String[] args) {
// A 객체 생성
A a = new A();
// A 인스턴스 메소드 호출
a.useB();
// // B 객체 생성
// A.B b = a.new B();
}
}
/*
실행 결과
B-생성자 실행
1
B-method1 실행
2
B-method2 실행
*/
정적 멤버 클래스
- 정적 멤버 클래스 B 내부에는 필드, 생성자, 메소드 선언이 올 수 있다.
로컬 클래스
- 생성자 or 메소드 내부에 선언된 클래스
- 로컬 변수(생성자 또는 메소드의 매개변수 또는 내부에서 선언된 변수)를 로컬 클래스에서 사용할 경우 로컬 변수는 final 특성을 갖게 되므로 값을 읽을 수만 있고 수정할 수 없게 된다.
public class A {
// 메소드
public void method1(int arg) { // final int arg
// 로컬 변수
int var = 1; // final int var = 1;
// 로컬 클래스
class B {
// 메소드
void method2() {
// 로컬 변수 읽기 - 가능
System.out.println(arg);
System.out.println(var);
// 로컬 변수 수정 - X
// arg = 2;
// var = 2;
}
}
// 로컬 객체 생성
B b = new B();
// 로컬 객체 메소드 호출
b.method2();
// 로컬 변수 수정 - X
// arg = 3;
// var = 3;
}
}
바깥 멤버 접근
- 중접 클래스가 어떻게 선언되었느냐에 따라 바깥 클래스의 필드와 메소드에 접근 제한 있을 수 있음
- 인스턴스 멤버 클래스
- 바깥 클래스의 모든 필드와 메소드
- 정적 멤버 클래스(static)
- 바깥 클래스의 정적(static) 필드와 정적(static) 메소드
- 인스턴스 멤버 클래스
public class A {
// A의 인스턴스 필드와 메소드
int field1;
void method1() { }
// A의 정적 필드와 메소드
static int field2;
static void method2() { }
// 인스턴스 멤버 클래스
class B {
void method() {
// A의 인스턴스 필드와 메소드 사용
field1 = 10; // O
method1(); // O
// A의 정적 필드와 메소드 사용
field2 = 10; // O
method2(); // O
}
}
// 정적 멤버 클래스
static class C {
void method() {
// A의 인스턴스 필드와 메소드 사용 - X
// field1 = 10;
// method1();
// A의 정적 필드와 메소드 사용 - O
field2 = 10;
method2();
}
}
}
바깥 클래스의 객체 접근
- 중첩 클래스 내부에서 this는 해당 중첩 클래스의 객체
- 중첩 클래스 내부에서 바깥 클래스의 객체를 얻으려면 바깥클래스이름.this
바깥클래스이름.this.메소드이름()
public class A {
// A 인스턴스 필드
String field = "A-field";
// A 인스턴스 메소드
void method() {
System.out.println("A-method");
}
// 인스턴스 멤버 클래스
class B {
// B 인스턴스 필드
String field = "B-field";
// B 인스턴스 메소드
void method() {
System.out.println("B-method");
}
void print() {
// B 객체의 필드와 메소드 사용
System.out.println(this.field);
this.method();
// A 객체의 필드와 메소드 사용
System.out.println(A.this.field);
// ★ 바깥클래스이름.this ★
A.this.method();
}
}
// A의 인스턴스 메소드
void useB() {
B b = new B();
b.print();
}
}
public class AExample {
public static void main(String[] args) {
// A 객체 생성
A a = new A();
// A 메소드 호출
a.useB();
}
}
/*
실행 결과
B-field
B-method
A-field
A-method
*/
중첩 인터페이스
- 클래스의 멤버로 선언된 인터페이스
- 해당 클래스와 긴밀한 관계를 맺는 구현 객체를 만들기 위함
- UI 프로그램에서 이벤트를 처리할 목적으로 많이 활용
public class Button {
// 정적 중첩 인터페이스
// - public이면서 Button 객체 없이 사용할 수 있는 static 중첩 인터페이스
public static interface ClickListener {
// 추상 메소드
void onClick();
}
// 필드
private ClickListener clickListener;
// 메소드
public void setClickListener(ClickListener clickListener) {
this.clickListener = clickListener;
}
public void click() { // Button이 클릭되었을 때 실행하는 메소드 선언
this.clickListener.onClick();
}
}
public class ButtonExample {
public static void main(String[] args) {
// Ok 버튼 객체 생성
Button btnOk = new Button();
// Ok 버튼 클릭 이벤트를 처리할 ClickListener 구현 클래스(로컬 클래스)
class OkListener implements Button.ClickListener {
@Override
public void onClick() {
System.out.println("Ok 버튼을 클릭했습니다");
}
}
// Ok 버튼 객체에 ClickListener 구현 객체 주입
btnOk.setClickListener(new OkListener());
// Ok 버튼 클릭하기
btnOk.click();
// Cancel 버튼 객체 생성
Button btnCancel = new Button();
// Cancel 버튼 클릭 이벤트를 처리할 ClickListener 구현 클래스(로컬 클래스)
class CancelListener implements Button.ClickListener {
@Override
public void onClick() {
System.out.println("Cancel 버튼을 클릭했습니다");
}
}
// Cancel 버튼 객체에 ClickListener 구현 객체 주입
btnCancel.setClickListener(new CancelListener());
// Cancel 버튼 클릭하기
btnCancel.click();
}
}
/*
실행 결과
Ok 버튼을 클릭했습니다
Cancel 버튼을 클릭했습니다
*/
익명 객체
- 이름이 없는 객체
- 명시적으롴 클래스를 선언하지 않고 객체 생성
- 즉, 따로 클래스나 인터페이스를 구현한 뒤 오버라이딩하지 않고 필요할 때 객체 생성과 동시에 오버라이딩하여 객체 생성
- 익명 자식 객체
- 클래스 상속해서 만들 경우
- 익명 구현 객체
- 인터페이스를 구현해서 만들 경우
- 즉, 일시적으로 사용되어 재사용되지 않고 나중에 버려지기 객체를 의미한다.
- 일시적으로 한 번만 사용되는 경우
- 재사용성 없고 확장성 활용하는 것이 유지보수에서 더 불리할 때
예를 들어 식물이라는 부모 클래스를 상속받는 10만개의 식물이 있다고 하자.
이 식물이 단발성으로 등장해서 각자 무언가를 하고(메소드 실행) 사라진다면 10만개의 식물 클래스를 별도로 만드는 것은 의미가 없다.
이런 경우 익명 객체를 사용한다.
익명 자식 객체
- 부모 타입의 필드, 로컬 변수, 매개변수의 값으로 대입 가능
new 부모생성자(매개값, ...) { // 필드 // 메소드 }
public class Tire {
public void roll() {
System.out.println("일반 타이어가 굴러갑니다.");
}
}
public class Car {
// 필드에 Tire 객체 대입
private Tire tire1 = new Tire();
// 필드에 익명 자식 객체 대입
private Tire tire2 = new Tire() {
@Override
public void roll() {
System.out.println("익명 자식 Tire 객체 1이 굴러갑니다.");
}
};
// 메소드(필드 이용)
public void run1() {
tire1.roll();
tire2.roll();
}
// 메소드(로컬 변수 이용)
public void run2() {
// 로컬 변수에 익명 자식 객체 대입
Tire tire = new Tire() {
@Override
public void roll() {
System.out.println("익명 자식 Tire 객체 2가 굴러갑니다.");
}
};
tire.roll();
}
// 메소드(매개변수 이용)
public void run3(Tire tire) {
tire.roll();
}
}
public class CarExample {
public static void main(String[] args) {
// Car 객체 생성
Car car = new Car();
// 익명 자식 객체가 대입된 필드 사용
car.run1();
// 익명 자식 객체가 대입된 로컬변수 사용
car.run2();
// 익명 자식 객체가 대입된 매개변수 사용
car.run3(new Tire() {
@Override
public void roll() {
System.out.println("익명 자식 Tire 객체 3이 굴러갑니다.");
}
});
}
}
/*
실행 결과
일반 타이어가 굴러갑니다.
익명 자식 Tire 객체 1이 굴러갑니다.
익명 자식 Tire 객체 2가 굴러갑니다.
익명 자식 Tire 객체 3이 굴러갑니다.
*/
익명 구현 객체
new 인터페이스() { // 필드 // 메소드 }
public interface RemoteControl {
// 추상 메소드
void turnOn();
void turnOff();
}
public class Home {
// 필드에 익명 구현 객체 대입
private RemoteControl rc = new RemoteControl() {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
};
// 메소드(필드 이용)
public void use1() {
rc.turnOn();
rc.turnOff();
}
// 메소드(로컬 변수 이용)
public void use2() {
// 로컬 변수에 익명 구현 객체 대입
RemoteControl rc = new RemoteControl() {
@Override
public void turnOn() {
System.out.println("에어컨을 켭니다.");
}
@Override
public void turnOff() {
System.out.println("에어컨을 끕니다.");
}
};
rc.turnOn();
rc.turnOff();
}
// 메소드(매개변수 이용)
public void use3(RemoteControl rc) {
rc.turnOn();
rc.turnOff();
}
}
public class HomeExample {
public static void main(String[] args) {
// Home 객체 생성
Home home = new Home();
// 익명 구현 객체가 대입된 필드 사용
home.use1();
// 익명 구현 객체가 대입된 로컬 변수 사용
home.use2();
// 익명 구현 객체가 대입된 매개변수 사용
home.use3(new RemoteControl() {
@Override
public void turnOn() {
System.out.println("난방을 켭니다.");
}
@Override
public void turnOff() {
System.out.println("난방을 끕니다.");
}
});
}
}
/*
실행 결과
TV를 켭니다.
TV를 끕니다.
에어컨을 켭니다.
에어컨을 끕니다.
난방을 켭니다.
난방을 끕니다.
*/