Java Language
중첩 클래스 및 내부 클래스
수색…
소개
Java를 사용하여 개발자는 다른 클래스에서 클래스를 정의 할 수 있습니다. 이러한 클래스를 중첩 클래스 라고합니다. 중첩 된 클래스는 정적이 아닌 것으로 선언 된 경우 내부 클래스라고 부릅니다. 그렇지 않은 경우 정적으로 중첩 된 클래스라고합니다. 이 페이지는 Java Nested 및 Inner Class를 사용하는 방법에 대한 예제를 문서화하고 세부 사항을 제공합니다.
통사론
- 공용 클래스 OuterClass {public class InnerClass {}} // 내부 클래스는 private 일 수도 있습니다.
- 공용 클래스 OuterClass {public static class StaticNestedClass {}} // 정적 중첩 클래스는 private 일 수도 있습니다.
- public void method () {private class LocalClass {}} // 로컬 클래스는 항상 비공개입니다.
- SomeClass anonymousClassInstance = 새로운 SomeClass () {}; // 익명 내부 클래스의 이름을 지정할 수 없으므로 액세스가 확실하지 않습니다. 'SomeClass ()'가 abstract 인 경우, 본문은 모든 추상 메소드를 구현해야합니다.
- SomeInterface anonymousClassInstance = 새로운 SomeInterface () {}; // 본문은 모든 인터페이스 메소드를 구현해야합니다.
비고
용어 및 분류
Java 언어 스펙 (JLS)은 다음과 같이 다양한 종류의 Java 클래스를 분류합니다.
최상위 클래스 는 중첩 클래스가 아닌 클래스입니다.
중첩 된 클래스 는 다른 클래스 또는 인터페이스의 본문 내에서 선언이 발생하는 클래스입니다.
내부 클래스 는 명시 적 또는 암시 적으로 정적으로 선언되지 않은 중첩 클래스입니다.
내부 클래스는 비 정적 멤버 클래스 , 로컬 클래스 또는 익명 클래스 일 수 있습니다. 인터페이스의 멤버 클래스는 암시 적으로 정적이므로 내부 클래스로 간주되지 않습니다.
실제로 프로그래머는 내부 클래스를 "외부 클래스"로 포함하는 최상위 클래스를 참조합니다. 또한 "중첩 된 클래스"를 사용하여 정적 중첩 클래스에만 (명시 적으로 또는 암시 적으로) 참조하는 경향이 있습니다.
익명의 내부 클래스와 람다 사이에는 밀접한 관계가 있지만 람다는 클래스입니다.
의미 차이
최상위 클래스는 "기본 사례"입니다. 액세스 수정 자 의미에 기반한 일반 공개 규칙에 따라 프로그램의 다른 부분에서 볼 수 있습니다. 비 추상화의 경우, 액세스 수식 자에 근거 해 관련하는 생성자가 표시되는 모든 코드에 의해 인스턴스화 할 수 있습니다.
정적 중첩 클래스는 두 가지 예외를 제외하고 최상위 클래스와 동일한 액세스 및 인스턴스화 규칙을 따릅니다.
- 중첩 된 클래스는
private
로 선언 될 수 있습니다. 따라서 중첩 된 클래스는 해당 최상위 수준 클래스 외부에서 액세스 할 수 없습니다. - 중첩 클래스는 상위 레벨 클래스와 테스트 된 모든 클래스의
private
멤버에 액세스 할 수 있습니다.
정적 인 중첩 된 클래스는 타이트한 추상화 경계 내에서 여러 "엔터티 유형"을 나타내야 할 때 유용합니다. 예를 들어 중첩 된 클래스가 "구현 세부 사항"을 숨기는 데 사용되는 경우.
- 중첩 된 클래스는
내부 클래스는 둘러싸는 범위에 선언 된 비 정적 변수에 액세스하는 기능을 추가합니다.
- 비 정적 멤버 클래스는 인스턴스 변수를 참조 할 수 있습니다.
- 메서드 내에서 선언 된 로컬 클래스는 메서드의 로컬 변수를 참조 할 수도 있습니다 (
final
. (Java 8 이상에서는 효과적 일 수 있습니다.) - 익명 내부 클래스는 클래스 또는 메서드 내에서 선언 될 수 있으며 동일한 규칙에 따라 변수에 액세스 할 수 있습니다.
내부 클래스 인스턴스가 둘러싼 클래스 인스턴스의 변수를 참조 할 수 있다는 사실은 인스턴스화에 영향을 미칩니다. 특히, 내부 클래스의 인스턴스가 생성 될 때 암시 적으로 또는 명시 적으로 엔 클로징 인스턴스가 제공되어야합니다.
중첩 클래스를 사용하는 간단한 스택
public class IntStack {
private IntStackNode head;
// IntStackNode is the inner class of the class IntStack
// Each instance of this inner class functions as one link in the
// Overall stack that it helps to represent
private static class IntStackNode {
private int val;
private IntStackNode next;
private IntStackNode(int v, IntStackNode n) {
val = v;
next = n;
}
}
public IntStack push(int v) {
head = new IntStackNode(v, head);
return this;
}
public int pop() {
int x = head.val;
head = head.next;
return x;
}
}
그리고 그것의 사용은 (특히) 중첩 된 클래스의 존재를 전혀 인정하지 않습니다.
public class Main {
public static void main(String[] args) {
IntStack s = new IntStack();
s.push(4).push(3).push(2).push(1).push(0);
//prints: 0, 1, 2, 3, 4,
for(int i = 0; i < 5; i++) {
System.out.print(s.pop() + ", ");
}
}
}
정적 대 정적이 아닌 중첩 클래스
중첩 클래스를 만들 때 중첩 클래스를 정적으로 유지하도록 선택할 수 있습니다.
public class OuterClass1 {
private static class StaticNestedClass {
}
}
또는 비 정적 :
public class OuterClass2 {
private class NestedClass {
}
}
핵심에는 정적 중첩 클래스에는 외부 클래스 의 주변 인스턴스 가 없지만 정적이 아닌 중첩 클래스에는 있습니다. 이는 중첩 클래스를 인스턴스화 할 수있는 위치와 시간 및 해당 중첩 클래스의 인스턴스에 액세스 할 수있는 경우 모두에 영향을줍니다. 위의 예에 추가 :
public class OuterClass1 {
private int aField;
public void aMethod(){}
private static class StaticNestedClass {
private int innerField;
private StaticNestedClass() {
innerField = aField; //Illegal, can't access aField from static context
aMethod(); //Illegal, can't call aMethod from static context
}
private StaticNestedClass(OuterClass1 instance) {
innerField = instance.aField; //Legal
}
}
public static void aStaticMethod() {
StaticNestedClass s = new StaticNestedClass(); //Legal, able to construct in static context
//Do stuff involving s...
}
}
public class OuterClass2 {
private int aField;
public void aMethod() {}
private class NestedClass {
private int innerField;
private NestedClass() {
innerField = aField; //Legal
aMethod(); //Legal
}
}
public void aNonStaticMethod() {
NestedClass s = new NestedClass(); //Legal
}
public static void aStaticMethod() {
NestedClass s = new NestedClass(); //Illegal. Can't construct without surrounding OuterClass2 instance.
//As this is a static context, there is no surrounding OuterClass2 instance
}
}
따라서 정적 대 정적이 아닌 결정은 주로 중첩 클래스를 구성 할 수있는시기와 위치에 영향을 미치지 만 외부 클래스의 필드와 메서드에 직접 액세스 할 수 있어야하는지 여부에 달려 있습니다.
일반적으로 외부 클래스의 필드와 메서드에 액세스해야하는 경우가 아니라면 중첩 클래스를 정적으로 만듭니다. 공개가 필요하지 않으면 필드를 비공개로 설정하는 것과 마찬가지로 (외부 인스턴스에 대한 액세스를 허용하지 않음으로써) 중첩 클래스에서 사용할 수있는 가시성을 감소시켜 오류 가능성을 줄입니다.
내부 클래스에 대한 액세스 한정자
Java의 액세스 수정 자에 대한 자세한 설명은 여기에서 찾을 수 있습니다 . 그러나 Inner 클래스와 어떻게 상호 작용합니까?
public
은 평소처럼 유형에 액세스 할 수있는 범위에 제한없이 액세스 할 수 있습니다.
public class OuterClass {
public class InnerClass {
public int x = 5;
}
public InnerClass createInner() {
return new InnerClass();
}
}
public class SomeOtherClass {
public static void main(String[] args) {
int x = new OuterClass().createInner().x; //Direct field access is legal
}
}
protected
와 default 수정 자 (모두 없음)는 예상대로 작동하지만 중첩되지 않은 클래스에서도 마찬가지입니다.
흥미롭게도 private
이고, 그것이 속한 수업에 국한하지 않는다. 오히려 .java 파일 인 컴파일 단위로 제한됩니다. 즉, Outer 클래스는 Inner 클래스 필드 및 메서드에 대한 모든 액세스 권한을 가지며, private
클래스로 표시된 경우에도 마찬가지입니다.
public class OuterClass {
public class InnerClass {
private int x;
private void anInnerMethod() {}
}
public InnerClass aMethod() {
InnerClass a = new InnerClass();
a.x = 5; //Legal
a.anInnerMethod(); //Legal
return a;
}
}
내부 클래스 자체는 public
아닌 다른 클래스의 가시성을 가질 수 있습니다. private
또는 다른 제한된 액세스 수정 자로 표시하면 다른 (외부) 클래스가 유형을 가져 와서 할당 할 수 없습니다. 하지만 여전히 해당 유형의 객체에 대한 참조를 얻을 수 있습니다.
public class OuterClass {
private class InnerClass{}
public InnerClass makeInnerClass() {
return new InnerClass();
}
}
public class AnotherClass {
public static void main(String[] args) {
OuterClass o = new OuterClass();
InnerClass x = o.makeInnerClass(); //Illegal, can't find type
OuterClass.InnerClass x = o.makeInnerClass(); //Illegal, InnerClass has visibility private
Object x = o.makeInnerClass(); //Legal
}
}
익명 내부 클래스
익명의 내부 클래스는 단일 문으로 선언되고 인스턴스화되는 내부 클래스의 형태입니다. 결과적으로 프로그램의 다른 곳에서 사용할 수있는 클래스의 이름은 없습니다. 즉 그것은 익명입니다.
익명 클래스는 일반적으로 경량 클래스를 만들어 매개 변수로 전달할 수 있어야하는 상황에서 사용됩니다. 이것은 일반적으로 인터페이스로 수행됩니다. 예 :
public static Comparator<String> CASE_INSENSITIVE =
new Comparator<String>() {
@Override
public int compare(String string1, String string2) {
return string1.toUpperCase().compareTo(string2.toUpperCase());
}
};
이 익명 클래스는 대소 문자 차이를 무시하고 두 문자열을 비교하는 Comparator<String>
객체 ( CASE_INSENSITIVE
)를 정의합니다.
익명 클래스를 사용하여 자주 구현되고 인스턴스화되는 다른 인터페이스는 Runnable
및 Callable
입니다. 예 :
// An anonymous Runnable class is used to provide an instance that the Thread
// will run when started.
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world");
}
});
t.start(); // Prints "Hello world"
익명 내부 클래스는 클래스를 기반으로 할 수도 있습니다. 이 경우 익명 클래스는 암시 적 extends
기존 클래스를 extends
합니다. 확장되는 클래스가 abstract 클래스 인 경우 익명 클래스는 모든 추상 메서드를 구현해야합니다. 또한 비 추상적 인 메소드를 대체 할 수 있습니다.
생성자
익명 클래스는 명시 적 생성자를 가질 수 없습니다. 대신 확장되는 클래스의 생성자에 매개 변수를 전달하기 위해 super(...)
를 사용하는 암시 적 생성자가 정의됩니다. 예 :
SomeClass anon = new SomeClass(1, "happiness") {
@Override
public int someMethod(int arg) {
// do something
}
};
SomeClass
의 익명 하위 클래스에 대한 암시 적 생성자는 호출 서명 SomeClass(int, String)
과 일치하는 SomeClass
생성자를 호출합니다. 사용할 수있는 생성자가 없으면 컴파일 오류가 발생합니다. 일치하는 생성자에 의해 throw 된 예외는 암시 적 생성자에 의해 throw됩니다.
물론 이것은 인터페이스를 확장 할 때 작동하지 않습니다. 인터페이스에서 익명 클래스를 만들면 클래스 수퍼 클래스는 no args 생성자 만있는 java.lang.Object
입니다.
메서드 로컬 내부 클래스
메소드 로컬 내부 클래스 라는 메소드 내에 작성된 클래스 . 이 경우 내부 클래스의 범위는 메서드 내에서 제한됩니다.
메소드 로컬의 내부 클래스는 내부 클래스가 정의 된 메소드 내에서만 인스턴스화 될 수 있습니다.
메소드 로컬 내부 클래스 사용의 예 :
public class OuterClass {
private void outerMethod() {
final int outerInt = 1;
// Method Local Inner Class
class MethodLocalInnerClass {
private void print() {
System.out.println("Method local inner class " + outerInt);
}
}
// Accessing the inner class
MethodLocalInnerClass inner = new MethodLocalInnerClass();
inner.print();
}
public static void main(String args[]) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
실행하면 결과가 표시됩니다. Method local inner class 1
입니다.
비 정적 인 내부 클래스에서 외부 클래스에 액세스하기
외부 클래스에 대한 참조는 클래스 명, 사용 this
public class OuterClass {
public class InnerClass {
public void method() {
System.out.println("I can access my enclosing class: " + OuterClass.this);
}
}
}
외부 클래스의 필드와 메서드에 직접 액세스 할 수 있습니다.
public class OuterClass {
private int counter;
public class InnerClass {
public void method() {
System.out.println("I can access " + counter);
}
}
}
그러나 이름 충돌의 경우 외부 클래스 참조를 사용할 수 있습니다.
public class OuterClass {
private int counter;
public class InnerClass {
private int counter;
public void method() {
System.out.println("My counter: " + counter);
System.out.println("Outer counter: " + OuterClass.this.counter);
// updating my counter
counter = OuterClass.this.counter;
}
}
}
외부에서 비 정적 내부 클래스의 인스턴스 생성
이 클래스에서도 외부 클래스에서 볼 수있는 내부 클래스를 만들 수 있습니다.
내부 클래스는 외부 클래스에 의존하며 인스턴스의 참조를 필요로합니다. 내부 클래스의 인스턴스를 만들려면 new
연산자는 외부 클래스의 인스턴스에서만 호출하면됩니다.
class OuterClass {
class InnerClass {
}
}
class OutsideClass {
OuterClass outer = new OuterClass();
OuterClass.InnerClass createInner() {
return outer.new InnerClass();
}
}
사용법을 outer.new
로 outer.new
.