У меня есть два очень похожих случая, когда единственное различие заключается в расположении метода main (либо в A1, либо в A4). Структура класса в обоих случаях одинакова.
Первый код:
Код: Выделить всё
package javatest;
// A1.java
import java.io.Serializable;
class A1 {
static {
new A2(new A1());
System.out.println("A1-S");
}
{
System.out.println("A1-N1");
}
private A1 a1;
public A1() {
System.out.println("A1()");
}
public A1(A1 a1) {
this();
System.out.println("A1(A1)");
this.a1 = a1;
new A2(a1);
}
public void metoda1() {
new A1();
System.out.println("A1.metoda1()");
}
{
System.out.println("A1-N2");
}
public static void main(String[] args) {
}
}
class A2 extends A1 implements Serializable {
static {
System.out.println("A2-S");
}
{
System.out.println("A2-N");
}
protected A2() {
System.out.println("A2()");
this.metoda1();
}
public A2(A1 a1) {
System.out.println("A2(A1)");
a1.metoda1();
}
@Override
public void metoda1() {
super.metoda1();
System.out.println("A2.metoda1()");
}
public void metoda2() {
System.out.println("A2.metoda2()");
}
}
class A3 extends A2 {
A2 a2 = null;
static {
System.out.println("a3-S");
}
{
System.out.println("a3-N1");
}
public A3() {
super();
System.out.println("a3()");
}
public A3(A2 a2) {
this();
this.a2 = a2;
System.out.println("a3(A2)");
}
{
System.out.println("a3-N2");
}
public A3(A1 a1, A2 a2) {
this(a2);
System.out.println("a3(A1,A2)");
}
public void metoda2() {
System.out.println("a3.metoda()");
}
}
class A4 extends A3 {
A1 a1 = new A1();
A3 a2 = new A3(new A1(new A1()), new A2(a1));
Serializable a3 = new A3();
static {
System.out.println("A4-S");
}
public A4() {
super();
System.out.println("A4()");
super.metoda1();
}
{
System.out.println("A4-N");
}
}
class A5 extends A1 {
static {
System.out.println("A5-S");
}
public A5() {
super();
System.out.println("A5()");
}
{
System.out.println("A5-N");
}
}
Код: Выделить всё
package javatest;
// A1.java
import java.io.Serializable;
class A1 {
static {
new A2(new A1());
System.out.println("A1-S");
}
{
System.out.println("A1-N1");
}
private A1 a1;
public A1() {
System.out.println("A1()");
}
public A1(A1 a1) {
this();
System.out.println("A1(A1)");
this.a1 = a1;
new A2(a1);
}
public void metoda1() {
new A1();
System.out.println("A1.metoda1()");
}
{
System.out.println("A1-N2");
}
}
class A2 extends A1 implements Serializable {
static {
System.out.println("A2-S");
}
{
System.out.println("A2-N");
}
protected A2() {
System.out.println("A2()");
this.metoda1();
}
public A2(A1 a1) {
System.out.println("A2(A1)");
a1.metoda1();
}
@Override
public void metoda1() {
super.metoda1();
System.out.println("A2.metoda1()");
}
public void metoda2() {
System.out.println("A2.metoda2()");
}
}
class A3 extends A2 {
A2 a2 = null;
static {
System.out.println("a3-S");
}
{
System.out.println("a3-N1");
}
public A3() {
super();
System.out.println("a3()");
}
public A3(A2 a2) {
this();
this.a2 = a2;
System.out.println("a3(A2)");
}
{
System.out.println("a3-N2");
}
public A3(A1 a1, A2 a2) {
this(a2);
System.out.println("a3(A1,A2)");
}
public void metoda2() {
System.out.println("a3.metoda()");
}
}
class A4 extends A3 {
A1 a1 = new A1();
A3 a2 = new A3(new A1(new A1()), new A2(a1));
Serializable a3 = new A3();
static {
System.out.println("A4-S");
}
public A4() {
super();
System.out.println("A4()");
super.metoda1();
}
{
System.out.println("A4-N");
}
public static void main(String[] args) {
}
}
class A5 extends A1 {
static {
System.out.println("A5-S");
}
public A5() {
super();
System.out.println("A5()");
}
{
System.out.println("A5-N");
}
}
A2-S
A1-N1
A1-N2
A1()
A1-N1
A1-N2
A1()
A2-N
A2(A1)
A1-N1
A1-N2
A1()
A1.metoda1()
A1-S
Вывод «Второго кода»:
A1-N1
A1-N2
A1()
A1-N1
A1-N2
A1()
A2-N
A2(A1)
A1-N1
A1-N2
A1()
A1.metoda1()
A1-S
A2-S
a3-S
A4-S
Я понимаю все основные концепции, например тот факт, что статический блок инициализации выполняется только один раз при загрузке класса, но такое поведение немного сбивает с толку.
Мои рассуждения следующие:
Когда в коде 2 метод main находится внутри класса A4, JVM начинается с A4 и может «видеть» всю иерархию наследования: A4 расширяет A3, A3 расширяет A2, а A2 расширяет A1. По этой причине я предположил, что во время выполнения статического блока инициализации в A1, когда он встречает выражение new A2(new A1()), сначала оценивается внутреннее выражение new A1(), поскольку JVM уже знает об A2 из-за иерархии классов, даже если A2 еще не полностью инициализирован.
В коде 1, где основной метод находится внутри класса A1, JVM изначально «видит» только A1. В начале нет активного обхода иерархии наследования. Он входит в статический блок A1, но внутри этого статического блока у нас есть новый A2(new A1()), поэтому JVM впервые встречает A2. Поскольку он еще не знает или не инициализировал A2, он приостанавливает выполнение статического блока A1, сначала переходит к инициализации A2, а затем продолжает.
Поправьте меня, если я ошибаюсь.
Мобильная версия