정적 클래스보다 정적이 아닌 내부 클래스를 선호하는 이유는 무엇입니까?


37

이 질문은 Java에서 중첩 클래스를 정적 ​​중첩 클래스 또는 내부 중첩 클래스로 만들지 여부에 관한 것입니다. 여기와 Stack Overflow에서 검색했지만이 결정의 디자인 영향에 관한 질문은 실제로 찾을 수 없었습니다.

내가 찾은 질문은 정적 클래스와 내부 중첩 클래스의 차이점에 대해 묻는 것입니다. 그러나 익명 클래스를 제외하고 Java에서 정적 중첩 클래스를 사용해야하는 확실한 이유를 아직 찾지 못했습니다.이 클래스는 고려하지 않습니다.

정적 중첩 클래스 사용의 효과에 대한 이해는 다음과 같습니다.

  • 적은 커플 링 : 클래스가 외부 클래스의 속성에 직접 액세스 할 수 없으므로 일반적으로 커플 링이 줄어 듭니다. 커플 링이 적 으면 일반적으로 더 나은 코드 품질, 더 쉬운 테스트, 리팩토링 등을 의미합니다.
  • Single Class: 클래스 로더는 매번 새 클래스를 관리 할 필요가 없으며 외부 클래스의 객체를 만듭니다. 우리는 같은 클래스에 대한 새로운 객체를 계속해서 얻습니다.

내부 클래스의 경우 일반적으로 사람들은 외부 클래스의 속성에 대한 액세스 권한을 전문가로 간주합니다. 이 관점에서 디자인 관점과 다른 점이 있습니다.이 직접 액세스는 높은 결합을 의미하며 중첩 된 클래스를 별도의 최상위 클래스로 추출하려는 경우 본질적으로 선회 한 후에 만 ​​할 수 있습니다 정적 중첩 클래스로

그래서 내 질문은 이것으로 귀착됩니다 : 비 정적 내부 클래스에 사용 가능한 속성 액세스가 높은 커플 링을 초래하여 코드 품질이 낮아지고 (익명 적이 지 않은) 중첩 클래스는이를 유추한다고 가정하는 것이 잘못입니까 일반적으로 정적인가?

즉, 중첩 된 내부 클래스를 선호하는 설득력있는 이유가 있습니까?


2
에서 자바 튜토리얼 : ".이 액세스 할 필요가없는 경우 사용 비 정적 중첩 클래스 (또는 내부 클래스는) 당신이 둘러싸는 인스턴스의 비공개 필드 및 메소드에 액세스해야하는 경우 정적 중첩 클래스를 사용합니다."
gnat

5
튜토리얼 따옴표에 감사드립니다. 그러나 이것은 인스턴스 필드에 액세스하려는 이유가 아니라 차이점이 무엇인지를 반복합니다.
Frank

오히려 선호하는 것에 대해 암시하는 일반적인 지침입니다. 이 답변
gnat

1
내부 클래스가 엔 클로징 클래스와 밀접하게 연결되어 있지 않으면 처음에는 내부 클래스가 아니어야합니다.
Kevin Krumwiede

답변:


23

그의 저서 "Effective Java Second Edition"의 항목 22에있는 Joshua Bloch는 어떤 종류의 중첩 클래스를 사용해야하는 이유와 이유를 알려줍니다. 아래에 인용문이 있습니다.

정적 멤버 클래스의 일반적인 용도 중 하나는 공용 도우미 클래스로서 외부 클래스와 함께 사용하는 경우에만 유용합니다. 예를 들어, 계산기가 지원하는 작업을 설명하는 열거 형을 고려하십시오. Operation 열거 형은 클래스의 공개 정적 멤버 클래스 여야합니다 Calculator. 클라이언트는 and와 Calculator같은 이름을 사용하는 작업을 참조 할 수 있습니다.Calculator.Operation.PLUSCalculator.Operation.MINUS

