为什么实例常量在对象的所有字段都设置为默认值时会有一个值?

huangapple go评论74阅读模式
英文:

Why an instance constant has a value when the object has all fields set to default?

问题

以下是您要求的代码翻译部分:

public class Main {
    public static void main(String[] args) {
        new B();
    }
}

class A {
    A() {
        System.out.println("A 构造函数之前");
        action();
        System.out.println("A 构造函数之后");
    }

    protected void action() {
        System.out.println("从未被调用");
    }
}

class B extends A {
    private final int finalField = 42;
    private int field = 99;

    B() {
        System.out.println("B 构造函数");
        action();
    }

    public void action() {
        System.out.println("B 动作,finalField=" + finalField + ",field=" + field);
    }
}

至于您提到的代码中的一行:

B action, finalField=42, field=0

变量 "finalField" 在对象 B 创建之初已经初始化,但是变量 "field" 在父类构造函数中调用 "action" 方法时仍然具有默认值。

英文:

Here is the code:

public class Main {
    public static void main(String[] args) {
        new B();
    }
}

class A {
    A() {
        System.out.println("A constructor before");
        action();
        System.out.println("A constructor after");
    }

    protected void action() {
        System.out.println("Never called");
    }
}

class B extends A {
    private final int finalField = 42;
    private int field = 99;

    B() {
        System.out.println("B constructor");
        action();
    }

    public void action() {
        System.out.println("B action, finalField=" + finalField + ", field=" + field);
    }
}

And the result is:

A constructor before
B action, finalField=42, field=0
A constructor after
B constructor
B action, finalField=42, field=99

I confused by this line :

B action, finalField=42, field=0

Object B is not completely initialized and when we call method "action" from super class constructor - variable "field" has a default value, but the final variable "finalField" already has value 42.

When was the "finalField" initialized?

答案1

得分: 5

当使用常量表达式(15.29)初始化最终字段时,它被称为常量变量(4.12.4)

private final int finalField = 42;

这意味着字符串

"B action, finalField=" + finalField + ", field="

本身就是一个常量表达式,其值在编译时确定。如果您检查已编译的类文件,您实际上会在常量池部分找到字符串 B action, finalField=42, field=

通常情况下,当使用常量变量字段时,它必须在编译时被其值替换。在运行时不允许(13.1)引用字段:

> 3. 对于常量变量(§4.12.4)的字段的引用必须在编译时解析为常量变量初始化器所表示的值 V。
>
> 如果这样的字段是非静态的,则代码中不能存在对字段的引用,除了在包含字段的类中(它将是类,而不是接口,因为接口只有静态字段)。该类应该有代码,在实例创建(§12.5)期间将字段的值设置为 V。

字段初始化程序仍然在您预期时运行:在 A 构造函数返回之后,在 B 构造函数开始之前。观察未初始化的值很棘手,因为编译器会内联变量的使用,但您可以通过反射访问字段的值:

public void action() {
    try {
        System.out.println("B action, finalField="
            + getClass().getDeclaredField("finalField").get(this)
            + ", field=" + field);
    } catch (IllegalAccessException | NoSuchFieldException e) {
        e.printStackTrace();
    }
}

输出:

A构造函数之前
B action, finalField=0, field=0
A构造函数之后
B构造函数
B action, finalField=42, field=99
英文:

When a final field is initialized with a constant expression (15.29), it's called a constant variable (4.12.4):

private final int finalField = 42;

This means that the string

"B action, finalField=" + finalField + ", field="

is a constant expression itself and its value is determined at compile time. If you inspect the compiled class file you will in fact find the string B action, finalField=42, field= in the constant pool section.

In general, when a field that is a constant variable is used, it has to be replaced by its value at compile time. It is not allowed (13.1) to reference the field at run time:

> 3. A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.
>
> If such a field is non-static, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only static fields.) The class should have code to set the field's value to V during instance creation (§12.5).

The field initializer still runs when you would expect: after A constructor has returned and before B constructor starts. Observing the uninitialized value is tricky because the compiler inlines the uses of the variable, but you can access the value of the field through reflection:

