Java 클래스 초기화 및 실례화의 "레이어"2개

클래스 초기화를 고려할 때, 우리는 모두 하위 클래스 초기화를 진행할 때, 부모 클래스가 초기화되지 않으면 먼저 하위 클래스를 초기화해야 한다는 것을 안다.그러나 일은 한마디도 이렇게 간단하지 않다.
먼저 Java에서 트리거를 초기화하는 조건을 살펴보겠습니다.
(1) new 실례화 대상을 사용하여 정적 데이터와 방법에 접근할 때, 즉 지령: new, getstatic/putstatic과 invokestatic을 만났을 때;
(2) 반사로 클래스를 호출할 때;
(3) 하나의 클래스를 초기화할 때 부류가 초기화되지 않으면 부류의 초기화를 촉발한다.
(4) 입구main 방법이 있는 클래스를 실행한다.
(5) JDK1.7 동적 언어 지원에서 메소드 핸들이 있는 클래스입니다. 초기화가 시작되지 않으면 초기화됩니다.
컴파일한 후에 방법을 생성하면 클래스의 초기화는 이 방법에서 진행되고 이 방법은 실행만 하며 JVM이 이를 보증하고 동기화 제어를 한다.
그 중에서 조건(3), 방법 호출의 측면에서 볼 때 하위 클래스의 은 시작할 때 상위 클래스의 를 호출합니다. 이것은 우리가 하위 클래스 구조기에서 먼저 상위 클래스의 구조기를 호출해야 하는 것과 유사합니다.
그러나 주의해야 할 것은'촉발'은 초기화를 완성하는 것이 아니다. 이것은 하위 클래스의 초기화가 상위 클래스의 초기화 종료보다 앞당겨질 수 있다는 것을 의미한다. 이것이 바로'위험'의 소재이다.
1. 클래스 초기화의 예:
이 예에서 나는 하나의 외곽류를 사용하여 2개의 계승 관계가 있는 정적 구성원류를 포함한다. 외곽류의 초기화와 정적 구성원류는 인과 관계가 없기 때문에 이렇게 보여주는 것은 안전하고 편리하다.
부류 A와 자류 B는 각각main 함수를 포함하고 위의 트리거 조건(4)에서 알 수 있듯이 이 두 개의main 함수를 호출하여 서로 다른 클래스 초기화 경로를 트리거한다.
이 예는 상위 클래스가 하위 클래스의 static 인용을 포함하고 정의된 곳에서 초기화하는 문제입니다.

public class WrapperClass { 
  private static class A { 
    static { 
      System.out.println(" A ..."); 
    } 
    // static  
    private static B b = new B(); 
    protected static int aInt = 9; 
 
    static { 
      System.out.println(" A ..."); 
    } 
 
    public static void main(String[] args) { 
 
    } 
  } 
 
  private static class B extends A { 
    static { 
      System.out.println(" B ..."); 
    } 
    //  
    private static int bInt = 9 + A.aInt; 
 
    public B() { 
      // static  
      System.out.println(" B  " + "bInt " + bInt); 
    } 
 
    static { 
      System.out.println(" B ... " + "aInt :" + bInt); 
    } 
 
    public static void main(String[] args) { 
 
    } 
  } 
} 
시나리오 1: 클래스 B의 main 함수를 입력할 때 결과를 출력합니다.

/** 
   *  A ... 
   *  B  bInt 0 
   *  A ... 
   *  B ... 
   *  B ... aInt :18 
   */ 
분석:main 함수의 호출은 클래스 B의 초기화를 촉발하고 클래스 B에 들어가는 방법을 볼 수 있다. 클래스 A는 그 부류로서 먼저 A의 방법을 초기화하기 시작했는데 그 중에서 한 문장은 new B()이다.이때 B의 실례화가 진행될 것입니다. 이것은 클래스 B의 에 있습니다. main 라인은 이미 클래스 B의 를 자물쇠로 잠갔습니다. 우리는 처음에 JVM이 하나의 클래스의 초기화 방법을 한 번만 실행할 것을 보장한다고 말했습니다. JVM은 new 명령을 받은 후에 클래스 B의 방법에 들어가지 않고 직접 실례화를 합니다. 그러나 이때 클래스 B는 클래스 초기화를 완성하지 않았습니다.그래서 bInt의 값이 0인 것을 볼 수 있다. (이 0은 클래스 불러오는 단계의 분배 방법 구역에 메모리를 설치한 후 0을 초기화하는 것이다.)
따라서 부모 클래스에 하위 클래스 유형을 포함하는static역을 포함하고 값을 부여하는 동작을 하면 하위 클래스의 실례화는 클래스 초기화가 완료되기 전에 진행될 수 있음을 알 수 있다.
시나리오 2: 클래스 A의 main 함수를 입력할 때 결과를 출력합니다.

/** 
   *  A ... 
   *  B ... 
   *  B ... aInt :9 
   *  B  bInt 9 
   *  A ... 
   */ 