비 정적 멤버 클래스의 일반적인 용도 중 하나 는 외부 클래스의 인스턴스를 관련없는 클래스의 인스턴스로 볼 수있는 어댑터 를 정의하는 것 입니다. 예를 들어, 구현의 Map인터페이스는 일반적으로 그들의 구현 비 정적 부재 클래스를 사용하여 수집 뷰 에 의해 반환되는, MapkeySet, entrySet그리고 values방법. 마찬가지로 Setand와 같은 컬렉션 인터페이스의 구현은 List일반적으로 비 정적 멤버 클래스를 사용하여 반복자를 구현합니다.

// Typical use of a nonstatic member class
public class MySet<E> extends AbstractSet<E> {
    ... // Bulk of the class omitted

    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        ...
    }
}

엔 클로징 인스턴스에 액세스 할 필요가없는 멤버 클래스를 선언하는 경우 항상static 수정자를 선언에 배치하여 정적이 아닌 멤버 클래스가 아닌 정적 클래스로 만드십시오.


1
어댑터 가 외부 클래스 MySet인스턴스 의 뷰를 어떻게 변경 하는지 확실하지 않습니까? 나는 경로 의존적 유형과 같은 것을 차이점으로 생각할 수 myOneSet.iterator.getClass() != myOtherSet.iterator.getClass()있지만, 다시 말하지만 Java에서는 클래스가 동일하므로 실제로는 불가능합니다.
Frank

@Frank 꽤 일반적인 어댑터 클래스입니다. 세트를 해당 요소의 스트림 (반복자)으로 볼 수 있습니다.
user253751

@immibis 덕분에 반복자 / 어댑터의 기능을 잘 알고 있지만이 질문 은 구현 방법관한 것입니다.
Frank

@ Frank는 외부 인스턴스의보기를 어떻게 변경하는지 말하고있었습니다. 이것이 바로 어댑터가하는 일입니다. 일부 객체를 보는 방식이 바뀝니다. 이 경우 해당 개체는 외부 인스턴스입니다.
user253751

10

정적이 아닌 내부 클래스에 사용 가능한 속성 액세스가 높은 커플 링을 초래하므로 코드 품질이 낮아지고 (익명 및 로컬이 아닌 ) 내부 클래스는 일반적으로 정적이어야 한다고 가정하는 것이 정확 합니다.

내부 클래스를 비 정적으로 만드는 결정에 대한 디자인의 의미는 Java Puzzlers , Puzzle 90 (아래 인용의 굵은 글꼴은 내 것임)에 나와 있습니다.

멤버 클래스를 작성할 때마다 스스로에게 물어보십시오.이 클래스에는 실제로 엔 클로징 인스턴스가 필요합니까? 대답이 '아니요'인 경우 확인하십시오 static. 내부 수업은 때때로 유용하지만 , 프로그램을 이해하기 어렵게하는 복잡한 문제를 쉽게 도입 할 수 있습니다. 그들은 제네릭 (퍼즐 89), 리플렉션 (퍼즐 80), 상속 (이 퍼즐)과 복잡한 상호 작용을 합니다. 당신이 선언하면 Inner1static문제가 사라집니다. 당신은 또한 선언하면 Inner2static멋진 보너스를 참 : 실제로 프로그램이 무엇을하는지 이해할 수있다.

요약하면, 한 클래스가 다른 클래스의 내부 클래스와 서브 클래스 인 경우는 거의 없습니다. 더 일반적으로, 내부 클래스를 확장하는 것이 거의 적절하지 않습니다. 필요한 경우 둘러싼 인스턴스에 대해 길고 열심히 생각하십시오. 또한 static중첩 클래스가 아닌 클래스를 선호합니다 static. 대부분의 멤버 클래스는 선언 할 수 있으며 선언해야합니다 static.

관심이 있으시면 Stack Overflow에서이 답변에 Puzzle 90에 대한보다 자세한 분석 이 제공됩니다 .


위의 내용은 본질적으로 Java 클래스 및 객체 자습서 에서 제공되는 확장 된 버전의 지침입니다 .