public void action() {
    try {
        System.out.println("B action, finalField="
            + getClass().getDeclaredField("finalField").get(this)
            + ", field=" + field);
    } catch (IllegalAccessException | NoSuchFieldException e) {
        e.printStackTrace();
    }
}

Output:

A constructor before
B action, finalField=0, field=0
A constructor after
B constructor
B action, finalField=42, field=99

答案2

得分: 1

在Java中有两种类型的流程。

1. 静态流程
2. 实例流程

**静态流程**

每当我们执行父子类时,将自动执行以下事件序列。

- 从父类到子类识别静态成员
- 从父类到子类执行静态变量赋值和静态块
- 只执行子类的main方法

**实例流程**

- 每当我们执行Java类时,静态控制流将首先执行。
- 从父类到子类识别实例成员
- 只在父类中执行实例变量赋值和实例块
- 执行父类构造函数
- 在子类中执行实例变量赋值和实例块
- 执行子类构造函数

基于这些规则,我将尝试解释JVM如何执行您的代码。

1. 识别静态成员:-> public static void main(String[] args);

2. 执行main方法

3. 首先识别类A中的实例成员,然后是类B中的实例成员

   > //A类成员
   >
   > protected void action();
   >
   > //B类成员
   >
   > private final int finalField = 42;(因为这是final,编译器将直接赋值)
   >
   > private int field = 0;(为这个成员添加默认值)
   >
   > public void action();

4. 只在父类中执行实例变量赋值和实例块

   // 没有成员需要赋值

5. 执行父类构造函数

   B构造函数中的第一行是super(),因此控制权将转到A类构造函数。

   **System.out.println("A constructor before")**
   
   **action();** //这有点棘手,因为这两个方法编译器已经识别出来了,
   JVM有两个选项可供选择,调用A类中的action()还是B类中的action(),
   但由于在B类中覆盖了action()方法,Java会使用运行时多态规则来执行B类中的action()方法。

   // 此时field的值为0

   **System.out.println("A constructor after")**

6. 只在子类中执行实例变量赋值和实例块

   > private int field = 99

9. 执行子类构造函数
英文:

In java there are two types of flows .

  1. Static flow
  2. Instance flow

Static Flow

Whenever we are executing Parent Child Class the following sequence of events will
be performed automatically.

  • Identification of static Members from Parent to Child

  • Execution of Static Variable Assignments and static blocks from parent to child

  • Execution of Only Child class main

Instance Flow

  • Whenever we are executing a Java class static control flow will be executed first.

  • Identification of Instance Members from Parent to Child

  • Execution of Instance variable assignments and instance block only in parent
    class

  • Execution of Parent Class Constructor

  • Execution of Instance variable assignments and instance blocks in child class

  • Execution of child class constructor

Based on these rules i will try to explain how jvm executes your code .

  1. Identifies static member:-> public static void main(String[] args) ;

  2. Execute main method

  3. Identify instance members in A class first and then B class

> //A class members
>
>
> protected void action();
>
>
> //B class members
>
>
> private final int finalField =42; (since this is final complier will direclty assing it )
>
>
>
>
> private int field =0;(add default values for this member)
>
>
>
>
> public void action();

  1. Execution of Instance variable assignments and instance blocks only in parent

// no member to assign

  1. Execute Parent Construcor

    First line in B constructor is super() so the control will go in A class constructor.

    System.out.println("A constructor before")

    action(); //This is little bit tricky ,since the both methods are already
    identified
    by the compiler jvm has two options to choose ,action() in A class or
    action() B ,but
    .since action method is overriden in B class and java for overriden methods
    uses
    Runtime Polimorifizem rule ,it will execute action() method in B classs

    // at this time value of field is 0

    System.out.println("A constructor after")

  2. Execution of Instance variable assignments and instance blocks only in child
    class

> private int field =99

  1. Execute child class constructor

huangapple
  • 本文由 发表于 2020年8月16日 19:17:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/63436203.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定