일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 백엔드
- FileInputStream
- Java
- 코린이
- try-catch
- 개발자
- 변수
- 졸리다
- 자바
- 인터페이스
- SSR
- 새벽공부
- 초보개발자
- MPA
- 보조 스트림
- 코딩
- ArrayList
- exception
- 바이트 단위 스트림
- 인스턴스
- throws
- 메서드
- node.js
- 다형성
- 파이팅
- 배열
- 상속
- 자료형
- 예외 처리
- 문자 단위 스트림
- Today
- Total
SHUSTORY
13-1 내부 클래스 본문
내부 클래스 정의와 유형
내부 클래스는 '클래스 내부에 선언한 클래스'로, 내부에 클래스를 선언한 이유는 대개 이 클래스와 외부 클래스가 밀접한 관련이 있거나 다른 클래스와 협력할 일이 없는 경우이다.
내부 클래스는 선언하는 위치나 예약어에 따라 크게 다음 네 가지로 나뉜다.
인스턴스 내부 클래스 / 정적(static) 내부 클래스/ 지역(local) 내부 클래스/ 익명(anonymous) 내부 클래스
내부 클래스는 유형에 따라 만드는 방법이 다르며 클래스 내부에 선언할 수 있는 변수 유형과 사용할 수 있는 외부 클래스 변수 유형도 다르다.
인스턴스 내부 클래스
인스턴스 내부 클래스는 인스턴스 변수를 선언할 때와 같은 위치에 선언하며, 외부 클래스 내부에서만 생성하여 사용하는 객체를 선언할 때 쓰인다.
인스턴스 내부 클래스는 외부 클래스 생성 후 생성되기 때문에 외부 클래스를 생성하기 전에는 사용할 수 없으며,
이는 정적 내부 클래스와의 차이점으로 볼 수 있다.
외부 클래스에서 private 예약어를 사용하여 변수 num과 정적 변수 sNum을 선언하였다.
두 변수는 private로 선언했지만 외부 클래스 안에 있기 때문에 내부 클래스에서도 사용 가능하다.
인스턴스 내부 클래스는 외부 클래스를 생성한 이후에 사용해야 하기 때문에 클래스의 생성과 상관없이 사용 가능한 정적 변수는 인스턴스 내부 클래스에서 선언할 수 없다. ( 정적 메서드 또한 마찬가지이다. )
외부 클래스의 메서드에서 내부 클래스의 메서드는 사용 가능하다.
내부 클래스를 생성하는 이유는 그 클래스를 감싸고 있는 외부 클래스에서만 사용하기 위해서이므로 내부 클래스를 그 밖의 다른 클래스에서 생성해서 사용하는 것은 맞지 않다.
하지만 외부 클래스 외의 다른 클래스에서 private이 아닌 내부 클래스를 생성하는 것이 문법적으로 가능하긴 하다.
예제01을 예로 들자면 OutClass를 생성하고 인스턴스 변수를 이용하여 outClass.usingClass( ); 문장으로 내부 클래스 기능을 호출하는 것이다.
이때 만약 내부 클래스를 private로 선언하지 않았다면 외부 클래스가 아닌 다른 클래스에서도 다음처럼 내부 클래스를 생성할 수 있다. OutClass를 만들고, 생성한 참조 변수를 사용하여 내부 클래스를 생성한다.
* 내부 클래스를 private로 선언했다면 다른 클래스에서 InClass를 사용할 수 없다.
따라서 만약 어떤 클래스를 내부에서만 사용할 목적이라면 내부 클래스를 private로 선언한다.
정적 내부 클래스
내부 클래스가 외부 클래스 생성과 무관하게 사용할 수 있어야 하고 정적 변수도 사용할 수 있어야 한다면 정적 내부 클래스를 사용하면 된다. 정적 내부 클래스는 인스턴스 내부 클래스처럼 외부 클래스의 멤버 변수와 같은 위치에 정의하며 static 예약어를 함께 사용한다.
정적 내부 클래스에서는 외부 클래스의 인스턴스 변수는 사용할 수 없다.
25, 26행을 보면 외부 클래스를 생성하지 않고 바로 정적 내부 클래스가 생성 가능한 것을 확인할 수 있다.
정적 내부 클래스에서 사용하는 메서드가 정적 메서드인 경우에는 외부 클래스와 정적 내부 클래스에 선언된 변수 중 정적 변수만 사용 가능하다.
정적 내부 클래스 메서드 | 변수 유형 | 사용 가능 여부 |
일반 메서드 void inTest( ) |
외부 클래스의 인스턴스 변수 (num) | X |
외부 클래스의 정적 변수 (sNum) | O | |
정적 내부 클래스의 인스턴스 변수 (inNum) | O | |
정적 내부 클래스의 정적 변수 (sInNum) | O | |
정적 메서드 static void sTest( ) |
외부 클래스의 인스턴스 변수 (num) | X |
외부 클래스의 정적 변수 (sNum) | O | |
정적 내부 클래스의 인스턴스 변수 (inNum) | X | |
정적 내부 클래스의 정적 변수 (sInNum) | O |
다른 클래스에서 정적 내부 클래스 사용하기
정적 내부 클래스는 외부 클래스를 생성하지 않고도 내부 클래스 자료형으로 바로 선언하여 생성할 수 있다.
또 정적 내부 클래스에 선언한 메서드나( 정적 메서드 포함 ) 변수는 private이 아닌 경우 다른 클래스에서도 바로 사용 가능하다.
따라서 내부 클래스를 만들고 외부 클래스와 무관하게 다른 클래스에서도 사용하려면 정적 내부 클래스로 생성하면 된다.
하지만 정적 내부 클래스를 private으로 선언했다면 이 또한 다른 클래스에서 사용할 수 없다.
지역 내부 클래스
지역 내부 클래스는 지역 변수처럼 메서드 내부에 클래스를 정의하여 메서드 내부에서만 사용하는 것을 말한다.
* Runnable 인터페이스는 자바에서 스레드를 만들 때 사용하는 인터페이스로 java.lang 패키지에 선언되어 있으며 반드시 run( ) 메서드를 구현해야 한다.
getRunnable( ) 메서드를 보면 메서드의 반환형은 Runnable이다.
그렇기 때문에 이 메서드에서는 Runnable 자료형의 객체를 생성하여 반환해야 한다.
그래서 이 메서드 내부에 Runnable 인터페이스를 구현한 MyRunnable이라는 이름의 지역 내부 클래스를 정의하였다.
또한 14행에서 자바 스레드가 실행될 때 호출되는 run( ) 메서드를 구현했다.
이 메서드에서 Runnable 자료형을 반환해야 하므로 return new MyRunnable( ); 문장으로 MyRunnable 클래스를 생성한 후 반환하였다.
이 MyRunnable을 사용하려면 이 클래스를 직접 생성하는 것이 아니라 getRunnable( ) 메서드 호출을 통해 생성된 객체를 반환받아야 한다. (31행) _ Outer 클래스 생성 후 Runnable형 객체로 getRunnable( )을 호출한다.
* 지역 내부 클래스 이름은 클래스를 생성하여 반환할 때만 사용한다.
지역 내부 클래스에서 지역 변수의 유효성
지역 변수는 메서드가 호출될 때 스택 메모리에 생성되고 메서드의 수행이 끝나면 메모리에서 사라진다.
그런데 지역 내부 클래스에 포함된 getRunnable( ) 메서드의 매개변수 i와 메서드 내부에 선언한 변수 num은 지역 변수다.
run( ) 메서드는 getRunnable( ) 메서드의 지역 변수 i와 num을 사용한다.
그런데 지역 내부 클래스가 가지고 있는 getRunnable( ) 메서드 호출이 끝난 후에도 run( ) 메서드가 정상적으로 호출된다.
이는 getRunnable( ) 메서드 호출이 끝나고 스택 메모리에서 지워진 후에도 참조할 수 있다는 것이다.
즉 내부 클래스에서 사용하는 지역 변수는 상수로 처리된다.
그러므로 num과 i 변수의 값을 다른 값으로 바꾸려고 하면 오류가 발생한다.
정리하면, 지역 내부 클래스에서 사용하는 메서드의 지역 변수는 모두 상수로 바뀐다.
익명 내부 클래스
익명 내부 클래스에서는 클래스 이름을 사용하지 않는다.
익명 내부 클래스는 단 하나의 인터페이스 또는 단 하나의 추상 클래스를 바로 생성할 수 있다.
그런데 앞에서 인터페이스는 인스턴스로 생성할 수 없다고 했으므로,
Runnable 인터페이스를 생성할 수 있으려면 인터페이스 몸체가 필요하다.
10-15행을 보면 Runnable 인터페이스에서 반드시 구현해야 하는 run( ) 메서드가 포함되어 있으며 마지막에 세미콜론을 사용해서 익명 내부 클래스가 끝났다는 것을 알려준다.
익명 내부 클래스는 19-24행처럼 인터페이스나 추상 클래스 자료형으로 변수를 선언한 후 익명 내부 클래스를 생성해 대입할 수도 있으며, 이 경우에도 마찬가지로 추상 메서드나 인터페이스 구현 후 세미콜론을 통해 클래스 끝을 나타낸다.
마지막으로 29-32행은 익명 클래스를 사용하는 방법으로, 지역 내부 클래스와 사용 방법은 동일하다.
Runnable 인터페이스 자료형으로 변수를 선언하고, 인터페이스의 익명 내부 클래스가 구현된 메서드를 호출하면 인스턴스를 반환한다. 그리고 runnable.run( ) 또는 out.runner.run( )으로 인터페이스의 메서드를 호출할 수 있다.
정리하면 익명 내부 클래스는 변수에 직접 대입하는 경우도 있고 메서드 내부에서 인터페이스나 추상 클래스를 구현하는 경우도 있다. 이때 사용하는 지역 변수는 상수화되므로 메서드 호출이 끝난 후에도 사용할 수 있다.
종류 | 구현 위치 | 사용할 수 있는 외부 클래스 변수 |
생성 방법 |
인스턴스 내부 클래스 | 외부 클래스 멤버 변수와 동일 | 외부 인스턴스 변수 외부 전역 변수 |
외부 클래스를 먼저 만든 후 내부 클래스 생성 |
정적 내부 클래스 | 외부 클래스 멤버 변수와 동일 | 외부 전역 변수 | 외부 클래스와 무관하게 생성 |
지역 내부 클래스 | 메서드 내부에 구현 | 외부 인스턴스 변수 외부 전역 변수 |
메서드를 호출할 때 생성 |
익명 내부 클래스 | 메서드 내부에 구현 변수에 대입하여 직접 구현 |
외부 인스턴스 변수 외부 전역 변수 |
메서드를 호출할 때 생성되거나, 인터페이스 타입 변수에 대입할 때 new 예약어를 사용하여 생성 |
'프로그래밍 > JAVA' 카테고리의 다른 글
14-1 예외 클래스 (0) | 2023.03.16 |
---|---|
13-2 람다식 (0) | 2023.03.14 |
12-5 Map 인터페이스 (0) | 2023.03.08 |
12-4 Set 인터페이스 (0) | 2023.03.08 |
12-3 List 인터페이스 (0) | 2023.03.08 |