로컬 클래스
git/eomcs-java-basic/src/main/java com.eomcs.oop.ex11.d
메서드 안에 정의하는 클래스를 local class라고 하며, 중첩 클래스가 특정 메서드 안에서만 사용되는 경우 로컬 클래스로 정의한다. 로컬 클래스로 정의하는 이유는 외부의 노출을 방지함으로써 유지보수를 좋게 하기 위함이다. 즉, 메서드 안으로 사용 범위를 제한하는 캡슐화를 위한 문법이다. 로컬 클래스는 메서드 안으로 사용 범위를 제한할 뿐, 메서드를 호출할 때 클래스가 정의된다는 뜻이 아니다.
class A {
void m1() {
class X {
}
X obj = new X();
}
static void m2() {
class X {
}
X obj = new X();
}
}
로컬 클래스의 접근 범위
로컬 클래스가 정의된 메서드 밖에서는 어디에서도 로컬 클래스를 사용할 수가 없다.
class B {
void m1() {
class X {
}
X obj = new X();
}
static void m2() {
//X obj; // 컴파일 오류!
}
}
.class 파일명
각각 다른 메서드에서 같은 이름의 로컬 클래스를 정의하는 것이 가능하기 때문에 같은 이름의 로컬 클래스들을 구별하기 위해 컴파일러는 클래스명 앞에 같은 이름의 클래스가 정의된 순서대로 1부터 숫자를 붙인다.
class B2 {
void m1() {
class X {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$1X.class
}
class Y {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$1Y.class
}
}
static void m2() {
class Y {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$2Y.class
}
class X {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$2X.class
}
class Z {
// 로컬 클래스의 .class 파일명
// => [바깥클래스명]$[정의된순서][로컬클래스명].class
// => 예) B2$1Z.class
}
}
}
로컬 클래스의 내장 변수
스태틱 메서드 안에는 그 클래스의 주소를 담은 this 내장 변수를 갖고 있찌 않으므로 그 안의 클래스도 마찬가지로 바깥 클래스에 대한 정보를 담은 내장 변수가 없다.
반면 인스턴스 메서드 안에 정의된 로컬 클래스는 인스턴스 메서드가 갖는 this 내장 변수를 그대로 가지며, 뿐만 아니라 로컬 클래스가 정의된 메서드 안에 있는 로컬 변수까지 함께 저장된다.
class D {
int v1 = 1;
void m1() {
// 인스턴스 메서드는 this에 C 인스턴스 주소를 저장하고 있다.
int v2 = 2;
class X {
int v3 = 3;
// .class 파일의 코드:
// class com.eomcs.oop.ex11.d.D$1X {
//
// int v3; <== 로컬 클래스의 인스턴스 필드
// final synthetic com.eomcs.oop.ex11.d.D this$0; <== 바깥 클래스의 인스턴스 주소 저장
// private final synthetic int val$v2; <== 바깥 메서드의 로컬 변수 저장
//
void f() {
int v4 = 4;
System.out.printf("v4 = %d\n", v4);
System.out.printf("v3 = %d\n", this.v3);
System.out.printf("v2 = %d\n", v2);
System.out.printf("v1 = %d\n", D.this.v1);
}
}
X obj = new X();
obj.f();
}
}
로컬 클래스에서 변수를 찾는 순서
로컬 클래스에서 내장 변수를 명시하지 않고, 변수명만 사용했을 때 다음과 같은 순서로 변수를 검색한다.
- 로컬 클래스 안에 정의된 메서드 안의 로컬 변수
- 로컬 클래스 안에 정의된 인스턴스 변수
- 로컬 클래스가 정의된 메서드 안의 로컬 변수
- 바깥 클래스의 인스턴스 / 스태틱 변수
class D2 {
int v1 = 1;
void m1() {
//int v1 = 10;
class X {
//int v1 = 100;
void f() {
//int v1 = 1000;
System.out.printf("v1 = %d\n", v1);
}
}
X obj = new X();
obj.f();
}
}
메서드의 로컬 변수를 로컬 클래스 안에서 사용하기
어떤 메서드 안에서 정의된 로컬 변수와 로컬 클래스가 있을 때, 로컬 클래스 안에서 로컬 변수를 사용하기 위해서는 사용할 로컬 변수가 상수 혹은 상수에 준하는 변수가 되어야 한다. 그러려면 로컬 변수는 다음 조건을 만족해야 한다.
- final로 선언된다.
- 값을 한 번만 할당한다.
로컬 클래스와 로컬 변수는 메서드 호출이 완료되면 사라지기 때문에 로컬 클래스는 사용하는 로컬 변수를 조회하는 용도로만 사용해야 한다. 따라서 로컬 클래스가 로컬 변수를 사용할 때 내부적으로 로컬 변수 그 자체를 사용하지 않고, 그 값을 복사한 또 다른 변수를 사용한다.따라서 로컬 클래스가 로컬 변수를 사용한다고 해도 직접 사용하는 것이 아니며 이 변수의 값을 바꾸는 것도 의미가 없기 때문에 완전히 조회용으로 사용하기 위해서 로컬 변수는 값을 변경할 수 없는 상수 혹은 상수에 준하는 변수가 되어야 하는 것이다.
public class Exam0340 {
Runner createRunner(final String name) {
class A implements Runner {
/*
* Exam0340 outer; // 바깥 클래스의 주소를 받을 필드
*
* String paramName; // run() 메서드에서 사용할 로컬 변수을 값을 받을 필드
*
* public A(Exam0365 obj, String str) { outer = obj; paramName = str;}
*
*/
@Override
public void run() {
System.out.printf("%s님이 달립니다!", name);
}
}
return new A();
}
}
실습 - 이너 클래스
git/eomcs-java-project/mini-pms-26.b
스태틱 중첩 클래스였던 ListIterator, StackIterator, QueueIterator를 논 스태틱 중첩 클래스, 즉 이너 클래스로 바꿔주면, 쉽게 바깥 클래스, 즉 AbstractList나, Stack, Queue 클래스의 필드에 접근이 가능하다.
훈련 목표
-
Iterator 구현체를 스태틱 중첩 클래스가 아니라 논 스태틱 중첩 클래스로 변경한다.
-
Iterator 구현체에 있던 필드를 삭제하고 바깥 클래스에서 직접 갖고 온다.
1단계 : ListIterator의 선언부에 static 을 빼준다. 그러면 AbstractList에서 선언된 타입 파라미터 E가 ListIterator 내부까지 영향을 끼칠 수 있기 때문에 굳이 ListIterator에서 직접 타입파라미터를 선언할 필요가 없다. 그리고 이미 ListIterator 객체를 생성할 때 외부 객체의 주소가 그 안에 저장되어있기 때문이다. 필드가 필요없기 때문에 생성자의 파라미터도 지워준다.next() 메서드에서 리턴할 때 사용되는 get 메서드도 ListIterator 객체 안에 저장된 외부 객체의 주소를 갖고 호출한다. 즉 AbstractList.this.get()을 간략히 한 형태이다.
package com.eomcs.util;
import java.util.NoSuchElementException;
public abstract class AbstractList<E> implements List<E>{
@Override
public Iterator<E> iterator() {
return new ListIterator();
}
private class ListIterator implements Iterator<E> {
private int cursor;
@Override
public boolean hasNext() {
return cursor < AbstractList.this.size();
}
@Override
public E next() {
if (cursor == size()) {
throw new NoSuchElementException();
}
return get(cursor++);
}
}
}
2단계 : StackIterator와 QueueIterator도 마찬가지로 static을 삭제하는 데, 이것은 외부 객체를 그대로 사용하는 것이 아니라, 외부 객체를 복제한 객체를 사용해야하므로 여전히 필드는 필요하다.
package com.eomcs.util;
import java.util.EmptyStackException;
import java.util.NoSuchElementException;
public class Stack<E> extends LinkedList<E> {
@Override
public Iterator<E> iterator() {
return new StackIterator();
}
private class StackIterator implements Iterator<E> {
Stack<E> stack;
public StackIterator() {
try {
this.stack = Stack.this.clone();
} catch (Exception e) {
throw new RuntimeException("스택 복제 중 오류 발생");
}
}
@Override
public boolean hasNext() {
return !stack.empty();
}
@Override
public E next() {
if (stack.empty()) {
throw new NoSuchElementException();
}
return stack.pop();
}
}
}
실습 - 로컬 클래스
git/eomcs-java-project/mini-pms-26.c
이 Iterator 구현체가 외부 클래스의 iterator() 메서드에서만 사용되고 있기 때문에 이 메서드 안에서 클래스를 정의하자. 이렇게 되면 외부에서 절대 이 클래스에 접근할 수 없다. 또한 논스태틱 클래스처럼 내장 변수 this가 존재하여 외부 클래스의 모든 필드에 접근이 가능하다.
훈련 목표
Iterator 구현체를 iterator 메서드의 로컬 클래스로 정의한다.
1단계 : Iterator 구현체를 외부 클래스의 iterator() 메서드에 정의하고, 이 객체를 생성하여 리턴한다.
import java.util.NoSuchElementException;
public abstract class AbstractList<E> implements List<E>{
protected int size;
@Override
public int size() {
return size;
}
@Override
public Iterator<E> iterator() {
class ListIterator implements Iterator<E> {
private int cursor;
@Override
public boolean hasNext() {
return cursor < AbstractList.this.size();
}
@Override
public E next() {
if (cursor == size()) {
throw new NoSuchElementException();
}
return AbstractList.this.get(cursor++);
}
}
return new ListIterator();
}
}
'국비 교육' 카테고리의 다른 글
2020.9.17 일자 수업 : 파일 입출력, 바이트 스트림, 캐릭터 스트림 (0) | 2020.10.01 |
---|---|
2020.9.25 일자 수업 : JSON, 네트워크 (0) | 2020.09.26 |
2020.9.24 일자 수업 : character stream class (0) | 2020.09.25 |