자바가 클래스 중첩을 지원할 필요가 없다. 만약 Inner Classes Specification 문서를 공부했다면, 중첩 클래스에 대한 문제들을 발견할 것이다. 하지만 자바의 중첩 클래스 지원은 적어도 두가지 이점이 있다.
중첩 클래스는 소스코드의 투명성을 증진시킨다. 객체에 밀접한 클래스 선언을 할 수 있다는 것은 조작하기 쉽게 만들고, 클래스의 메쏘드에서 객체의 필드를 직접 접근할 수 있게 하며 객체의 메쏘드를 직접 호출 있도록 해준다. 비록 private 필드와 메쏘드 일지라도 가능하다. 그런 이점을 이해하기 위해서, Employee 객체 안에 존재하는 Job 객체의 배열을 나열해야 하는 프로그램을 가정해보자.
JobIterator1은 Job, Employee,
JobIterator1 클래스로 구성된다. Job 클래스는 job 타이틀을 캡슐화 하고 Employee 객체는 고용인(employee) 이름과 직업을 저장하는 배열을 캐슐화 한다.
JobIterator1은 Job과 Employee 객체, 그리고 고용인의 이름과 직업을 찍는 main() 메쏘드를 포함한다.
Employee 클래스에 드러난 hasMoreJobs()와 nextJob() 함수를 자세히 보자. 이 함수들은 반복자(Iterator)를 구성한다. Employee 객체가 초기화 될때, 내부 private 멤버인 jobs는 0으로 설정된다. 만약 인덱스의 값이 jobs 배열의 길이보다 작다면 hasMoreJobs() 함수는 TRUE를 리턴한다. nextJob() 함수는 인덱스 배열로부터 Job 객체를 반환받기 위해서 인덱스의 값을 사용하고, 동시에 인덱스의 값은 다음 객체의 참조를 위해서 증가한다.
JobIterator1 클래스는 문제가 있다. 첫째, 완벽하게 끝난 후에 재 시작을 할 수 없다. 하지만, 인덱스의 값을 0으로 초기화 하는 reset() 이라는 함수를 추가함으로써 쉽게 문제를 해결할 수 있을 것이다. 둘째, 좀더 심각한 문제는 하나의 Employee 객체를 위한 여러 개의 반복자(Iterator)를 만드는 것이 불가능하다. 그 문제를 해결하기 위해서, 전형적으로 개발자는 Iterator라는 클래스를 정의한다. 한번의 Iterator 클래스가 끝나고, 프로그램은 새로운 Iterator 객체를 만듬으로써 새로운 반복을 시작할 수 있다. 또한 여러 개의 Iterator 객체를 생성함으로써, 프로그램은 같은 Employee 객체의 job 배열을 여러 번 반복 수행 할 수 있다. Listing2는
JobIterator라 명명한 반복자 클래스의 사용을 보여준다.
~cpp
// JobIterator2.java
class Job
{
private String jobTitle;
Job (String jobTitle)
{
this.jobTitle = jobTitle;
}
public String toString ()
{
return jobTitle;
}
}
class Employee
{
private String name;
private Job [] jobs;
Employee (String name, Job [] jobs)
{
this.name = name;
this.jobs = jobs;
}
String getName ()
{
return name;
}
JobIterator getJobIterator ()
{
return new JobIterator (jobs);
}
}
class JobIterator
{
private Job [] jobs;
private int jobIndex = 0;
JobIterator (Job [] jobs)
{
this.jobs = jobs;
}
boolean hasMoreJobs ()
{
return jobIndex < jobs.length;
}
Job nextJob ()
{
return !hasMoreJobs () ? null : jobs [jobIndex++];
}
}
class JobIterator2
{
public static void main (String [] args)
{
Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
Employee e = new Employee ("John Doe", jobs);
System.out.println (e.getName () + " works the following jobs:\n");
JobIterator ji = e.getJobIterator ();
while (ji.hasMoreJobs ())
System.out.println (ji.nextJob ());
}
}
비록
JobIterator2가 편리하게
JobIterator1의 문제를 해결했지만, 새로운 프로그램은 새로운 문제를 만들었다. Employee 객체와 깊이 연관된
JobIterator2는 독립적인
JobIterator로써의 역할을 할 수 없게 된다. 비록 우리의 예제에서는 사소한 문제이지만, 중대한 프로그램에서는 심각한 문제일 수 있다. 데이터 공유를 만들기 위해서, 몇몇 클래스들이 완벽하게 다른 클래스들에 의존한다는 것을 인지해야한다. 의존 클래스들을 그들이 의존하는 클래스들 안에 선언해야 할 것이다. Listing3은
JobIterator 클래스를 Employee 클래스 안에 선언하는 방법을 보여준다.
~cpp
class Job
{
private String jobTitle;
Job (String jobTitle)
{
this.jobTitle = jobTitle;
}
public String toString ()
{
return jobTitle;
}
}
class Employee
{
private String name;
private Job [] jobs;
Employee (String name, Job [] jobs)
{
this.name = name;
this.jobs = jobs;
}
String getName ()
{
return name;
}
JobIterator getJobIterator ()
{
return new JobIterator ();
}
class JobIterator
{
private int jobIndex = 0;
public boolean hasMoreJobs ()
{
return jobIndex < jobs.length;
}
public Object nextJob ()
{
return !hasMoreJobs () ? null : jobs [jobIndex++];
}
}
}
class JobIterator3
{
public static void main (String [] args)
{
Job [] jobs = { new Job ("Janitor"), new Job ("Delivery Person") };
Employee e = new Employee ("John Doe", jobs);
System.out.println (e.getName () + " works the following jobs:\n");
Employee.JobIterator eji = e.getJobIterator ();
while (eji.hasMoreJobs ())
System.out.println (eji.nextJob ());
}
}
소스 코드의 투명성의 증진을 더하기 위해서, 클래스 중첩의 두번째 이점을 고려해보자. 중첩된 클래스들이 다른 클래스들 안에 있을 때, 이름 충돌은 감소한다. Listing3을 자세히 보면, 최상위 클래스들은 Job, Employee,
JobIterator3이다. 또한 Employee.
JobIterator 클래스가 있다. 만약 우리가 Employee와 같은 Level의
JobIterator 인터페이스를 추가한다고 생각하면, Job, Employee,
JobIterator3,
JobIterator가 최상위 클래스이다. Employee.
JobIterator와
JobIterator가 다른 이름을 나타내기 때문에, 이름 충돌의 문제는 발생하지 않는다.