❑ 내부 클래스(Inner Class) 란?
말 그대로 클래스 내부에 선언된 클래스
주로 클래스 내에서만 사용하고 다른 클래스에서는 사용하지 않을 때 내부 클래스로 선언함
class Outer { // 외부 클래스
class Inner { // 내부 클래스
}
}
클래스를 감싸고 있는 클래스 : 외부 클래스
클래스안에 있는 클래스 : 내부 클래스
내부 클래스는 총 4가지 종류가 있음
내부 클래스 종류 | 선언 위치 |
인스턴스 내부 클래스 | 인스턴스 변수 위치 |
정적 내부 클래스 | 정적 변수 위치 |
지역 내부 클래스 | 지역 변수 |
익명 내부 클래스 |
클래스내 변수(인스턴스 변수, 정적 변수, 지역 변수)의 위치와 선언 위치가 같다.
1. 인스턴스 내부 클래스( instance inner class)
인스턴스 변수가 선언되는 위치에 선언된다.
외부 클래스 내부에서만 생성하여 사용할 객체를 선언할 때 사용한다.
항상 외부 클래스가 먼저 생성된 후 인스턴스 내부 클래스가 생성된다.
따라서 외부 클래스 생성 없이는 사용 불가
인스턴스 내부 클래스에 static 변수, static 메소드를 선언할 수 없게 되어있어서 오류가 발생한다.
static 변수나 static 메소드는 클래스의 생성과 상관없이 사용할 수 있지만
내부 클래스는 외부 클래스가 생성되야만 생성되기 때문에 static 사용 불가
외부 클래스에서 private으로 선언한 것들 모두 내부 클래스에서 접근 가능
인스턴스 내부 클래스의 메소드는 외부 클래스의 메소드가 호출될 때 사용할 수 있다.
2. 정적 내부 클래스(static inner class)
인스턴스 내부 클래스는 정적 변수나 정적 메소드를 생성할 수 없다.
하지만 정적 내부 클래스는 정적 메소드, 정적 변수를 내부에 생성하고 사용할 수 있다.
따라서 외부 클래스의 생성과 상관없이 사용하거나 내부에 정적 변수, 정적 메소드를 사용하고 싶다면 정적 내부 클래스 사용하면 된다.
정적 내부 클래스는 외부 클래스의 멤버 변수와 같은 위치에 정의한다.
정적 변수와 마찬가지로 static 예약어를 사용한다.
예시 코드
public class OutClass { // 외부 클래스
private int num = 1; // private으로 선언한 인스턴스 변수
private static int sNum = 2; // private으로 선언한 정적 변수
static class InStaticClass { // 정적 내부 클래스
int inNum = 4; // 정적 내부 클래스의 인스턴스 변수
static int sInNum = 5; // 정적 내부 클래스의 정적 변수
void inTest() {
// num += 10; // 오류⚠️ (정적 내부 클래스에서는 외부 클래스의 인스턴스 변수 사용 불가)
System.out.println("InStaticClass inNum = " + inNum + "(내부 클래스의 인스턴스 변수 사용)");
System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 정적 변수 사용)");
System.out.println("OutClass sNum = " + sNum + "(외부 클래스의 정적 변수 사용)");
}
static void sTest() {
// num += 10; // 오류⚠️ (정적 내부 클래스에서는 외부 클래스의 인스턴스 변수 사용 불가)
// inNum += 10; // 오류⚠️ (정적 메소드에서는 인스턴스 변수 사용 불가)
sInNum += 100; // 내부 클래스의 정적 변수 사용 가능
System.out.println("OuterClass sNum = " + sNum + "(외부 클래스의 정적 변수 사용)");
System.out.println("InStaticClass sInNum = " + sInNum + "(내부 클래스의 정적 변수 사용)");
}
}
}
class main {
public static void main(String[] args) {
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
System.out.println("정적 내부 클래스 일반 메소드 호출");
sInClass.inTest();
System.out.println();
System.out.println("정적 내부 클래스의 정적 메소드 호출");
OutClass.InStaticClass.sTest();
}
}
<출력 결과>
정적 내부 클래스 일반 메소드 호출
InStaticClass inNum = 4(내부 클래스의 인스턴스 변수 사용)
InStaticClass sInNum = 5(내부 클래스의 정적 변수 사용)
OutClass sNum = 2(외부 클래스의 정적 변수 사용)
정적 내부 클래스의 정적 메소드 호출
OuterClass sNum = 2(외부 클래스의 정적 변수 사용)
InStaticClass sInNum = 105(내부 클래스의 정적 변수 사용)
정적 내부 클래스 내의 일반 메소드와 정적 메소드는 변수 유형에 따라 사용가능 여부가 다르다.
표로 정리하면 아래와 같다.
정적 내부 클래스 메소드 | 변수 유형 | 사용 가능 여부 |
일반 메서드 void Method() |
외부 클래스의 인스턴스 변수 | x |
외부 클래스의 정적 변수 | o | |
정적 내부 클래스의 인스턴스 변수 | o | |
정적 내부 클래스의 정적 변수 | o | |
정적 메서드 static void sMethod() |
외부 클래스의 인스턴스 변수 | x |
외부 클래스의 정적 변수 | o | |
정적 내부 클래스의 인스턴스 변수 | x | |
정적 내부 클래스의 정적 변수 | o |
정적 내부 클래스의 정적 메소드에서는 외부 클래스나 내부 클래스에서 정적 변수만 사용 할 수 있다.
➤ 다른 클래스에서 정적 내부 클래스 사용하기
정적 내부 클래스는 외부 클래스를 생성하지 않고도 내부 클래스 자료형으로 바로 선언하여 생성할 수 있다.
OutClass.InStaticClass sInClass = new OutClass.InStaticClass();
또한 정적 내부 클래스에 선언한 메소드나 변수는 private이 아닌 경우에 다른 클래스에서도 사용할 수 있다.
OutClass.InStaticClass.sTest();
2. 지역 내부 클래스(local inner class)
지역 변수와 같이 메소드 내부에 정의하는 클래스이다.
지역 내부 클래스 선언하기
class LocalTest { // 외부 클래스
int num = 10;
static int sNum = 100;
Runnable getMyPet(int pNum){ // 메소드
int mNum = 20;
// static int insNum = 200; // 메소드내 정적 변수 선언 불가
class MyPet implements Runnable{ // 🔥지역내부 클래스
int inNum = 30; // 컴파일러가 자동으로 final 추가
// num += 10; // 외부 클래스의 멤버변수 변경 불가
// mNum += 10; // 지역변수는 상수로 바뀌므로 변경하면 오류 발생
// pNum += 10; // 메소드의 지역변수는 상수로 바뀌므로 변경하면 오류 발생
public void run() {
System.out.println("외부 클래스의 멤버 변수 num : " + num);
System.out.println("외부 클래스의 정적 변수 sNum : " + sNum);
System.out.println("외부클래스의 메소드내 지역변수 mNum : " + mNum);
System.out.println("지역 내부 클래스의 멤버 변수 inNum : " + inNum);
System.out.println("지역 내부 클래스를 감싸고 있는 메소드의 매개변수 pNum : " + pNum);
}
}
return new MyPet();
}
}
만든 지역 내부 클래스는 이렇게 사용된다.
public class LocalInnerPractice {
public static void main(String[] args) {
LocalTest myPet = new LocalTest();
Runnable runnable = myPet.getMyPet(10);
runnable.run();
}
}
<출력 결과>
외부 클래스의 멤버 변수 num : 10
외부 클래스의 정적 변수 sNum : 100
외부클래스의 메소드내 지역변수 mNum : 20
지역 내부 클래스의 멤버 변수 inNum : 30
지역 내부 클래스를 감싸고 있는 메소드의 매개변수 pNum : 10
지역내부 클래스를 생성하는건 생소하기도 하고 복잡하다.
일단은 외부 클래스를 반드시 만들어 줘야 하고
메소드를 실행해서 return으로 지역 내부 클래스 객체를 생성해서 반환하도록 설계해주면 된다.
※ 참고) 메소드 내의 지역 변수는 static 변수로 선언할 수 없다.
자바에서는 static local variable을 지원하지 않는다.
static local variables are not allowed!
static 변수는 field 에만 정의되는 변수이다.
메소드는 실행될 때만 stack 메모리에 올라갔다가 실행이 끝나면 stack메모리에서 사라진다.
그런데 정적 변수는 컴파일과 동시에 클래스 정보와 함께 메모리를 할당받고 프로그램 실행중에는 계속 메모리 공간에 있어서 접근할 수 있다.
따라서 자바에서 static 변수는 내부에 선언하면 오류가 발생한다.
참고 사이트
https://www.geeksforgeeks.org/g-fact-47/
➤ 지역 내부 클래스에서 지역 변수 = 상수 ?
지역 내부 클래스에서 사용하는 변수는 상수로 바뀌어서 값을 변경할 수 없게 된다.
그 이유를 이해하려면 일단 지역 내부 클래스의 지역변수가 JVM에서 메모리가 어떻게 흘러가는지를 알아야한다.
항상 지역 변수는 메소드가 호출될 때 stack 메모리에 생성되고 메소드 실행이 끝나면 메모리에서 사라진다.
그런데 만약에 지역변수를 지역 내부 클래스에서 일반 변수처럼 쓸 수 있다면 어떻게 될까?
다른 클래스에서 지역 내부 클래스를 만들기 위해 메소드를 호출하고
메소드는 스택 메모리에 올라갔다가 메소드 수행이 끝나면 스택 메모리에서 사라진다.
LocalTest myPet = new LocalTest();
Runnable runnable = myPet.getMyPet(10); // getMyPet()메소드의 실행이 끝나면 스택메모리에서 사라짐
runnable.run(); // 지역내부 클래스의 메소드를 사용하면서 getMyPet()의 지역 변수가 사용됨
그런데 메소드의 매개변수와 지역변수를 지역 내부 클래스의 객체를 이용해서 스택메모리에서 사라진 이후에도 참조할 수 있다.
이 때 값을 변경할 수 있다면(변수라면) 문제가 생기기 때문에 지역 변수와 매개변수는 모두 상수로 처리된다.
상수 예약어 'final'을 작성해야하지만 안써도 컴파일러에서 자동으로 final을 추가해준다.
참고 사이트
https://www.geeksforgeeks.org/local-inner-class-java/
지역 내부 클래스에서 사용하는 메소드의 지역 변수는 모두 상수로 바뀐다!
위에 거 다 까먹어도 이것만 기억하면 지역내부 클래스를 쓸 수 있다.
4. 익명 내부 클래스 (Anonymous inner class)
익명 클래스가 무엇인가?
클래스 이름을 사용하지 않는 클래스를 익명 클래스라고 한다.
지역 내부 클래스와 쓰임새가 비슷하지만 이름이 없기 때문에 일회용이다.
클래스의 선언과 동시에 객체를 생성하기 때문에 하나의 객체만을 생성한다.
따라서 한번만 사용하는 클래스를 생성할 때 만들면 된다.
지역 내부 클래스에서 사용한 코드를 익명 내부 클래스로 바꾸면 이렇게 작성할 수 있다.
class Person {
Runnable getRunnable(int i) {
int num = 100;
return new Runnable() { // 익명 내부 클래스, Runnable 인터페이스 생성
@Override
public void run() {
// num += 10; // 오류 발생
// i += 20; // 오류 발생
System.out.println("매개변수 i : " + i);
System.out.println("지역 변수 num : " + num);
}
}; // 끝나는 중괄호 뒤에 ; 붙임
}
Runnable runnable = new Runnable() { // 익명 내부 클래스를 변수에 대입
@Override
public void run() {
System.out.println("Runnable이 구현된 익명 클래스 변수");
}
};
}
생성한 익명 내부 클래스를 사용해보자
public class AnnoymousInnerTest {
public static void main(String[] args) {
Person p = new Person();
Runnable runnable = p.getRunnable(10);
runnable.run();
p.runnable.run();
}
}
<출력 결과>
매개변수 i : 10
지역 변수 num1 : 100
Runnable이 구현된 익명 클래스 변수
익명 클래스는 끝나는 부분에 세미콜론(;)을 작성해서 컴파일러에 익명 클래스가 끝났다고 알려줘야 한다.
익명 내부 클래스는 단 하나의 인터페이스나 단 하나의 추상 클래스를 바로 생성할 수 있다.
인터페이스는 인스턴스를 생성할 수 없는데..???
인터페이스의 인스턴스를 만들려면 구현을 다하지 않은 메소드를 작성해줘야 한다.
익명 내부 클래스를 호출할 때는 지역 내부 클래스 처럼
인스턴스를 생성해서 호출해도 되고
예시 코드의 'p.runnable.run()' 처럼 클래스내의 인터페이스 이름으로 바로 호출할 수도 있다.
익명 내부 클래스는 아직은 이해하기 어려운 개념이라는 생각이 든다. 🙁
익명 내부 클래스의 용도는 주로 안드로이드에서 버튼이나 텍스트 상자 같은 위젯(widget)의 이벤트를 처리하는 핸들러를 구현하는데 사용? 한다고 한다.
읽어주셔서 감사합니다.😃 도움이 되셨길 바랍니다.
오개념에 대한 지적은 언제나 환영입니다.🙌
'TIL(Today I Learned)' 카테고리의 다른 글
Java 심화-2️⃣ 람다식, 스트림 (0) | 2022.05.23 |
---|---|
5/19 (목) Java 심화-1️⃣ Enum, Annotation (0) | 2022.05.20 |
5/17 (화) 컬렉션 프레임워크-1️⃣ 제네릭(Generic) (0) | 2022.05.17 |
5/16 (월) 자바 코딩과 여러가지 오류,, (0) | 2022.05.16 |
5/13 (금) 자바 다형성 (0) | 2022.05.13 |