| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
- ARRANGEMENT
- java
- JetpackCompose
- 프로그래머스
- BFS
- Alignment
- compose
- programmers
- broadcastAsUser
- Android
- memory leak
- 구현
- 삽질로그
- NextPermutaion
- Inner Class
- hidden-api
- 알고리즘
- FCM
- Today
- Total
undefined
[JAVA] Inner class사용 시 주의할 점 본문
내부 클래스(Inner class)를 사용할 때 주의해야하는 이유를 알아보자.
내부 클래스 종류
1. 정적 내부 클래스 (static)
외부 클래스 인스턴스가 아닌 클래스 자체에 속하는 클래스로 외부 클래스의 멤버 변수만 접근 가능하다.
public class Outer {
static class StaticInner {
// ...
}
}
2. 멤버클래스 (non-static)
외부 클래스의 '인스턴스'에 속함. 외부 클래스의 모든 멤버에 접근 가능.
여기서 메모리 누수가 발생할 수 있다.
public class Outer {
class Inner {
// ...
}
}
3. 지역클래스
지역변수처럼 함수가 실행되는 동안에만 사용 가능하다.
public class Outer {
void someMethod() {
class LocalInner {
// ...
}
}
}
4. 익명클래스
한번만 쓰고 버릴 클래스를 만들 때 사용. 주로 인터페이스를 구현하거나 추상클래스 사용받아서 사용
여기서도 외부 클래스의 변수에 접근할 수 있고, 역시 메모리 누수가 발생할 수 있다.
new Thread(new Runnable() {
public void run() {
// ...
}
}).start();
그런데 메모리 누수가 어떻게 발생한다는건지 이해가 되지 않았다.
그래서 내부클래스가 어떻게 메모리 누수를 발생시키는데?
비 정적 멤버 클래스를 생성하려면 반드시 외부 클래스의 인스턴스가 필요하다. 즉 내부 클래스 인스턴스는 외부 클래스 인스턴스에 대한 참조를 가지고 있어야 한다.
알겠는데 와닿지 않아.. 눈으로 확인해보자.
public class Outer {
private int value = 10;
public class Inner {
public int getValue() {
return value;
}
}
}
이 코드가 컴파일되면,
public class Outer {
private int value = 10;
public class Inner {
final Outer this$0; // 컴파일러가 추가한 참조
public Inner() {
this$0 = Outer.this; // 생성자에서 외부 클래스에 대한 참조를 초기화
}
public int getValue() {
return this$0.value;
}
}
}
위처럼 되는데, this$0이 outer클래스에 대한 참조이다. 그래서 inner에서 이 외부 클래스의 모든 변수에 접근할 수 있는 것!
익명클래스도 마찬가지.
public class OuterClass {
private int value = 10;
public void someMethod() {
Thread thread = new Thread(new Runnable() { // Runnable 인터페이스를 구현하는 익명 클래스
@Override
public void run() {
System.out.println(value); // 외부 클래스의 멤버 변수에 접근
}
});
thread.start();
}
}
public class OuterClass {
private int value = 10;
public void someMethod() {
class AnonymousClass implements Runnable {
final OuterClass this$0; // 컴파일러가 추가한 참조
AnonymousClass() {
this$0 = OuterClass.this; // 생성자에서 외부 클래스에 대한 참조를 초기화
}
@Override
public void run() {
System.out.println(this$0.value);
}
}
Thread thread = new Thread(new AnonymousClass());
thread.start();
}
}
위 코드를 보면 AnonymousClass의 생성자에서 참조가 초기화되며, 이를 통해 AnonymousClass에서 OuterClass의 멤버 변수 value에 접근할 수 있게 된다.
코드를 디컴파일하면 컴파일러가 추가한 참조를 볼 수 있다고 한다
메모리 누수를 막으려면?
당연하지만 명시적으로 참조를 제거하면 된다.
public class OuterClass {
public class InnerClass {
// ...
}
public void someMethod() {
InnerClass inner = new InnerClass();
// ...
// inner 인스턴스가 더 이상 필요하지 않으므로 null로 설정
inner = null;
}
}
근데 이런 방법은 실수할 가능성이 높아서 피하라고 한다.
그 외에 정적 내부 클래스를 사용하거나,WeakReference를 사용하거나, Android에서 제공하는 Lifecycle-aware Component를 사용하는 방법도 있다. 는데 그건 다음에 알아보자