클래스 초기화를 고려할 때, 우리는 서브 클래스 초기화를 수행 할 때 부모 클래스가 초기화되지 않으면 서브 클래스를 먼저 초기화해야한다는 것을 알고 있습니다. 그러나 일이 단일 문장만큼 간단하지 않습니다.
먼저 Java의 초기화 트리거 조건을 살펴 보겠습니다.
(1) 새로운 사용을 사용하여 개체를 인스턴스화하고 정적 데이터 및 메소드에 액세스 할 때, 즉 지침이 발생할 때 : 신규, getstatic/putstatic 및 invokestatic;
(2) 반사를 사용하여 클래스를 호출 할 때;
(3) 클래스를 초기화 할 때, 부모 클래스가 초기화되지 않은 경우, 부모 클래스의 초기화가 먼저 트리거됩니다.
(4) 입구 메인 방법이 실행되는 클래스;
(5) 초기화가 트리거되지 않은 경우 메소드 핸들이 JDK1.7의 동적 언어 지원에있는 클래스;
컴파일 후 <clinit> 방법이 생성 되고이 방법에서 클래스 초기화가 수행됩니다. 이 방법은 실행되며 JVM은이를 보장하고 동기화 제어를 수행합니다.
그중에서도 방법 호출의 관점에서 조건 (3)은 처음에 부모 클래스 <clinit>를 재귀 적으로 호출하는 서브 클래스 <clinit>이며, 이는 먼저 서브 클래스 생성자에서 부모 클래스 생성자를 호출해야한다는 사실과 유사합니다.
그러나 "트리거"는 초기화를 완료하지 못한다는 점에 유의해야합니다. 즉, "위험"이있는 부모 클래스의 초기화 전에 서브 클래스의 초기화가 미리 끝날 수 있음을 의미합니다.
1. 수업 초기화의 예 :
이 예에서는 주변 클래스를 사용하여 상속 관계가있는 2 개의 정적 멤버 클래스를 포함합니다. 주변 클래스와 정적 멤버 클래스의 초기화는 인과 관계가 없기 때문에 이와 같이 보여주는 것이 안전하고 편리합니다.
학부모 클래스 A 및 아동 클래스 B에는 각각 주요 기능이 포함되어 있습니다. 상기 트리거 조건 (4)으로부터,이 두 가지 주요 함수를 각각 호출하여 각각 다른 클래스 초기화 경로가 트리거임을 알 수있다.
이 예제의 문제는 부모 클래스에 아동 클래스의 정적 참조를 포함하고 정의에서 초기화한다는 것입니다.
public class wrapperclass {private static class a {static {system.out.println ( "클래스 A 초기화 시작 ..."); } // 부모 클래스에는 자식 클래스의 정적 참조가 포함되어 있습니다. 보호 된 정적 int aint = 9; static {system.out.println ( "클래스 A 초기화 종료 ..."); } public static void main (String [] args) {}} private static class b는 a {static {system.out.println ( "클래스 B 초기화 시작 ...")을 확장합니다. } // 서브 클래스의 도메인은 상위 클래스의 도메인 개인 정적 int bint = 9 + a.aint의 도메인에 따라 다릅니다. public b () {// 생성자의 정적 도메인은 클래스 System.out.println에 의존합니다 ( "클래스 B" + "값의 값" + bint); } static {system.out.println ( "클래스 B 초기화 종료 ..." + "AINT의 값 :" + bint); } public static void main (String [] args) {}}} 시나리오 1 : 항목이 클래스 B의 주요 함수 인 경우 출력 결과 :
/** * 클래스 A 초기화가 시작됩니다 ... * 클래스 B의 생성자는 bint 0 * 클래스 A 초기화 종료 ... * 클래스 B 초기화가 시작됩니다 ... * 클래스 B 초기화 종료 ... Aint 값 : 18 * */
분석 : 주요 함수의 호출은 클래스 B의 초기화를 유발하고 클래스 B의 <clinit> 방법에 들어가는 것을 알 수 있습니다. 클래스 A는 부모 클래스로서 먼저 초기화를 시작하고 a의 <clinit> 방법을 입력하고 새로운 b ()가 있습니다. 현재 B는 클래스 B의 <clinit>에 이미 B가 인스턴스화 될 것입니다. 메인 스레드는 잠금을 얻었고 클래스 B의 <clinit>를 실행하기 시작했습니다. 처음에는 JVM이 클래스의 초기화 방법이 한 번만 실행되도록 보장 할 것이라고 말했습니다. 새 명령을받은 후 JVM은 클래스 B의 <Clinit> 방법을 다시 입력하지 않지만 직접 인스턴스화됩니다. 그러나 현재 클래스 B는 클래스 초기화를 완료하지 않았으므로 BINT의 값은 0임을 알 수 있습니다 (이 0은 클래스 로딩의 준비 단계 동안 메소드 영역의 메모리를 할당 한 후 수행 된 제로 초기화);
따라서, 부모 클래스는 자식 유형의 정적 도메인을 포함하고 할당 조치를 수행한다는 결론을 내릴 수 있으며, 이는 클래스 초기화가 완료되기 전에 서브 클래스 인스턴스화를 수행 할 수 있습니다.
시나리오 2 : 항목이 클래스 A의 주요 함수 인 경우 출력 결과 :
/** * 클래스 A 초기화가 시작됩니다 ... * 클래스 B 초기화가 시작됩니다 ... * 클래스 B 초기화 종료 ... AINT의 값 : 9 * 클래스 B의 생성자는 bint 9 * 클래스 A 초기화가 끝납니다 ... */
분석 : 시나리오 1을 분석 한 후 클래스 B의 초기화에 의해 클래스 A의 초기화를 트리거하면 클래스 B의 초기화가 완료되기 전에 클래스 A에서 클래스 변수 B의 인스턴스화가 수행 될 것임을 알고 있습니다. 따라서 클래스 A가 먼저 초기화되면 클래스 변수 인스턴스화가있을 때 클래스 B를 먼저 트리거하여 인스턴스화 전에 초기화를 수행 할 수 있습니까? 대답은 그렇습니다. 그러나 여전히 문제가 있습니다.
출력에 따르면, 클래스 B의 초기화가 클래스 A의 초기화가 완료되기 전에 수행되는 것을 알 수 있습니다. 이는 클래스 B가 초기화 된 후에 만 클래스 변수 AINT와 같은 변수가 초기화되므로 클래스 B에서 도메인 BINT에 의해 얻은 AINT의 값은 "18"가 아니라 "0"입니다.
결론 : 요약하면, 부모 클래스에 서브 클래스 유형의 클래스 변수를 포함하고 정의 할 때 인스턴스화하는 것은 매우 위험하다고 결론을 내릴 수 있습니다. 특정 상황은 예처럼 간단하지 않을 수 있습니다. 정의에서 값을 할당하기 위해 메소드를 호출하는 것도 위험합니다. 서브 클래스 유형의 정적 도메인을 포함 시키려면 정적 메소드를 통해 값을 할당해야합니다. JVM은 정적 메소드가 불리기 전에 모든 초기화 조치가 완료되도록 할 수 있기 때문에 (물론,이 보증은 정적 B B = 새 B (); 해당 초기화 동작을 포함해서는 안된다는 것입니다.
2. 즉각적인 예 :
먼저, 객체 생성 과정을 알아야합니다.
(1) 새 명령을 마주 할 때 클래스가로드, 검증, 준비, 구문 분석 및 초기화를 완료했는지 확인하십시오 (구문 분석 프로세스는 기호 참조를 기호 참조와 같은 기호 참조로 구문 분석하는 것이 기호 참조이며, 초기화가 완료된 후에이 기호 참조를 사용 할 때 수행 할 수있는 것과 같은 기호 참조와 같은 기호 참조와 같은 기호 참조로서의 기준을 직접 참조로 구문 분석하는 것입니다.
(2) 메모리를 할당하고, 무료 목록 또는 포인터 충돌 방법을 사용하고, 새로 할당 된 메모리를 "0". 따라서이 링크에서 모든 인스턴스 변수는 기본적으로 기본적으로 0으로 초기화됩니다 (NULL으로 참조).
(3) 부모 클래스의 <init> 메소드 (생성자)에 대한 호출을 확인하는 것을 포함하여 <init> 메소드를 실행하고 인스턴스 변수에 의해 정의 된 할당 조치가 인스턴트 인자에서 실행되고 마지막으로 생성자에서 동작을 호출합니다.
이 예제는 더 잘 알려져있을 수 있습니다. 즉, "생성자, 복제 방법 및 readObject 방법에서 우선적 인 방법을 호출하지 마십시오"를 위반합니다. 그 이유는 Java의 다형성, 즉 동적 결합 때문입니다.
부모 클래스 A의 생성자는 보호 된 방법을 포함하고 클래스 B는 서브 클래스입니다.
public class orudinstantiation {private static class a {public a () {dosomething (); } protected void dosomething () {System.out.println ( "A 's DoSomething"); }} private static class b는 a {private int bint = 9; @override protected void dosomething () {system.out.println ( "B의 dosomething, bint :" + bint); }} public static void main (String [] args) {b b = new b (); }}출력 결과 :
/** * B의 dosomething, bint : 0 */
분석 : 우선, 디스플레이가 없으면 Java 컴파일러가 기본 생성자를 생성하고 처음에 부모 클래스의 생성자를 호출한다는 것을 알아야합니다. 따라서 클래스 B의 생성자는 처음에 클래스 A의 생성자를 첫 번째로 호출합니다.
보호 된 방법은 클래스 A에서 호출됩니다. 출력 결과에서 서브 클래스의 메소드 구현이 실제로 호출되고 서브 클래스의 인스턴스화가 아직 시작되지 않았으므로 Bint는 "예상"이 아니라 0;
이는 동적 바인딩 때문이며, Dosomething은 보호 된 방법이므로 InvokeVirtual 지시문을 통해 호출되며,이 지침은 객체 인스턴스 유형을 기반으로 해당 메소드 구현을 찾습니다 (여기서 B의 인스턴스 객체가 있으며 해당 메소드는 클래스 B의 메소드 구현입니다).
결론 : 앞에서 언급했듯이, "생성자, 복제 방법 및 readObject 메소드에서 우정 가능한 메소드를 호출하지 마십시오".
위의 것은 Java 클래스 초기화와 인스턴스화의 두 가지 "지뢰"입니다. 모든 사람의 학습에 도움이되기를 바랍니다.