> Java > java지도 시간 > 본문

Java의 static 키워드를 한번에 이해하게 해주세요

醉折花枝作酒筹
풀어 주다: 2021-08-04 17:49:46
앞으로
2131명이 탐색했습니다.

많은 학생들이 이런 질문을 접한 적이 있을 것입니다. 정보를 찾아본 후 잊어버리고 다시 접하게 된다면 여전히 정확한 답변을 할 수 없을 것입니다. 다음으로 4단계를 통해 이 코드의 실행 순서를 분해하고 규칙을 요약하도록 안내하겠습니다.

코드 실행 순서를 검토하기 위한 시작 질문:

public class Parent {
    static {
        System.out.println("Parent static initial block");
    }

    {
        System.out.println("Parent initial block");
    }

    public Parent() {
        System.out.println("Parent constructor block");

    }
}

public class Child extends Parent {
    static {
        System.out.println("Child static initial block");
    }

    {
        System.out.println("Child initial block");
    }
    
    private Hobby hobby = new Hobby();

    public Child() {
        System.out.println("Child constructor block");
    }
}

public class Hobby {
    static{
        System.out.println("Hobby static initial block");
    }

    public Hobby() {
        System.out.println("hobby constructor block");
    }
}
로그인 후 복사

new Child()가 실행될 때 위 코드는 무엇을 출력합니까?

많은 학생들이 이런 문제에 직면했을 것입니다. 정보를 확인하고 잊어버린 경우에도 여전히 올바르게 대답하지 못할 수 있습니다. 다음으로, 학급 대표는 이 코드의 실행 순서를 해체하고 규칙을 요약하기 위해 4단계를 안내할 것입니다.

1. 컴파일러는 무엇을 최적화하나요?

다음 두 코드는 컴파일 전과 후의 변경 사항을 비교합니다.

컴파일 전의 Child.java

public class Child extends Parent {
    static {
        System.out.println("Child static initial block");
    }
    {
        System.out.println("Child initial block");
    }
    
    private Hobby hobby = new Hobby();
    
    public Child() {
        System.out.println("Child constructor block");
    }
}
로그인 후 복사

컴파일 후의Child.class

public class Child extends Parent {
    private Hobby hobby;

    public Child() {
        System.out.println("Child initial block");
        this.hobby = new Hobby();
        System.out.println("Child constructor block");
    }

    static {
        System.out.println("Child static initial block");
    }
}
로그인 후 복사

컴파일러가 초기화 블록에 값을 할당하고 인스턴스 필드는 생성자 코드 앞으로 이동되며 관련 코드의 순서는 유지됩니다. 실제로 생성자가 여러 개인 경우 초기화 코드가 복사되어 이동됩니다.

이를 바탕으로 첫 번째 우선순위를 그릴 수 있습니다:

  • 초기화 코드 > 생성자 코드

2.

클래스 로딩 프로세스는 대략 3단계로 나눌 수 있습니다: 로딩->링크->초기화

초기화 단계는 8가지 상황에 의해 트리거될 수 있습니다. Zhou Zhiming》P359 "클래스 초기화를 트리거하는 8가지 상황"):

  • 새 키워드를 사용하여 객체를 인스턴스화하는 경우

  • 유형의 정적 필드를 읽거나 설정하는 경우(상수 " 제외))

  • 유형의 정적 메서드 호출

  • 다음을 사용하여 클래스를 호출하는 경우 Reflection

  • 클래스 초기화 시 상위 클래스가 초기화되지 않은 것으로 확인되면 해당 상위 클래스의 초기화가 먼저 시작됩니다.

  • 가상 머신이 시작되면 메인 클래스(다음을 포함하는 클래스)

  • MethodHandle 인스턴스가 처음 호출되면 MethodHandle이 가리키는 메소드가 초기화됩니다.

  • 기본 메소드(기본값이 수정됨) 인터페이스 메소드)가 인터페이스에 정의되어 있으면 인터페이스의 구현 클래스가 초기화되어야 합니다.

항목 2와 3이 정적 코드에 의해 트리거되기 전에

실제로 초기화 단계는 실행하는 과정입니다. 컴파일러에 의해 자동으로 생성되는 클래스 생성자<clinit> 메서드는 모든 정적 수정 클래스 변수의 할당 작업 및 정적 문 블록(정적{} 블록)을 수집하고 이러한 코드가 나타나는 순서를 유지합니다.

항목 5에 따르면 JVM은 하위 클래스의 <clinit>가 유지되는지 확인합니다. ;메서드가 실행되기 전에 상위 클래스의 <clinit> 메소드가 실행되었습니다. 변수 또는 정적 메서드는 클래스 초기화를 트리거하며 클래스 초기화는 <clinit>, 즉 정적 수정 할당 작업 및 정적{} 블록을 실행하는 것이며 JVM은 상위 클래스 초기화를 보장합니다. 먼저 수행된 다음 하위 클래스 초기화가 수행됩니다.

이것은 두 번째 우선 순위로 이어집니다.

