
드디어 내부클래스
지금까지 한 내용들은 거의 헤드퍼스트자바에서 한 번이라도 제대로 다룬 적이 있는데 내부클래스는 그냥 이것이 있다정도로만하고 넘어간 느낌이라 궁금하긴하다
'내부 클래스는 활용빈도가 높지 않으므로 내부 클래스의 기본 원리와 특징을 이해하는 정도까지만 학습해도 충분하다. 실제로 발생하지 않을 경우까지 이론적으로 만들어 내서 고민하지말자'
와우 (홍대마스코트 아님)
가볍게 읽어보고 7장을 마치겠다.
내부클래스
말그대로 클래스 내부에 선언된 클래스다. 제일 궁금한 것. 왜 내부에 선언할까? 간단하다. 두 클래스가 서로 긴밀한 관계에 있기 때문이다. 내부 클래스 선언 시 두 클래스 간 서로의 멤버에 쉽게 접근할 수 있고, 외부에는 클래스의 노출을 줄일 수 있다. 캡슐화라고 하는 그거
그렇기 때문에 내부클래스는 내부클래스를 감싸고 있는 외부클래스 이외에서는 잘 호출되지 말아야한다.
종류
변수들이 인스턴스변수, 정적변수, 지역변수처럼 나뉘는 것처럼 클래스도 나뉜다.
- 인스턴스 클래스
외부클래스의 변수 선언부에 선언된다. 외부클래스의 인스턴스멤버처럼 취급되며 외부클래스의 인스턴스 변수와 관련된 기능을 수행한다.
- 정적 클래스
인스턴스 클래스와 같은 곳에서 선언되며, 정적변수처럼 다뤄진다. 외부클래스를 선언하지 않고도 접근이 가능하다. 그래서 꽤 사용된다.
- 지역 클래스
메서드, 초기화블럭 내에 선언되어 선언된 영역에서만 실행된다.
- 익명클래스
클래스 선언과 객체 생성을 동시에 하는 일회용메서드. 뒤에서 간략하게 알아볼 것이다.
class Outer {
class Inner { }
static class InnerStatic { }
void method() {
class InnerLocal { }
}
}
이렇게 선언되며, Inner 클래스가 내부클래스, InnerStatic 클래스가 정적내부클래스, InnerLocal 클래스가 지역내부클래스이다.
클래스 접근제어자
인스턴스 내부클래스와 정적 내부클래스는 멤버변수와 같은 취급을 받기 때문에 접근제어자를 사용할 수 있다. private, public, default, protected 등.. 또한 클래스이기 때문에 클래스에 사용되는 abstract, final 같은 제어자를 사용할 수 있다.
JDK16 이전에는 정적내부클래스가 아닌 내부클래스는 상수 (final static으로 접근제어자가 지정된 변수)를 제외하고는 정적변수를 선언할 수 있었으나 JDK16 이후로는 사용이 가능하다.
인스턴스 내부클래스는 외부클래스의 인스턴스 멤버를 객체 생성없이 바로 사용할 수 있다. 왜냐하면 내부클래스가 사용되려면 어차피 외부클래스도 생성되야아하므로 그렇다. 다만 정적 내부클래스의 경우에는 외부클래스가 생성되지 않아도 접근이 가능하므로 외부클래스의 인스턴스 멤버를 사용할 수 없다. 굳이 하려면 정적내부클래스에 외부클래스의 인스턴스를 하나 만든 후에 접근해야한다.
이는 내부클래스 간에도 동일해서, 정적내부클래스는 인스턴스내부클래스에 객체생성없이 접근할 수 없으나 인스턴스 내부클래스에서 정적 내부클래스는 객체생성 없이 접근할 수 있다.
내부클래스의 접근범위
인스턴스 내부클래스는 외부클래스의 인스턴스 멤버로 취급된다. 따라서 외부클래스의 인스턴스 변수를 해당 변수의 접근제어자에 상관없이 자유롭게 접근이 가능하다.
정적 내부클래스는 외부클래스의 정적 멤버로 취급된다. 그렇기 때문에 외부클래스의 인스턴스 변수를 사용할 수 없으며, 정적 변수만이 사용가능하다.
지역 내부클래스에서는 인스턴스 멤버와 정적멤버, 그리고 지역 내부클래스가 사용될 영역의 지역변수도 사용가능하다. 단, 지역변수는 final이 붙은 지역 변수만 가능하다. 왜냐하면 메서드가 실행이 끝나 지역변수가 소멸되어도, 지역 클래스의 인스턴스가 없어진 지역 변수를 참조하려는 경우가 발생할 수 있기 때문이다.
예를 들자면, 지역 내부클래스를 반환형으로 지정하여 내부클래스의 인스턴스가 메서드 밖으로 나갈 경우, 이 내부클래스를 호출하였는데 내부클래스가 지역변수를 사용하고 있다면, 지역변수는 사라졌고 내부클래스는 사라진 지역변수를 찾으려고 해 오류가 생길 것이다. final로 선언하면 내부클래스의 그 변수의 복사본을 가지게된다.
단 JDK8부터는 final을 생략하여도 컴파일러가 알아서 붙여준다.
외부에서 내부클래스에 접근할 수 있나?
여기서의 외부는 외부클래스가 아니라 그냥 다른 클래스를 말한다. 물론 가능하다.
class Outer {
class Inner { }
static class InnerStatic { }
void method() {
class InnerLocal { }
}
}
Outer o = new Outer();
o.Inner inner = new o.new Inner();
Outer.InnerStatic InSt = new Outer.InnerStatic();
이런식으로 인스턴스 내부클래스는 외부클래스를 생성한 다음에 접근하여 생성하고, 정적 내부클래스는 그냥 클래스명.내부클래스명으로 생성가능하다.
다만 이럴 경우에는 내부 클래스로 선언하면 안되는 경우임에도 내부클래스로 선언한 것이다. 외부에서 호출해야한다면 그냥 다른 클래스로 분리하는 것이 좋다.
이 때 컴파일하여 생성되는 파일명은 '외부 클래스명$내부 클래스명.class' 형식으로 생성된다. 지역 내부클래스의 경우에는 '외부 클래스명$(숫자)내부클래스명'이 된다. 지역내부클래스는 중복이름을 허용하기 때문에 숫자로 구분해야하기 때문이다.
또한 내부클래스에서 this를 사용해야할 경우 내부클래스 자체는 this, 외부클래스를 나타낼 때는 외부클래스이름.this로 해야한다.
익명클래스
익명클래스는 내부클래스에 속하지만 이름이 없다. 클래스의 선언과 객체 생성을 동시에 진행한다. 그래서 단 하나의 겍체만 생성할 수 있는 일회용 코드다.
class Outer {
Object object = new Object() { void method() { } };
이렇게 사용한다. 클래스 선언부에 new 조상클래스 이름() 또는 구현할 인터페이스의 이름이 들어가며, 상속 또는 구현이 필수, 상속과 구현을 동시에하거나 다중구현을 할 수 없다. 위의 예시처럼 상속받을 클래스가 없다면 Object 클래스를 상속해야한다.
컴파일 시에는 이름이 없기 때문에 외부클래스$숫자.class 로 클래스 파일명이 정해진다.
매개변수로 무언갈 넘겨줄 때 코드를 작성해야하지만 외부에서 작성할 필요가 없는 간단한 코드의 경우에는 매개변수에 익명클래스를 선언하여 넘겨주면된다. 가령 Comparator 라던가..
List<String> list = List.of("apple", "banana", "kiwi");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
이거는 GPT에게서 받아온 코드인데 원래라면 Comparator를 구현하여 매개변수로 넘겨주어야하지만 이렇게 익명클래스를 활용하면 굳이 클래스를 하나 안 만들어도되고 간결하게 넘길 수 있다.
근데 그냥 람다가 더 편하니까 그거 쓰는데 훨씬 낫다. 암 그렇고말고
암튼 이렇게 내부클래스까지 읽으며 7장을 다 읽었다. OOP의 꽃. 상속부터 다형성까지 한 차례 읽어봤고, 앞으로도 더 읽어봐야한다. 그래도 큰 산은 넘었고, 헤드퍼스트자바에서 이해하기 어려웠던점, 또는 까먹었던점 등을 더욱 명확히 할 수 있지 않았나 생각든다. 앞으로는 예외처리 - 자바랭 - 포매팅 - 컬렉션 등등.. 쓰레드나 I/O 나오기 전까지는 지금보다는 편하게 볼 수 있지 않을까?
'언어공부 > Java | Kotlin' 카테고리의 다른 글
| [CS] 이펙티브자바 독서 #2 - 아이템18 (0) | 2025.10.14 |
|---|---|
| [CS] 이펙티브자바 독서 #1 - 아이템15 ~ 17 (0) | 2025.10.11 |
| [CS] 자바의 정석 독서 #12 - 추상클래스와 인터페이스 (0) | 2025.10.09 |
| [CS] 자바의 정석 독서 #11 - 다형성 (1) | 2025.10.09 |
| [CS] 자바의 정석 독서 #10 - 패키지와 접근제어자 (0) | 2025.10.02 |