본문 바로가기
  • soobinhand의 기술 블로그
도서/자바 프로그래밍 언어 - James Gosling

[자바 프로그래밍 언어] 5장 중첩 클래스와 인터페이스

by soobinhand 2021. 12. 25.
728x90

5.1     정적 중첩 타입

  • 중첩 타입을 정의할 수 있다는 것은 다음의 두 가지 주요 용도를 지원할 수 있다는 것을 의미한다.
    • 중첩 클래스와 중첩 인터페이스가 논리적으로 관련된 그룹에 구조화되고 같은 범위에 속하는 타입이 될 수 있게 해준다.
    • 중첩 클래스가 논리적으로 연관된 객체를 간단하고도 효율적으로 연결할 수 있다.
  • 중첩 타입은 이 타입을 선언한 타입의 일부분이 되며 이 둘은 서로의 멤버에 접근할 수 있는 관계.
  • 정적일 경우에는 단순한 구조의 타입을 허용하는 반면, 아닐 경우에는 중첩 객체와 이를 감싸는 외부 객체와의 특별한 관계를 정의해야 한다.
  • 외부 클래스나 인터페이스 내에 static 멤버로 선언된 중첩 클래스나 중첩 인터페이스는 최상위 클래스나 최상위 인터페이스처럼 동작한다.
  • 중첩 타입의 이름은 "외부이름.중첩이름"으로 표현.
  • 그래서 중첩 타입은 외부 타입에 접근할 수 있는 경우에만 접근 가능.
  • 정적 중첩 타입은 외부 타입 내에 선언된 멤버이기 때문에 적당한 객체 참조만 있다면 private 멤버를 포함한 모든 멤버에 접근 가능.
  • 정적 중첩 클래스는 형식이 단순하다. 이 클래스를 선언하기 위해서는 클래스 선언 앞에 static 제한자를 선언하기만 하면 된다.
  • 인터페이스에서 중첩 클래스를 선언할 경우, 무조건 static이어야 하며 제한자는 선언 안 해도 됨.
  • 정적 중첩 클래스는 최상위 클래스처럼 동작함. 다른 클래스를 확장할 수도 있으며 다른 인터페이스를 구현할 수도 있음. 그리고 다른 클래스가 이 클래스를 확장할 수도 있음.
public class BankAccount{
	private long number;
    private long balance;
    
    public static class Permissions{
    	public boolean cnaDeposit, canWithdraw, canClose;
    }
}
  • Permissions 클래스를 BankAccount 클래스 내부에 정의해서 BankAccount 클래스의 멤버로 만든다.
  • 아예 외부에서 Permissions에 접근하기 위해서는 완전한 이름을 사용해야 한다.
  • BankAccount.Permissions perm = ~~
  • 인터페이스 내부에 선언된 정적 중첩 클래스는 무조건 public이지만 정적 중첩 클래스를 클래스 내부에 선언하는 경우에는 접근 제한자를 원하는데로 선언 가능.
  • 중첩 인터페이스는 항상 static이며, 협약에 따라 인터페이스 선언 시 생략 가능.

5.2     내부 클래스

  • 비정적 중첩 클래스를 내부 클래스라 부르기도 한다. 비정적 클래스 멤버들은 클래스의 인스턴스와 연관된다.
  • 내부 클래스의 선언은 한 가지 제약 사항을 제외하고는 최상위 클래스를 선언하는 것과 동일하다. 이 제약 사항은 내부 클래스가 static 멤버를 가질 수 없다는 것이다. 하지만 상수나 상수를 생성하는 표현식으로 초기화해야 하는 final static 필드는 선언 가능.
  • 중첩 클래스는 외부 클래스의 private 필드와 메소드를 포함한 모든 멤버를 조건 없이 접근할 수 있다. 이는 반대도 마찬가지.
  • 외부 객체에 대한 참조를 위해서는 외부 클래스 이름과 this를 함께 사용하면 된다. 이를 한정된 this라 한다. X.this.method()처럼 말이다.
  • 중첩 단계는 한 단계 정도가 적당하다. 그 이상이 되면 가독성 및 사용성을 떨어뜨린다.
  • 내부 클래스도 정적 중첩 클래스나 최상위 클래스처럼 다른 클래스를 확장할 수 있다.
class Host{
	int x;
    
    class Helper extends Unknown{
    	void increment(){ x++; }
    }
}
  • Unknown에도 x 필드가 있다고 한다면 내부 클래스의 x는 Host 것이 아니라 Unknown 것이 된다. 코드가 어떤 일을 하는지 헷갈리지 않게 하기 위해서는 이처럼 이름만 사용해서는 안 된다. 그래서 this.x나 Host.this.x처럼 참조하는 대상을 명시해야 함.