부모 클래스의 정적 코드 > 정적 코드
  • 3.정적 코드는 한 번만 실행됩니다. 정적 코드(정적 메서드 제외)는 한 번만 실행된다는 것을 알고 있습니다.

이 메커니즘이 어떻게 보장되는지 생각해 보셨나요?

답은 다음과 같습니다.

JDK8 이전의 상위 위임 모델은 다음과 같습니다. :

애플리케이션 클래스 로더 → 확장 클래스 로더 → 시작 클래스 로더

일반 개발에서 작성된 클래스는 기본적으로 애플리케이션 클래스에 의해 로드됩니다. 상위 클래스 로더가 로드되면 상위 클래스인 확장 클래스 로더에 위임되고, 확장 클래스 로더는 상위 클래스인 시작 클래스 로더에 위임합니다. 상위 클래스 로더 피드백이 로딩 요청을 완료할 수 없는 경우에만 하위가 로드를 완료하려고 시도합니다. 이 프로세스가 상위 위임입니다. 세 가지의 부모-자식 관계는 상속을 통해 달성되지 않고 결합 모드를 통해 이루어집니다.

이 프로세스의 구현도 매우 간단합니다. 핵심 구현 코드는 다음과 같습니다.

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    // 首先检查该类是否被加载过
    // 如果加载过,直接返回该类
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 如果父类抛出ClassNotFoundException
            // 说明父类无法完成加载请求
        }

        if (c == null) {
            // 如果父类无法加载,转由子类加载
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}
로그인 후 복사

댓글과 결합하면 다음과 같습니다. 누구나 이해하기 쉽습니다.

부모가 위임한 코드에서 동일한 클래스 로더 하에서는 클래스가 한 번만 로드될 수 있으므로 클래스가 한 번만 초기화되도록 제한된다는 것을 알 수 있습니다. 따라서 클래스의 정적 코드(정적 메서드 제외)는 클래스 초기화 중에 한 번만 실행됩니다

4. <init> 및 <clinit>

이전에는 컴파일러에 의해 자동으로 생성된 클래스 생성자가 도입되었습니다. clinit> 메서드를 사용하면 모든 정적 수정 클래스 변수의 할당 작업과 정적 문 블록(정적{} 블록)을 수집하고 코드 표시 순서를 유지합니다. 클래스 초기화 중에 실행됩니다

따라서 컴파일러도 마찬가지입니다. 또한 < ;init> 메서드를 생성하고 인스턴스 필드의 할당 작업, 초기화 문 블록({} 블록)의 코드 및 생성자(Constructor)를 수집하고 코드 표시 순서를 유지합니다. 새 명령어 이후에 실행됩니다

그래서 새 클래스를 생성할 때 JVM이 해당 클래스를 로드하지 않은 경우 먼저 초기화된 다음 인스턴스화됩니다.

이 시점에서 세 번째 우선순위 규칙이 나올 준비가 되었습니다:

  • 정적 코드(정적{} 블록, 정적 필드 할당 문) > 초기화 코드({} 블록, 인스턴스 필드 할당 문)

5. 이전 규칙 세 개를 결합하고 다음 두 가지를 요약했습니다. :

1. 정적 코드(정적{} 블록, 정적 필드 할당 문) > 초기화 코드({} 블록, 인스턴스 필드 할당 문) > 생성자 코드

2. 정적 코드

이전 요약에 따르면 초기화 코드와 생성자 코드는 컴파일러에 의해 <init>로 수집되고 정적 코드는 <clinit>로 수집되므로 위의 규칙이 다시 병합됩니다.

부모 클래스

처음에 나온 질문에 맞게 연습해 보세요. <clinit> > 子类<clinit> > 父类 <init> > 子类 <init>

new Child()를 실행할 때 new 키워드는 Child 클래스의 초기화를 트리거합니다. JVM은 상위 클래스가 있음을 발견하면 먼저 Parent 클래스를 초기화합니다. Parent 클래스의 실행을 시작한 다음 Child 클래스의 <clinit> 메소드를 실행합니다(<clinit>에 수집된 내용을 기억하시나요?).

이제 Child의 <init> 메소드를 실행할 준비가 되었습니다. 먼저 상위 클래스의 <init> 메소드를 실행하겠습니다. , 그리고 하위 클래스의 <init>를 실행합니다. (<init>에 무엇이 수집되었는지 기억하시나요?)

이 글을 읽고 나면 시작 질문에 대한 답을 이미 알고 계시리라 믿습니다. 먼저 출력 시퀀스를 직접 작성한 다음 코드를 작성하여 직접 확인하는 것이 좋습니다.

결론

Static은 글을 쓸 때마다 항상 두 가지 질문이 떠오릅니다. Static이 없어도 괜찮을까요?

이 기사에서 볼 수 있듯이 정적의 적용은 클래스 변수를 훨씬 넘어서고 정적 메서드만큼 간단합니다. 클래식 싱글턴 패턴에서는 static의 다양한 용도를 볼 수 있으며, 다음 기사에서는 싱글턴 패턴을 멋진 방식으로 작성하는 방법에 대해 설명합니다.

위 내용은 Java의 static 키워드를 한번에 이해하게 해주세요의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:segmentfault.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!