[Java] 내부 클래스
코틀린 내부 클래스
https://hellose7.tistory.com/119
내부 클래스란 클래스 안에 선언된 클래스를 의미한다.
보통 non-static 내부 클래스를 Inner 클래스라고 부르고, static 내부 클래스를 static nested 클래스라고 부른다.
non-static 내부 클래스
- 인스턴스 생성법
반드시 외부 클래스 인스턴스를 통해 인스턴스를 생성해야한다.
class Outer {
class Inner {
}
}
public class NestedClassTest {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println(outer.toString());
Outer.Inner inner = outer.new Inner();
System.out.println(inner.toString());
}
}
- 외부클래스명.this
non static 내부 클래스의 인스턴스가 생성될시에는 외부 클래스의 인스턴스가 항상 존재한다.
그렇기 때문에 내부 클래스의 인스턴스를 사용할 수 있는 곳(생성자,멤버변수메서드)에서는 외부 클래스의 인스턴스를 나타내는 외부클래스.this 를 사용할 수 있다.
class Outer {
class Inner {
public void printOuter() {
System.out.println(Outer.this);
}
public void printThis() {
System.out.println(this);
// = System.out.println(Inner.this);
}
}
}
public class NestedClassTest {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println(outer.toString());
Outer.Inner inner = outer.new Inner();
inner.printOuter();
}
}
this 대신 인스턴스 멤버를 직접 참조하는 것도 가능하다.
class Outer {
private String mString = "외부 인스턴스 멤버";
private void mMethod() {
System.out.println("외부 인스턴스 메서드");
}
class Inner {
String str = mString;
void method() {
mMethod();
}
}
}
- 메모리 누수
https://d2.naver.com/helloworld/329631
class Outer {
class Inner {
public void printOuter() {
System.out.println(Outer.this);
}
}
}
public class NestedClassTest {
public static void main(String[] args) {
Outer outer = new Outer();
System.out.println(outer.toString());
Outer.Inner inner = outer.new Inner();
outer = null; //외부 인스턴스 null
inner.printOuter();
}
}
non static 내부 클래스는 자기를 포함하는 인스턴스의 참조를 가지고 있다.
outer = null; 로 초기화하여도 루트부터 inner -> Inner -> Outer가 참조가 가능하므로 가비지 컬렉션의 대상이 되지 않는다.
inner =null; 로 해주지 않으면 main함수의 종료까지 쓸데없이 메모리를 사용하게 된채로 어플리케이션이 종료될 때까지 지 메모리에 버티고 있는 것이다.
- 사용가능한 변수, 메서드
1. 인스턴스 멤버(변수,메서드)는 클래스 멤버,인스턴스 멤버 모두 사용가능
2. 클래스 멤버는 클래스 멤버만 사용가능 (인스턴스 멤버 사용불가) . 인스턴스 멤버를 사용하려면 인스턴스를 생성한 뒤 사용해야함
class Outer {
private static String sString = "클래스 멤버";
private String mString = "인스턴스 멤버";
private static String sMethod() {
return "클래스 메서드";
}
private String mMethod() {
return "인스턴스 메서드";
}
class Inner {
//인스턴스 멤버는 외부의 모든 것 사용가능
private String str1 = sString;
private String str2 = sMethod();
private String str3 = mString;
private String str4 = mMethod();
//클래스 멤버는 외부의 클래스 멤버만 사용가능
private static String str5 = sString;
private static String str6 = sMethod();
private static String str7 = mString; // 에러
private static String str8 = mMethod(); // 에러
//인스턴스 멤버는 외부의 모든 것 사용가능
private void innerMethod() {
String str1 = sString;
String str2 = mString;
String str3 = sMethod();
String str4 = mMethod();
}
//클래스 멤버는 외부의 클래스 멤버만 사용가능
private static void innerStaticMethod() {
String str1 = sString;
String str2 = sMethod();
String str3 = mString; // 에러
String str4 = mMethod(); // 에러
}
}
}
- 외부 클래스와 내부 클래스의 메모리 로딩
static{} 클래스 초기화 블럭: 클래스가 메모리에 처음 로딩될 때만 단 한번 호출된다.
외부,내부 클래스 정의 상태
class Outer {
static String sOuterString;
static {
System.out.println("외부 클래스 메모리 로딩");
}
class Inner {
static String sInnerString;
static {
System.out.println("내부 클래스 메모리 로딩");
}
}
}
main함수
클래스를 메모리에 로딩시키기 위해 클래스 멤버(변수,메서드)를 호출한다.
public class NestedClassTest {
public static void main(String[] args) {
Outer.sOuterString="클래스 멤버에 값을 대입하여 클래스 메모리 로딩시킴";
}
}
public class NestedClassTest {
public static void main(String[] args) {
Outer.Inner.sInnerString = "클래스 멤버에 값을 대입하여 클래스 메모리 로딩시킴";
}
}
내부 클래스의 클래스 멤버가 호출될때 내부 클래스만 메모리에 로딩된다.
외부 클래스가 같이 로딩되지 않았다.
쉬운 이해를 위해 다음의 코드를 추가적으로 보자.
class Outer {
static String sOuterString = "외부의 클래스 멤버";
static {
System.out.println("외부 클래스 메모리 로딩");
}
class Inner {
static String sInnerString;
static {
System.out.println("내부 클래스 메모리 로딩");
System.out.println("sInnerString: " + sInnerString);
}
static void sInnerMethod() {
System.out.println("내부의 클래스 메서드 호출");
sInnerString = sOuterString;
//main함수의 코드에 Inner부터 클래스 로딩시키므로
//Outer는 sOuterString을 사용되는 여기서 로딩됨
System.out.println("sInnerString: " + sInnerString);
System.out.println("내부의 클래스 메서드 종료");
}
}
}
public class NestedClassTest {
public static void main(String[] args) {
Outer.Inner.sInnerMethod();
}
}
main함수의 코드만 변경시킨 것 (외부 클래스를 먼저 로딩시킴)
class Outer {
static String sOuterString;
static {
System.out.println("외부 클래스 메모리 로딩");
}
class Inner {
static String sInnerString;
static {
System.out.println("내부 클래스 메모리 로딩");
System.out.println("sInnerString: " + sInnerString);
}
static void sInnerMethod() {
System.out.println("내부의 클래스 메서드 호출");
sInnerString = sOuterString;
System.out.println("sInnerString: " + sInnerString);
System.out.println("내부의 클래스 메서드 종료");
}
}
}
public class NestedClassTest {
public static void main(String[] args) {
Outer.sOuterString = "외부의 클래스 멤버"; //외부 클래스 먼저 로딩
Outer.Inner.sInnerMethod();
}
}
이렇게 클래스 멤버의 동작을 알아봤다.
그러나 내부 클래스는 외부 클래스의 도움 목적이므로, 외부 클래스의 클래스 멤버는 내부 클래스의 클래스 멤버를 참조할 수 없게 만들었다.
static 내부 클래스
- 인스턴스 생성법
static 내부 클래스의 인스턴스는 외부 클래스의 인스턴스와 아무런 관계가 없다.
즉 독자적으로 생성이 가능하다. 그러나 외부 클래스에 포함된 클래스이므로 new 외부클래스명.클래스();로 사용
class Outer {
static class StaticInner {}
}
public class NestedClassTest {
public static void main(String[] args) {
Outer.StaticInner staticInner = new Outer.StaticInner();
System.out.println(staticInner);
}
}
- this 키워드
외부 클래스의 인스턴스와 관계가 존재하지 않으므로 외부클래스.this같은 행위는 가능하지 않다.
- 외부 클래스의 클래스 멤버만 사용가능하다.
class Outer {
static String sString = "외부의 클래스 멤버";
static void sMethod() {
System.out.println("외부의 클래스 메서드");
}
static class StaticInner {
static String innerStaticString= sString;
static void innerStaticMethod() {
sMethod();
}
}
}
- 클래스 메모리 로딩
위에서 살펴본 non-static처럼 외부 내부는 독자적으로 클래스 로딩이 이루어진다.
- 익명 클래스
일회성으로 인스턴스를 전달하기 위해 사용된다.
익명 클래스 사용시 메모리 릭이 발생할 수 있는 상황이 존재한다.
(내용 추가)
kotlin object 선언식 공부중 막힌 자바 여러 중첩 레벨 클래스
다음의 클래스 선언을 보자.
Nested 클래스의 인스턴스를 생성할 때 Outer, Inner 인스턴스 모두 필요없다.
(static클래스의 인스턴스는 모든 외부 클래스들의 인스턴스에 관계없이 독자적으로 생성될 수 있다)
public class TwoLevelStaticClassTest {
public static void main(String[] args) {
Outer.Inner.Nested nested= new Outer.Inner.Nested();
//모든 외부 클래스의 인스턴스 필요없다.
nested.method();
}
}