이것이 자바다 - Part 09

mj·2023년 1월 17일
0
post-thumbnail

Part 09 중첩 선언과 익명 객체

중첩 클래스

객체 지향 프로그램에서는 클래스 간에 서로 긴밀한 관계를 맺고 상호작용한다.

클래스가 여러 클래스와 관계를 맺는 경우에는 독립적으로 선언하는 것이 좋으나, 특정 클래스만 과계를 맺을 경우에는 중첩 클래스로 선언하는 것이 유지보수에 도움이 되는 경우가 많다.

중첩 클래스

  • 클래스 내부에 선언한 클래스
  • 장점: 클래스의 멤버를 쉽게 사용할 수 있고, 외부에는 중첩 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있다.
선언 위치에 따른 분류객체 생성 조건
인스턴스 멤버 클래스A 객체를 생성해야만 B 객체를 생성할 수 있음
정적 멤버 클래스A 객체를 생성하지 않아도 B 객체를 생성할 수 있음
로컬 클래스method 가 실행할 때만 B 객체를 생성할 수 있음
//인스턴스 멤버 클래스
class A {
	class B { ... }
}
//정적 멤버 클래스
class A {
	static class B { ... }
}
//로컬 클래스
class A {
	void method() {
    	class B { ... }
    }
}

중첩 클래스도 하나의 클래스이기 때문에 컴파일하면 바이트코드 파일(.class)이 별도로 생성된다.

멤버 클래스일 경우 바이ㅌ코드 파일의 이름은 다음과 같이 결정된다.

A $ B .class
A : 바깥 클래스
B : 멤버 클래스

로컬 클래스일 경우에는 다음과 같이 $1이 포함된 바이트코드 파일이 생성된다.

A $ B .class
A : 바깥 클래스
B : 멤버 클래스

인스턴스 멤버 클래스

인스턴스 멤버 클래스는 다음과 같이 A 클래스의 멤버로 선언된 B 클래스를 말한다.

[public] class A {
	[public | private] class B {
    }
}
구분접근 범위
public class B{}다른 패키지에서 B 클래스를 사용 할 수 있다.
class B {}같은 패키지에서만 B 클래스를 사용 할 수 있다.
private class B {}A 클래스 내부에서만 B 클래스를 사용할 수 있다.

인스턴스 멤버 클래스 B는 주로 A 클래스 내부에서 사용되므로 private 접근 제한을 갖는 것이 일반적이다.

B 객체는 A 클래스 내부 어디에서나 생성할 수는 없고, 인스턴스 필드값, 생성자, 인스턴스 메소드에서 생성할 수 있다.

B 객체를 A 클래스 외부에 생성하려면 default 또는 public 접근 제한을 가져야 하고, A 객체를 먼저 생성한 다음 B 객체를 생성해야 한다.

A a = new A();
B b = a.new b();

인스턴스 멤버 클래스 B 내부에는 일반 클래스와 같이 필드, 생성자, 메소드 선언이 올 수 있다. 정적 필드와 정적 메소드는 Java17 부터 선언 가능하다.

정적 멤버 클래스

정정 멤버 클래스는 다음과 같이 static 키워드와 함꼐 A 클래스의 멤버로 선언된 B 클래스를 말한다.

[public] class A {
	[public | private] static class B {
    }
}
  • 인스턴스 멤버 클래스스 접근 범위
구분접근 범위
public static class B {}다른 패키지에서 B 클래스를 사용할 수 있다.
static class B {}같은 패키지에서만 B 클래스를 사용할 수 있다.
private static class B {}A 클래스 내부에서만 B 클래스를 사용할 수 있다.

정적 멤버 클래스는 외부에서 사용되는 경우가 많기 때문에 주로 default 나 public 접근 제한을 가진다.

A 클래스 외부에서 B 객체를 생성하려면 A 객체 생성 없이 A 클래스로 접근해서 B객체를 생성할 수 있다.

A.B b = new A.B();

로컬 클래스

생성자 또는 메소드 내부에서 다음과 같이 선언된 클래스를 로컬 클래스라고 한다.

[public] class A {
	public A() {
    	class B { }
    }
    
    public void method() {
    	class B { }
    }
}

로컬 클래스는 생성자와 메소드가 실행될 동안에만 객체를 생성할 수 있다.

로컬 클래스 B 내부에는 일반 클래스와 같이 필드, 생성자, 메소드 선언이 올 수 있다.
정적 필드와 정적 메소드는 Java17 부터 선언 가능하다.