5.3     지역 내부 클래스

  • 내부 클래스는 메소드 몸체, 생성자, 초기화 블록 등의 코드 블록 내에 선언할 수도 있음.
  • 이를 지역 내부 클래스라고 함.
  • 지역 내부 클래스는 클래스의 멤버가 아니며 단지 지역 변수처럼 블록 내에 있는 코드의 일부분이다.
  • 이러한 내부 클래스는 이 클래스가 정의된 블록 외부에서는 절대 접근할 수 없다.
  • 지역 내부 클래스는 외부에서 접근할 수 없으니 접근 제한자를 가질 수 없고, static으로 선언도 불가.
  • 지역 내부 클래스는 범위 내에 정의된 지역 변수, 메소드 매개변수, 인스턴스 변수, 정적 변수와 같은 변수에 접근 가능. 변수 접근 시 유일한 제약은 지역 변수와 메소드 매개변수가 final로 선언되었을 때만 접근 가능하다는 것.
  • 내부 클래스는 외부 클래스의 인스턴스와 항상 관련되어 있다.

5.4     익명 내부 클래스

  • 지역 내부 클래스가 과분하다면 클래스를 확장하거나 인터페이스를 구현할 수 있는 익명 내부 클래스를 사용해야 한다. 이 클래스는 new 연산자와 함께 인스턴스화되는 동시에 정의된다.
public static Iterator<Object> walkThrough(final Object[] objs){
	return new Iterator<Object>(){
    	private int pos = 0;
        public boolean hasNext(){
        	return ( pos < objs.length);
        }
    }
}
  • 익명 클래스는 new 표현식에서 표현식의 일부로 정의된다. new가 명시한 타입은 익명 클래스의 슈퍼 타입이다.
  • 익명 클래스는 extends와 implements문을 명시할 수 없으며 어노테이션을 포함하여 어떤 제한자도 가질 수 없다.
  • 익명 내부 클래스는 이름을 가질 수 없기에 생성자를 명시적으로 선언할 수 없다. 그래서 익명 클래스가 생성자를 명시해야 할 정도로 복잡해진다면 지역 내부 클래스를 사용해야 한다.

5.5     중첩 타입의 상속

  • 중첩 타입이 정적 클래스든, 정적 인터페이스든, 내부 클래스든 이 모두는 필드가 상속되는 방식과 동일한 방식으로 상속된다.
  • 팩토리 메소드는 내부 클래스와 동일한 종류의 객체를 생성하기 위해 서브 클래스에서 오버라이드할 수 있는 메소드이다.
public abstract class Device {
    abstract class Port{

    }
}

class Printer extends Device{
    class SerialPort extends Port{

    }
    Port serial = createSerialPort();
    protected Port createSerialPort(){
        return new SerialPort();
    }
}
class HighSpeedPrinter extends Printer{
    class EnhancedSerialPort extends SerialPort{
        // ...
    }
    protected Port createSerialPort(){
        return new EnhancedSerialPort();
    }
}
  • HighSpeedPrinter 객체가 생성되고 Printer 클래스의 초기자가 실행되면 createSerialPort 오버라이드 메소드가 호출돼서 EnhancedSerialPort 인스턴스가 반환된다.
  • 이 예제는 서브 클래스 객체가 완전히 생성되기 전에 호출되는 서브 클래스의 메소드를 보여준다.

5.6     인터페이스 내의 중첩

  • 클래스 내에 중첩 클래스와 중첩 인터페이스를 선언할 수 있는 것처럼 인터페이스 내에서도 이들을 선언할 수 있다.
public interface Changeable {
    class Record{
        public Object changer;
        public String changeDesc;
    }
    Record getLastChange();
}
  • getLastChange 메소드는 Changeable.Record 객체를 반환하며 이 객체는 변경을 가한 객체와 변경한 내역을 포함한다.
  • 인터페이스 내에서 중첩 클래스의 또 다른 사용법은 인터페이스에 대한 기본 구현을 정의하는 것이다.
  • 인터페이스에서 공유되고 수정할 수 있는 데이터가 필요한 경우에는 중첩 클래스가 적당한 대안이다. 공유할 데이터를 저장할 필드와 이를 접근하기 위한 메소드를 중첩 클래스에 선언하고 이 클래스의 인스턴스를 참조하는 변수를 선언해야 한다.
public interface SharedData {
    class Data{
        private int x = 0;
        public int getX() {return x;}

        public void setX(int x) {
            this.x = x;
        }
    }
    Data data = new Data();
}
  • SharedData의 구현자와 사용자는 data 참조로 공통된 상태를 공유할 수 있다.

5.7     중첩 타입 구현

  • 정적이거나 비지역 중첩 타입으로 정의된 Outer.inner를 생각해보자. Outer.Inner는 소스 코드의 클래스 이름이다. 
  • 그래서 중첩 타입을 구현할 때는 두 가지 경우를 고려해야 함.
    • 애플리케이션의 클래스 파일을 묶을 때 파일 이름에 $가 있는지 살펴야 함.
    • 만약 중첩 클래스 인스턴스를 생성하기 위해 리플렉션 매커니즘을 사용하고 있다면 변형된 클래스 이름도 알아야 함.
728x90

댓글