분석: 상황 1의 분석을 통해 우리는 클래스 B의 초기화 트리거 클래스 A의 초기화로 인해 클래스 A에서 클래스 변수 b의 실례화는 클래스 B의 초기화가 완성되기 전에 진행될 수 있음을 알 수 있다. 만약에 클래스 A를 먼저 초기화하면 클래스 변수가 실례화될 때 클래스 B의 초기화를 먼저 촉발하여 실례화 전에 초기화할 수 있지 않을까?답은 긍정적이지만, 이것은 여전히 문제가 있다.
출력에 의하면 클래스 B의 초기화는 클래스 A의 초기화가 완성되기 전에 진행되었다. 이로 인해 클래스 변수 aInt의 변수는 클래스 B의 초기화가 완성된 후에야 초기화되었다. 그래서 클래스 B의 도메인 bInt가 얻은 aInt의 값은'0'이지 우리가 예상한'18'이 아니다.
결론: 종합적으로 보면 부류에 자류 유형의 클래스 변수를 포함하고 정의를 실례화하는 것은 매우 위험한 행위이다. 구체적인 상황은 예처럼 직설적이지 않을 수 있다. 호출 방법은 정의에 값을 부여하는 것처럼 위험을 내포하고 있다. 부류 유형의 static역을 포함하더라도 static 방법을 통해 값을 부여해야 한다.JVM은 static 방법이 호출되기 전에 모든 초기화 동작을 완성할 수 있기 때문이다. (물론 이런 보증도 static B b = new B () 를 포함해서는 안 된다.이러한 초기화 행위);
2. 실례화된 예:
먼저 객체가 작성되는 과정을 알아야 합니다.
(1) new 명령을 만나면 클래스가 불러오기, 검증, 준비, 해석, 초기화를 완성했는지 확인한다. (해석 과정은 기호 인용을 직접 인용으로 해석하는 것이다. 예를 들어 방법명은 하나의 기호 인용이다. 초기화가 끝난 후에 이 기호 인용을 사용할 때 할 수 있다. 바로 동적 귀속을 지원하기 위해서이다.) 이 과정을 먼저 진행하지 못했다.
(2) 메모리를 분배하고 빈 목록이나 바늘이 부딪치는 방법을 사용하며 새로 분배된 메모리를'0'으로 설정하기 때문에 모든 실례 변수는 이 부분에서 기본적으로 0(null로 인용)으로 초기화하는 과정을 거친다.
(3) 방법을 실행합니다. 부모 클래스를 호출하는 방법(구조기), 실례 변수가 정의한 값 부여 동작, 실례화기 순서 실행, 마지막으로 구조기의 동작을 호출합니다.
이 예는'구조기,clone 방법,read Object 방법에서 덮어쓸 수 있는 방법을 사용하지 마세요'라는 것을 위반한 것으로 더 잘 알려질 수 있습니다.그 이유는 바로 자바의 다태적, 즉 동적 귀속에 있다.
부류 A의 구조기에는protected 방법이 포함되어 있으며, 부류 B는 그 자류이다.

public class WrongInstantiation { 
  private static class A { 
    public A() { 
      doSomething(); 
    } 
 
    protected void doSomething() { 
      System.out.println("A's doSomething"); 
    } 
  } 
 
  private static class B extends A { 
    private int bInt = 9; 
 
    @Override 
    protected void doSomething() { 
      System.out.println("B's doSomething, bInt: " + bInt); 
    } 
  } 
 
  public static void main(String[] args) { 
    B b = new B(); 
  } 
} 
출력 결과:

/** 
   * B's doSomething, bInt: 0 
   */ 
분석: 우선 구조기를 제공하지 않을 때 자바 컴파일러가 기본 구조기를 생성하고 시작 부분에서 부류 구조기를 호출하기 때문에 클래스 B의 구조기는 클래스 A의 구조기를 먼저 호출한다는 것을 알아야 한다.
클래스 A에서protected 방법인doSomething을 호출했습니다. 출력 결과에서 우리는 실제적으로 호출된 것은 하위 클래스의 방법이 실현된 것을 보았습니다. 이때 하위 클래스의 실례화가 시작되지 않았기 때문에 bInt는'예상'처럼 9가 아니라 0입니다.
이것은 동적 연결이기 때문에doSomething은protected 방법이기 때문에invokevirtual 명령을 통해 호출된 것이다. 이 지령은 대상의 실례 유형에 따라 대응하는 방법을 찾아서 실현한다. (여기가 바로 B의 실례 대상이고 대응하는 방법은 클래스 B의 방법으로 실현된다.) 따라서 이 결과가 있다.
결론: 앞에서 말한 바와 같이'구조기,clone 방법,readObject 방법에서 덮어쓸 수 있는 방법을 사용하지 마세요'.
다음은 여러분을 위해 소개된 자바 클래스의 초기화와 실례화 중 2개의'지뢰밭'입니다. 여러분의 학습에 도움이 되기를 바랍니다.

좋은 웹페이지 즐겨찾기