로컬변수(생성자 또는 메소드의 매개변수 또는 내부에서 선언된 변수)를 로컬 클래스에서 사용할 경우 로컬 변수는 final 특성을 갖게 되므로 값을 읽을 수만 있고 수정할 수 없게 된다.
이것은 로컬 클래스 내부에서 값을 변경하지 못하도록 제한하기 때문이다.

바깥 멤버 접근

중첩 클래스는 바깥 클래스와 긴밀한 관계를 맺으면서 바깥 클래스의 멤버(필드, 메소드)에 접근할 수 있다.

하지만 중첩 클래스가 어떻게 선언되었느냐에 따라 접근 제한이 있을 수 있다.

바깥 클래스의 멤버 접근 제한

정적 멤버 클래스 내부에서는 바깥 클래스의 필드와 메소드를 사용할 때 제한이 따른다.

구분바깥 클래스의 사용 가능한 멤버
인스턴스 멤버 클래스바깥 클래스의 모든 필드와 메소드
정적 멤버 클래스바깥 클래스의 정적 필드와 정적 메소드

정적 멈버 클래스는 바깥 객체가 없어도 사용 가능해야 하므로 바깥 클래스의 인스턴스 필드와 인스턴스 메소드는 사용하지 못한다.

바깥 클래스의 객체 접근

중첩 클래스 내부에서 this는 해당 중첩 클래스의 객체를 말한다. 만약 중첩 클래스 내부에서 바깥 클래스의 객체를 얻으려면 바깥 클래스 이름에 this를 붙이면 된다.

바깥클래스이름.this	//바깥 객체

중첩 인터페이스

중첩 인터페이스는 클래스의 멤버로 선언된 인터페이스를 말한다.

인터페이스를 클래스 내부에 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현 객체를 만들기 위해서이다.

중첩 인터페이스의 선언

class A {
	[public | private] [static] interface B {
    	//상수 필드
        //추상 메소드
        //디폴트 메소드
        //정적 메소드
    }
}

외부의 접근을 막지 않으려면 public을 붙이고, A클래스 내부에서만 사용하려면 private을 붙인다.

접근 제한자를 붙이지 않으면 같은 패키지 않에서만 접근이 가능하다.
그리고 A 객체 없이 B 인터페이스를 사용할 수 있도록 하기 위해 static 을 추가할 수 있다.

중첩 인터페이스는 안드로이드와 같은 UI 프로그램에서 이벤트를 처리할 목적으로 많이 활용된다.
예를 들어 버튼을 클릭했ㅇ르 때 이벤트를 처리할 객체는 중첩 인페이스를 구현해서 만든다.

익명 객체

익명 객체는 이름이 없는 객체를 말한다. 명시적으로 클래스를 선언하지 않기 때문에 쉽게 객체를 생성할 수 있다는 장점이 있다.

익명 객체는 필드값, 로컬 변수값, 매개변수값으로 주로 사용된다.

익명 객체는 클래스를 상속하거나 인터페이스를 구현해야만 생성할 수 있다.
클래스를 상속해서 만들 경우 익명 자식 객체라고 하고, 인터페이스를 구현해서 만들 경우 익명 구현 객체라고 한다.

익명 자식 객체

익명 자식 객체는 부모 클래스를 상속박아 다음과 같이 생성된다.
이렇게 생성된 객체는 부모 타입의 필드, 로컬 변수, 매개변수의 값으로 대입할 수 있다.

new 부모생성자(매개값, ...) {
	//필드
    //메소드
}

중괄호 블록 안의 필드와 메소드는 익명 자식 객체가 가져야 할 멤버로, 중괄호 블록 안에서만 사용 가능하다.

익명 자식 객체는 부모 타입에 대입되므로 부모 타입에 선언된 멤버만 접근할 수 있기 때문이다.

익명 구현 객체

익명 구현 객체는 인터페이스를 구현해서 다음과 같이 생성된다.

new 인터페이스() {
	//필드
    //메소드
}

이렇게 생성된 객체는 인터페이스 타입의 필드, 로컬변수, 매개변수의 값으로 대입할 수 있다.
익명 구현 객체는 안드로이드와 같은 UI 프로그램에서 이벤트를 처리하는 객체로 많이 사용된다.

중괄호 블록 안의 필드와 메소드는 익명 구현 객체가 가져야 할 멤버로, 중괄호 블록 안에서만 사용 할 수 있다.

