네스티드 클래스
- 클래스 내에 정의된 클래스를 네스티드(Nested) 클래스라 하고, 이를 감싸는 클래스를 외부(Outer) 클래스라 한다.
class Outer { // 외부 클래스
class Nested {...} // 네스티드 클래스
}
💡 네스티드 클래스 종류
- 네스티드 클래스는 static의 선언 여부를 기준으로 Static 네스티드 클래스와 Non-static 네스티드 클래스로 나뉜다.
- Non-static 네스티드 클래스는 이너 클래스라고도 부르며 정의되는 위치나 특성에 따라 멤버 이너 클래스, 로컬 이너 클래스, 익명 이너 클래스로 나뉜다. (이너 생략 가능)
- Static 네스티드 클래스
- Non-Static 네스티드 클래스 (이너 클래스)
- 멤버 클래스
- 로컬 클래스
- 익명 클래스
// Staitc 네스티드 클래스
class OuterClass {
static class StaticNestedClass {...} // Static 네스티드 클래스
}
// Non-static 네스티드 클래스
class OuterClass {
class InnerClass {...} // Non-static 네스티드 클래스, 이너클래스
}
Static 네스티드 클래스
- Static 네스티드 클래스는 static 선언이 갖는 특성이 반영된 클래스이다.
(static이 붙으면 실제 클래스와 아무 관계없이 자리 빌려 들어온 거라 생각하자) - 따라서 자신을 감싸는 외부 클래스의 인스턴스 생성과 Static 네스티드 클래스의 인스턴스 생성은 무관하다.
- 외부 클래스의 인스턴스를 생성하지 않고도 Static 네스티드 클래스의 인스턴스 생성 가능
- 때문에 Static 네스티드 클래스 내에서는 외부 클래스에 static으로 선언된 변수와 메소드에만 접근이 가능하다.
인스턴스 생성 방법
외부 클래스의 이름을 포함하는 형태로 인스턴스 생성이 이뤄져야 한다.
Outer.Nested nst = new Outer.Nested();
특징
- 아래 예제를 보면 Nested1, Nested2 클래스 내에서 Outer 클래스의 static 멤버인 num에 접근하고 있다.
- 이때 num이 private으로 선언되어 있어도 같은 Outer 클래스 내에 있으니 접근이 가능하다.
- 따라서 Outer의 static 멤버인 num은 Nested1과 Nested2의 모든 인스턴스가 공유하게 된다.
- 이것이 Static 네스티드 클래스가 갖는 주요 특징이다.
class Outer {
private static int num = 0; // static 변수
static class Nested1 { // Static 네스티드 클래스
void add(int n) { num += n; }
}
static class Nested2 { // Static 네스티드 클래스
int get() { return num; }
}
}
class Nested_Static {
public static void main(String[] args) {
Outer.Nested1 nst1 = new Outer.Nested1(); // Static 네스티드 클래스의 인스턴스 생성
nst1.add(5);
Outer.Nested2 nst2 = new Outer.Nested2(); // Static 네스티드 클래스의 인스턴스 생성
System.out.println(nst2.get());
}
}
// 실행 결과
5
이너 클래스
- 멤버 클래스 (Member Class) : 인스턴스 변수, 인스턴스 메소드와 동일한 위치에 정의
- 로컬 클래스 (Local Class) : 중괄호 내에, 특히 메소드 내에 정의
- 익명 클래스 (Anonymous Class) : 이름이 없는 클래스
class Outer {
class MemberInner {...} // 멤버 클래스
void method() {
class LocalInner {...} // 로컬 클래스
}
}
멤버 클래스
- 멤버 클래스는 인스턴스 변수, 인스턴스 메소드와 동일한 위치에 정의되는 클래스를 말한다.
class Outer {
private int num = 0; // 인스턴스 변수
class Member { // 멤버 클래스 정의
void add(int n) { num += n; }
int get() { return num; }
}
}
class InnerClass_Member {
public static void main(String[] args) {
Outer o1 = new Outer();
Outer o2 = new Outer();
// o1 기반으로 멤버 클래스 인스턴스 생성
Outer.Member o1m1 = o1.new Member();
Outer.Member o1m2 = o1.new Member();
// o2 기반으로 멤버 클래스 인스턴스 생성
Outer.Member o2m1 = o2.new Member();
Outer.Member o2m2 = o2.new Member();
// o1 기반으로 생성된 인스턴스 메소드 호출
o1m1.add(5);
System.out.println(o1m2.get());
// o2 기반으로 생성된 인스턴스 메소드 호출
o2m1.add(7);
System.out.println(o2m2.get());
}
}
// 실행 결과
5
7
- 예제를 보면 Member 클래스 내에서는 Outer 클래스의 인스턴스 변수(num)에 접근이 가능하다. 이때 인스턴스 변수가 private으로 선언되어 있어도 가능하다.
- 이는 멤버 클래스의 인스턴스는 외부 클래스의 인스턴스에 종속적인 특징을 갖기 때문이다.
- 멤버 클래스의 인스턴스는 외부 클래스의 인스턴스 기반으로만 생성 가능하다.
Outer o1 = new Outer();
Outer.Menber o1m1 = o1.new Member();
Outer.Member o1m2 = o1.new Member();
이렇게 생성된 멤버 클래스의 두 인스턴스는 o1이 참조하는 인스턴스 멤버에 직접 접근할 수 있다.
즉, 두 인스턴스는 o1이 참조하는 인스턴스의 멤버를 공유하게 된다.
❓ 멤버 클래스 언제 사용할까 ?
- 클래스의 정의를 감춰야하는 경우 유용하게 사용된다.
- 멤버 클래스를 private으로 선언하여 감출 수 있다.
interface Printable {
void print();
}
class Paper {
private String str;
public Paper(String s) {
str = s;
}
public Printable getPrinter() {
return new Printer(); // 멤버 클래스 인스턴스 생성 및 반환
}
private class Printer implements Printable { // 멤버 클래스
public void print() {
System.out.println(str);
}
}
}
class InnerClass_Member_Use {
public static void main(String[] args) {
Paper p = new Paper("날씨 맑음");
Printable prn = p.getPrinter();
prn.print();
}
}
// 실행 결과
날씨 맑음
- Printer(멤버 클래스)는 private으로 선언되어 있고, private으로 선언되어있기 때문에 이 클래스를 감싸는 외부 클래스 내에서만 인스턴스 생성이 가능하다.
- 때문에 paper.getPrint()라는 메소드를 통해 Printer(멤버 클래스) 인스턴스를 생성하지만, getPrint 메소드가 어떠한 인스턴스의 참조 값을 반환하는지 알지 못한다.
- 다만 반환되는 참조 값의 인스턴스가 Printable을 구현하고 있어 Printable 참조변수로 참조할 수 있다는 사실만 알 수 있다.
- 이렇게 클래스의 정의가 감춰진 상황에서 멤버클래스가 유용하게 사용된다.
- 클래스의 정의를 감추면, getPrint 메소드가 반환하는 인스턴스가 다른 클래스의 인스턴스로 변경되어도 외부 코드는 전혀 수정할 필요가 없는 유연성을 갖추게 된다.
로컬 클래스
- 로컬 클래스는 멤버 클래스와 매우 유사하다.
- 로컬 클래스는 외부 클래스 메소드 내부에 정의되어 해당 메소드에서만 사용된다.
위 멤버 클래스 예제를 로컬 클래스로 수정하여 알아보도록 하자.
interface Printable {
void print();
}
class Paper {
private String str;
public Paper(String s) {
str = s;
}
public Printable getPrinter() {
class Printer implements Printable { // 로컬 클래스
public void print() {
System.out.println(str);
}
}
return new Printer(); // 로컬 클래스 인스턴스 생성 및 반환
}
}
class InnerClass_Local {
public static void main(String[] args) {
Paper p = new Paper("날씨 맑음");
Printable2 prn = p.getPrinter();
prn.print();
}
}
// 실행 결과
날씨 맑음
- 멤버 클래스 예제와 똑같지만, getPrint 메소드 안에 클래스를 정의하였으므로 Printer는 로컬 클래스가 된다.
- 메소드 내에서 클래스를 정의하면 메소드 내에서만 인스턴스 생성이 가능하다 (private 선언은 의미가 없어짐)
- 즉 멤버 클래스보다 해당 클래스를 더 깊이 감추는 효과가 있다.
익명 클래스
- 이름 없는 클래스의 정의를 익명 클래스라 한다.
- 클래스의 이름 없이 클래스의 정의만 있어도 인스턴스 생성이 가능하기 때문에 익명 클래스가 등장하였다.
- 익명 클래스의 형태로 정의하면, 클래스의 정의와 인스턴스의 생성을 하나로 묶을 수 있다.
interface Printable {
void print();
}
class Paper {
private String str;
public Paper(String s) {
str = s;
}
public Printable getPrinter() {
return new Printable() { // 익명 클래스 정의, 인스턴스 생성 및 반환
public void print() {
System.out.println(str);
}
};
}
}
class InnerClass_Anonymous {
public static void main(String[] args) {
Paper p = new Paper("날씨 맑음");
Printable prn = p.getPrinter();
prn.print();
}
}
// 실행 결과
날씨 맑음
🔍 익명 클래스 적용 예시
/* 익명 클래스 전 */
class StrComp implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
class InnerClass_Anonymous_Before {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Box");
list.add("Robot");
list.add("Toy");
StrComp cmp = new StrComp(); // 정렬 기준
Collections.sort(list, cmp); // 정렬 기준 변경해서 정렬 진행
System.out.println(list);
}
}
// 실행 결과
[Box, Toy, Robot]
/* 익명 클래스 후 */
class InnerClass_Anonymous_After {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Box");
list.add("Robot");
list.add("Toy");
Comparator<String> cmp = new Comparator<String>() { // 익명 클래스 정의
@Override
public int compare(String s1, String s2) { // 추상메소드 구현 내용
return s1.length() - s2.length();
}
};
Collections.sort(list);
System.out.println(list);
}
}
// 실행 결과
[Box, Robot, Toy]
정리
/* 멤버 클래스 */
class Paper {
private String str;
public Paper(String s) { str = s; }
public Printable getPrinter() {
return new Printer(); // 멤버 클래스 인스턴스 생성 및 반환
}
private class Printer implements Printable { // 멤버 클래스
public void print() {
System.out.println(str);
}
}
}
/* 로컬 클래스 */
class Paper {
private String str;
public Paper(String s) { str = s; }
public Printable getPrinter() {
class Printer implements Printable { // 로컬 클래스
public void print() {
System.out.println(str);
}
}
return new Printer(); // 로컬 클래스 인스턴스 생성 및 반환
}
}
/* 익명 클래스 */
class Paper {
private String str;
public Paper(String s) { str = s; }
public Printable getPrinter() {
return new Printable() { // 익명 클래스 정의, 인스턴스 생성 및 반환
public void print() {
System.out.println(str);
}
};
}
}
'☕ Java > 이론' 카테고리의 다른 글
[Java] Optional 클래스 (0) | 2022.02.07 |
---|---|
[Java] 메소드 참조 (Method References) (0) | 2022.02.04 |
[Java] 함수형 인터페이스 (0) | 2022.02.03 |
[Java] 람다 (0) | 2022.01.28 |
[Java] 가변인자, 어노테이션 (0) | 2022.01.26 |
[Java] 열거형 (enum) (0) | 2022.01.26 |
[Java] 컬렉션 기반 알고리즘 (sort, binarySearch, copy) (0) | 2022.01.25 |
[Java] Queue 컬렉션 클래스 (0) | 2022.01.25 |