둘러싸는 인스턴스의 비공개 필드 및 메서드에 액세스해야하는 경우 비 정적 중첩 클래스 (또는 내부 클래스)를 사용하십시오. 이 액세스가 필요하지 않은 경우 정적 중첩 클래스를 사용하십시오.

그래서, 질문에 대한 대답은 당신이 묻는 튜토리얼 당이다, 유일한 설득력 이유 클로징 인스턴스의 비공개 필드 및 메소드에 액세스 할 때 비 정적을 사용할 수있다 필요 .

튜토리얼 문구는 다소 광범위합니다 (이것이 Java Puzzlers가 그것을 강화하고 좁히려 고 시도하는 이유 일 수 있습니다). 특히, 엔클로저 인스턴스 필드에 직접 액세스 하는 것은 필자의 경험에서 실제로 필요한 적이 없었습니다. 이를 생성자 / 메소드 매개 변수로 전달하는 것과 같은 대체 방법은 항상 디버그 및 유지 관리가 쉬워졌습니다.


전반적으로, 둘러싼 인스턴스의 필드에 직접 액세스하는 내부 클래스를 디버깅하는 나의 (아주 고통스러운) 만남은이 연습이 관련된 알려진 악과 함께 전역 상태의 사용과 유사하다는 강한 인상을 남겼 습니다.

물론 Java는 그러한 "quasi global"의 손상이 둘러싼 클래스에 포함되도록 만들지 만 특정 내부 클래스를 디버깅해야 할 때 그러한 밴드 보조가 고통을 줄이는 데 도움이되지 않는 것처럼 느꼈습니다. 특정 번거로운 대상의 분석에 전적으로 초점을 맞추는 대신 "외국 적"의미 및 세부 사항을 명심하십시오.


완전성을 위해 위의 추론이 적용되지 않는 경우 가 있을 수 있습니다 . 예를 들어 map.keySet javadocs를 읽을 때 마다이 기능은 긴밀한 결합을 제안하며 결과적으로 비 정적 클래스에 대한 인수를 무효화합니다.

Set이 맵에 포함 된 키 의 뷰를 반환합니다 . 세트는 맵에 의해 지원되므로 맵에 대한 변경 사항이 세트에 반영되며 그 반대도 마찬가지입니다.

위의 방법으로 어떻게 든 관련 코드를 유지 관리, 테스트 및 디버그하기가 더 쉬울 수는 없지만 의도 한 기능으로 인해 합병증이 일치 / 정당화 될 수 있다고 주장 할 수 있습니다.


이 높은 커플 링으로 인한 문제에 대한 귀하의 경험을 공유하지만 튜토리얼의 require 사용법에 대해서는 좋지 않습니다 . 당신은 실제로 현장 접근이 필요하지 않았다고 스스로에게 말합니다. 불행히도, 이것은 또한 내 질문을 완전히 해결하지 못합니다.
Frank

당신이 볼 수 있듯이 @Frank, (Java Puzzlers가 지원하는 것으로 보이는) 튜토리얼 안내에 대한 나의 해석은 비 정적 클래스를 사용해야하며, 이것이 필요하다는 것을 증명할 수있을 때만, 특히 대체 방법이 더 나쁘다 . 내 경험에 의하면 대체 방법이 항상 더 나아 졌다는 점에 주목할 가치가있다
gnat

그게 요점입니다. 이 경우 항상 더 나은, 우리는 왜 귀찮게합니까?
Frank

@ 프랭크 (Frank)의 또 다른 대답 은 이것이 항상 그렇지 는 않은 매력적인 예를 제공합니다 . map.keySet javadocs를 읽은 결과,이 기능은 밀접한 결합을 제안하고 결과적으로 비 정적 클래스에 대한 인수를 무효화합니다. "세트는지도에 의해 지원되므로지도에 대한 변경 사항은 세트에 반영되며 그 반대도 마찬가지입니다. ... "
gnat

1

몇 가지 오해 :

적은 커플 링 : [static inner] 클래스는 외부 클래스의 속성에 직접 액세스 할 수 없으므로 일반적으로 커플 링이 줄어 듭니다.