문제

  1. 중첩 멤버 클래스에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 인스턴스 멤버 클래스는 바깥 클래스의 객체가 있어야 사용될 수 있다.
    ➋ 정적 멤버 클래스는 바깥 클래스의 객체가 없어도 사용될 수 있다.
    ➌ 인스턴스 멤버 클래스 내부에는 바깥 클래스의 모든 필드와 메소드를 사용할 수 있다.
    ➍ 정적 멤버 클래스 내부에는 바깥 클래스의 인스턴스 필드를 사용할 수 있다.
  • 답 : ➍
  1. 로컬 클래스에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 로컬 클래스는 메소드 내부에 선언된 클래스를 말한다.
    ➋ 로컬 클래스는 바깥 클래스의 필드와 메소드를 사용할 수 있다.
    ➌ 로컬 클래스는 static 키워드를 이용해서 정적 클래스로 만들 수 있다.
    ➍ final 특성을 가진 매개변수나 로컬 변수만 로컬 클래스 내부에서 사용할 수 있다.
  • 답 : ➌
  1. 익명 객체에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 익명 객체는 클래스를 상속하거나 인터페이스를 구현해야만 생성될 수 있다.
    ➋ 익명 객체는 필드, 매개변수, 로컬 변수의 초기값으로 주로 사용된다.
    ➌ 익명 객체에는 생성자를 선언할 수 있다.
    ➍ 익명 객체는 주로 재정의된 메소드를 멤버로 가진다.
  • 답 : ➌
  1. 다음과 같이 Car 클래스 내부에 Tire와 Engine 클래스가 멤버로 선언되어 있습니다. CarExample
    클래스에서 Tire와 Engine 객체를 생성하는 코드를 작성해보세요.
public class Car {
	class Tire {}
	static class Engine {}
}
public class CarExample {
	public static void main(String[] args) {
		Car myCar = new Car(); 
 		Car.Tire tire = ________________________________; 
        Car.Engine engine = _________________________________;
	}
}
  • 답 :
myCar.new Tire( )
new Car.Engine( )
  1. Action 인터페이스는 다음과 같이 work() 추상 메소드를 가지고 있습니다. ActionExample
    클래스의 main() 메소드에서 Action의 익명 구현 객체를 만들어 실행 결과와 동일하게 나오도록
    박스 안에 들어갈 코드를 작성해보세요.
public interface Action {
	public void work();
}
public class ActionExample {
	public static void main(String[] args) {
 		Action action =
 		action.work();
	}
}
//실행 결과
복사를 합니다.
  • 답 :
public class ActionExample {
	public static void main(String[] args) {
 		Action action = new Action() {
 			@Override
 			public void work() {
 				System.out.println("복사를 합니다.");
 			}
 		};
 		action.work();
	}
}
  1. AnonymousExample 클래스의 실행 결과를 보고, Vehicle 인터페이스의 익명 구현 객체를
    필드와 로컬 변수의 초기값 그리고 메소드의 매개값으로 대입해보세요.
public interface Vehicle {
	public void run();
}

//실행 결과
자전거가 달립니다.
승용차가 달립니다.
트럭이 달립니다.
  • 답 :
Vehicle field = new Vehicle() {
 	@Override
 	public void run() {
 		System.out.println("자전거가 달립니다.");
 	}
};	
void method1() {
 	Vehicle localVar = new Vehicle() {
 		@Override
 		public void run() {
 			System.out.println("승용차가 달립니다.");
 		}
 	};
}
anony.method2(
 	new Vehicle() {
 		@Override
 		public void run() {
 			System.out.println("트럭이 달립니다.");
 		}
 	}
 );
  1. 다음 Chatting 클래스는 컴파일 에러가 발생합니다. 원인을 설명해보세요.
public class Chatting {
	class Chat {
 		void start() {}
 		void sendMessage(String message) {}
	}
	void startChat(String chatId) {
 		String nickName = null;
 		nickName = chatId;
 		Chat chat = new Chat() {
 			@Override
 			public void start() {
 				while(true) {
 					String inputData = "안녕하세요";
 					String message = "[" + nickName + "] " + inputData;
 					sendMessage(message);
 				}
 			}
 		};
 		chat.start();
	}
}
  • 답 : nickName 은 final 특성을 가지기 때문에 값을 변경할 수 없다.
profile
사는게 쉽지가 않네요

0개의 댓글