2023.02.19 Java 복습
9. 중첩 선언과 익명객체
9.1 중첩클래스
객체 지향 프로그램이에서는 클래스간에 서로 긴밀한 관계를 맺고 상호작용한다.
클래스가 여러 클래스와 관계를 맺는 경우에는 독립적으로 선언하는 것이 좋으나 특정 클래스만 관계를 맺을 경우에는 중첩클래스로 선언하는 것이 유지보수에 도움이 되는 경우가 많다.
중첩클래스란 클래스 내부에 선언한 클래스를 말한다. 중첩클래스를 사용하면 클래스의 멤버를 쉽게 사용할 수있고 외부에는 중첩클래스 관계를 감춤으로써 코드이 복잡성을 줄일 수있다는 장점이 있다.
중첩 클래스 선언하는 위치에 따라 두가지로 분류된다.
클래스 멤버로서 선언되느 중첩클래스를 멤버클래스라고 하고 메소드 내부에서 선언되는 중첩클래스를 로컬 클래스라고 한다.
중첩클래스도 하나의 클래스이기때문에 컴파일 하면 바이트 코드 파일이 별도로 생성된다.
1.멤버클래스일 경우
바이트 코드파일의 이름은 A(바깥 클래스) $ B(멤버클래스) .class가 된다.
2.로컬클래스일 경우
$1이 포함된 바이트 코드 파일 A(바깥 클래스) $1 B(멤버클래스) .class가 된다.
9.2 인스턴스 멤버 클래스
인스턴스 멤버 클래스는 A클래스의 멤버로 선언된 B클래스를 말한다.
접근제한자에 따른 접근범위는 다음과 같다.
public 다른 패키지에서 B클래스 사용가능
default 같은 패키지에서만 사용가능
private A클래스 내부에서만 클래스 사용가능
인스턴스 멤버클래스 B는 주로 A클래스 내부에서 사용되므로 private접근제한자를 갖는 것이 일반적이다.
B객체는 A클래스 내부 어디에서나 생성할 수는 없고 인스턴스 필드값, 생성자, 인스턴스 메소드에서 생성할 수 있다.
A객체가 잇어야 B객체도 생성할 수 있기 때문이다.
A객체를 먼저 생성한다음 B객체를 생성해야한다.
public class A {
// 인스턴스 멤버 클래스
class B {
}
// 인스턴스 필드값으로 B객체 대입
B field = new B();
// 생성자
A() {
B b = new B();
}
// 인스턴스 메소드
void method() {
B b = new B();
}
}public class AExample {
public static void main(String[] args) {
// A객체 생성
A a = new A();
// B객체 생성
A.B b = a.new B();
}
}인스턴스 멤버 클래스 B내부에는 일반클래스와 가팅 필드 생성자 메소드 선언이 올 수 잇다.
정적필드와 정적메소드는 자바17부터 선언이 가능하다.(거의 쓸일 없다는 소리)
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 실행");
}
}
// A의 인스턴스 메소드
void useB() {
B b = new B();
System.out.println(b.field1);
b.method1();
//B클래스의 정적 필드 및 메소드 사용
System.out.println(B.field2);
B.method2();
}
}public class Aexample {
public static void main(String[] args) {
// A객체 생성
A a = new A();
//A인스턴스 메소드 호출
a.useB();
//A를통해서B의 정적필드 메소드 사용
A.B.field2 = 1;
A.B.method2();
}
}9.3 정적 멤버 클래스
정적 멤버 클래스는 static키워드와 함께 A클래스의 멤버로 선언된 B클래스를 말한다.
접근제한자에 따른 인스턴스 멤버 클래스의 접근 범위는 다음과 같다.
public static 다른 패키지에서 사용가능
default static 같은 패키지에서만
private static A내부에서만 사용가능
정적멤버 클래스 B는 클래스 내부에서 사용되기도 하지만 A클래스 외부에서 A와 함께 사용되는 경우가 많아 default또는 public접근제한을 가진다.
B객체는 A클래스 내부 어디든 객체를 생성할 수 있다.
A클래스 외부에서 B객체를 생성하려면 A객체 생성없이 A클래스로 접근 후 B객체를 생성할 수있다.
A.B b = new A.B();
public class A {
//인스턴스 멤버 클래스
static 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 실행");
}
}
}public class AExample {
public static void main(String[] args) {
// B객체 생성 및 인스턴스 필드 및 메소드 사용
A.B b = new A.B();
System.out.println(b.field1);
b.method1();
//b클래스의 정적필드 및 메소드사용
System.out.println(A.B.field2);
A.B.method2();
}
}9.4 로컬클래스
생성자 또는 메소드 내부에서 다음 선언된 클래스를 로컬클래스라고 한다.
로컬클래스는 생성자와 메소드가 실행될 동안만 객체를 생성할 수있다.
public class A {
//메소드
void useB() {
//로컬 클래스
class B{
//인스턴스 필드
int field1 = 1;
//정적 필드 (자바17~)
static int field2 = 2;
//생성자
B() {
System.out.println("B-생성자 실행");
}
//인스턴스 메소드
void method1() {
System.out.println("B-method1 실행");
}
void method2() {
System.out.println("B-method2 실행");
}
}
//로컬 객체 생성
B b = new B();
//로컬 객체의 인스턴스 필드와 메소드 사용
System.out.println(b.field1);
b.method1();
System.out.println(b.field2);
b.method2();
}
}public class AExample {
public static void main(String[] args) {
//A객체 생성
A a = new A();
//A메소드 호출
a.useB();
}
}로컬 변수를 로컬 클래스에서 사용할 경우 로컬 변수는 final 특성을 갖게 되므로 값을 읽을수만 있고 수정할 수 없게 된다.
이것은 로컬크래스 내부에서 값을 변경하지 못하도록 제한하기 때문이다.
자바 8 이후부터는 명시적으로 final을 붙이지 않아도되지만 붙여서 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: " + arg);
System.out.println("var: " + var);
//로컬 변수 수정 불가
//arg = 3;
//var = 2;
}
}
//로컬 객체 생성
B b = new B();
//로컬 객체 메소드 호출
b.method2();
//로컬 변수 수정 불가? <- 변경은 가능한데 변경하면
//로컬클래스에서 사용못하게됨.
//arg = 3;
//var = 2;
}
}로컬클래스에서 변수를 사용하게 되면 final특성이 되는 것임.
9.5 바깥 멤버 접근
중첩 클래스는 바깥 클래스와 긴밀한 관계를 맺으면서 바깥 클래스의 멤버에 접근할 수 있다.
중첩클래스가 어떻게 선언되었느냐에 따라 접근 제한이 있을 수 있다.
9.5.1 바깥 클래스의 멤버접근제한
정적멤버클래스 내부에서는 바깥클래스의 필드와 메소드를 사용할때 제한이 따른다.
인스턴스 멤버클래스 -> 바깥클래스의 모든 필드와 메소드 /
->A객체를 만들어야 B객체를 만들 수있으니 다 사용가능한게 맞는듯
정적멤버클래스 -> 바깥클래스의 정적필드와 정적 메소드
->A안만들어도 B를 쓸수잇는데 A가 만들어져야 사용가능한 인스턴스는 사용못하는게 맞다.
정적 메소드에서 인스턴스 필드를 사용하지 못하는 이유와 같은듯하다.
public class A {
//A의 인스턴스 필드와 메소드
int field1;
void method1() {}
//A의 정적필드와 메소드
static int field2;
static void method2() {}
//인스턴스 멤버 클래스
class B {
void method() {
//A의 인스턴스 필드와 메소드 사용
field1 = 10;
method1();
//A의 정적필드와 메소드 사용
field2 = 10;
method2();
}
}
//정적멤버 클래스
static class C {
void method() {
//A의 인스턴스 필드와 메소드 사용
//field1 = 10; 불가능
// method1(); 불가능
//A의 정적필드와 메소드 사용
field2 = 10; // 가능
method2(); // 가능
}
}
}9.5.2 바깥클래스의 객체 접근
중첩 클래스 내부에서 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");
}
//B인스턴스 메소드
void print() {
//B객체의 필드와 메소드 사용
System.out.println(this.field); //B의 필드
this.method(); //B의 메소드
//A객체의 필드와 메소드 사용
System.out.println(A.this.field); //A(바깥)의 필드
A.this.method(); //A(바깥)의 메소드
}
}
//A의 인스턴스 메소드
void useB() {
B b = new B();
b.print();
}
}9.6 중첩 인터페이스
중첩인터페이스는 클래스의 멤버로 선언된 인터페이스를 말한다.
인터페이스를 클래스 내부에서 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현객체를 만들기 위해서이다.
외부의 접근을 막지 않으려면 public을 붙이고 A클래스 내부에서만 사용하려면 private를 붙인다.
접근 제한자를 붙이지 않으면 같은 패키지 안에서만 접근이 가능하다.
staitic을 붙여 정적으로 선언할 수도 잇다.
public class Button {
//정적 중첩 인터페이스
public static interface ClickListener {
//추상메소드
void onClick();
}
}외부에서 접근 가능하도록 public이면서 Button객체없이 사용할 수 있는 static중첩 인터페이스로 선언했다.
onClick메소드는 버튼이 클릭되엇을 때 호출될 메소드이다.
public class Button {
//정적 중첩 인터페이스
public static interface ClickListener {
//추상메소드
void onClick();
}
//필드
private ClickListener clickListener;
//메소드
public void setClickListener(ClickListener clickListener) {
this.clickListener = clickListener;
}
}setter를 추가해서 외부에서 setter를 통해 ClickListener 구현객체를 필드에 저장할 수 있도록 하자.
public class Button {
//정적 중첩 인터페이스
public static interface ClickListener {
//추상메소드
void onClick();
}
//필드
private ClickListener clickListener;
//메소드
public void setClickListener(ClickListener clickListener) {
this.clickListener = clickListener;
}
public void click() {
this.clickListener.onClick();
}
}Button이 클릭되엇을때 실행할 메소드로 click()을 추가했다. 실행 내용은 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(); //Ok 버튼을 클릭했습니다.
// ------------------------------------------
// Cancel 버튼
// ok버튼 객체 생성
Button btnCancel = new Button();
// Ok버튼 클릭 이벤트를 처리할 ClickListener 구현클래스(로컬 클래스)
class CancelListener implements Button.ClickListener {
// 정적인터페이스라 바깥클래스.인터페이스
@Override
public void onClick() {
System.out.println("Cancel 버튼을 클릭했습니다.");
}
}
// Ok버튼 객체에 ClickListener 구현객체 주입
btnCancel.setClickListener(new CancelListener());
// Ok버튼 클릭
btnCancel.click(); //Cancel 버튼을 클릭했습니다.
}
}로컬클래스로 ClickListener의 구현클래스를 작성하고 그것을 주입해서 실행시킴
따로 클래스를 만들고 구현해도 됨.
9.7 익명객체
익명객체는 이름이 없는 객체를 말한다. 명시적으로 클래스를 선언하지 않기 때문에 쉽게 객체를 생성할 수 있다는 장점이 있다.
익명객체는 필드값 로컬변수값 매개변수값으로 주로 사용된다.
익명객체는 클래스를 상속하거나 인터페이스를 구현해야만 생성할 수 있다.
클래스를 상속해서 만들경우 익명자식객체라고 하고 인터페이스를 구현할 경우 익명 구현객체이다.
9.7.1 익명 자식객체
익명 자식객체는 부모 클래스를 상속받아 생성된다.
이렇게 생성된 객체는 부모 타입의 필드, 로컬변수, 매게변수의 값으로 대입할 수 있다.
익명 자식객체는 부모 타입에 대입되므로 부모 타입에 선언된 멤버만 접근할 수 있다.
중괄호 블록안에는 주로 부모 메소드를 재정의 하는 코드가 온다.
new 부모생성자 () {자식객체내용}; / ;꼭 붙이기
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 = new Car();
// 필드에 익명 자식객체가 대입된 메소드 사용
car.run1();
//tire1.roll(); 일반 타이어가 굴러갑니다.
//tire2.roll(); 익명 자식 Tire 객체 1이 굴러갑니다.
// 로컬 변수에 익명자식객체가 대입된 메소드 사용
car.run2(); //tire.roll(); 익명 자식객체 Tire 객체2가 굴러갑니다.
// 매개변수에 익명자식객체가 대입되는 메소드 사용
car.run3(new Tire() {
@Override
public void roll() {
System.out.println("익명 자식 Tire 객체 3이 굴러갑니다.");
}
});
//익명 자식 Tire 객체 3이 굴러갑니다.
}
}Tire 클래스는 roll()메소드를 가지고 있지만 익명자식객체는 roll()을 재저으이해 실행내용을 변경한다.(다형성)
익명자식객체가 부모타입에 대입되면 부모 메소드 roll()을 호출 할경우 재정의된 익명자식객체의 roll()메소드가 실행된다.
9.7.2 익명 구현 객체
익명 구현객체는 인터페이스를 구현해서 생성된다.
이렇게 생성된 객체는 인터페이스 타입의 필드, 로컬변수, 매개변수의 값으로 대입할 수 있다.
익명 구현객체는 안드로이드와 같은 UI 프로그램에서 이벤트를 처리하는 객체로 많이 사용된다.
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() {};
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() {});
home.use3(new RemoteControl() {
@Override
public void turnOn() {
System.out.println("난방을 켭니다.");
}
@Override
public void turnOff() {
System.out.println("난방을 끕니다.");
}
});
}
}9.6 중첩 인터페이스 예제를 수정해서 버튼 이벤트 처리 객체를 익명 구현객체로 대체해보자.
setter를 호출할때 매개값으로 ClickListener의 익명 구현객체를 대입하자.
명시적으로 구현클래스를 생성하지 않아 코드가 간결해진다.
public class ButtonExample {
public static void main(String[] args) {
// ok버튼 객체 생성
Button btnOk = new Button();
// Ok버튼 객체에 ClickListener 익명구현객체 주입
btnOk.setClickListener(new Button.ClickListener() {
@Override
public void onClick() {
System.out.println("Ok 버튼을 클릭했습니다.");
}
});
// Ok버튼 클릭
btnOk.click(); // Ok 버튼을 클릭했습니다.
// ------------------------------------------
// Cancel 버튼
// ok버튼 객체 생성
Button btnCancel = new Button();
// Ok버튼 객체에 ClickListener 익명구현객체 주입
btnCancel.setClickListener(new Button.ClickListener() {
@Override
public void onClick() {
System.out.println("Cancel 버튼을 클릭했습니다.");
}
});
// Ok버튼 클릭
btnCancel.click(); // Cancel 버튼을 클릭했습니다.
}
}2023.02.19 후기
중첩클래스 자체는 많이 쓰이지 않는 것 같은데 익명 객체는 람다식 부터 많이 사용되는데 한번더 보면서 이해를 잘하게 된 것 같다.
책의 구성이 참 잘되있다고 느껴지는게 익명객체를 설명하기 위해 중첩클래스 특성부터 배우게 된 것 같다.
좀 더 정진 해야할 것 같다.