IIRC-실제로는 가능합니다. (물론 객체 필드 인 경우 살펴볼 외부 객체가 없습니다. 그럼에도 불구하고, 이러한 객체를 어디서나 가져 오면 해당 필드에 직접 액세스 할 수 있습니다).

단일 클래스 : 클래스 로더는 매번 새 클래스를 관리 할 필요가 없으며 외부 클래스의 객체를 만듭니다. 우리는 같은 클래스에 대한 새로운 객체를 계속해서 얻습니다.

나는 당신이 여기서 무엇을 의미하는지 잘 모르겠습니다. 클래스 로더는 각 클래스마다 한 번씩 (클래스가로드 될 때) 총 두 번만 관련됩니다.


떨림은 다음과 같습니다.

Javaland에서는 클래스를 만드는 것이 약간 무서워 보입니다. 나는 그것이 중요한 개념처럼 느껴 져야한다고 생각한다. 일반적으로 자체 파일에 속합니다. 상당한 상용구가 필요합니다. 디자인에주의를 기울여야합니다.

다른 언어 (예 : 스칼라 또는 C ++)와 비교 : 두 비트의 데이터가 함께 속하는 작은 홀더 (클래스, 구조체 등)를 만드는 드라마는 전혀 없습니다. 유연한 액세스 규칙은 상용구를 줄이고 관련 코드를 물리적으로 가깝게 유지할 수 있도록 도와줍니다. 이것은 두 클래스 사이의 '마인드 거리'를 줄입니다.

내부 클래스를 비공개로 유지하여 직접 액세스에 대한 디자인 문제를 해결할 수 있습니다.

(... '왜 정적이 아닌 공개 내부 클래스인가?'라는 질문을한다면 매우 좋은 질문입니다.

코드 가독성을 높이고 DRY 위반 및 상용구를 피할 수 있으면 언제든지 사용할 수 있습니다. 내 경험에서 자주 발생하지는 않지만 준비가되어 있기 때문에 준비된 예가 없습니다.


정적 내부 클래스는 여전히 좋은 기본 접근 방식입니다. 비 정적은 내부 클래스가 외부를 참조하는 추가 숨겨진 필드를 생성합니다.


0

팩토리 패턴을 만드는 데 사용할 수 있습니다. 팩토리 패턴은 작성된 오브젝트가 작동하기 위해 추가 생성자 매개 변수가 필요할 때 종종 사용되지만 매번 제공하는 것은 지루합니다.

치다:

public class QueryFactory {
    @Inject private Database database;

    public Query create(String sql) {
        return new Query(database, sql);
    }
}

public class Query {
    private final Database database;
    private final String sql;

    public Query(Database database, String sql) {
        this.database = database;
        this.sql = sql;
    } 

    public List performQuery() {
        return database.query(sql);
    }
}

로 사용 :

Query q = queryFactory.create("SELECT * FROM employees");

q.performQuery();

정적이 아닌 내부 클래스를 사용하여 동일한 결과를 얻을 수 있습니다.

public class QueryFactory {
    @Inject private Database database;

    public class Query {
        private final String sql;

        public Query(String sql) {
            this.sql = sql;
        }

        public List performQuery() {
            return database.query(sql);
        }
    }
}

로 사용:

Query q = queryFactory.new Query("SELECT * FROM employees");

q.performQuery();

공장처럼, 특별히이라는 방법을 혜택을 얻을 create, createFromSQL또는 createFromTemplate. 여러 내부 클래스 형태로도 여기에서 수행 할 수 있습니다.

public class QueryFactory {

    public class SQL { ... }
    public class Native { ... }
    public class Builder { ... }

}

팩토리에서 생성 된 클래스로의 매개 변수 전달이 덜 필요하지만 가독성이 약간 저하 될 수 있습니다.

비교:

Queries.Native q = queries.new Native("select * from employees");

vs

Query q = queryFactory.createNative("select * from employees");
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.