由于java中的继承而创建了多少个对象?

Posted

技术标签:

【中文标题】由于java中的继承而创建了多少个对象?【英文标题】:How many objects are created due to inheritance in java? 【发布时间】:2013-07-26 12:04:04 【问题描述】:

假设我有三个班级:

class A 
    A() 
        // super(); 
        System.out.println("class A");
    

class B extends A 
    B() 
        // super(); 
        System.out.println("class B");
    

class C extends B 
    public static void main(String args[]) 
        C c = new C(); //Parent constructor will get called
    

当我创建一个类 C 的实例时,它会调用超类的构造函数。那么,创建的对象不止一个吗?如果只创建一个对象,那么 super() 与另一个类的构造函数有何不同? super() 方法是否在内部创建一个对象?我知道的是,构造函数也是一个方法(我可能错了)。

我的问题是:

    这种情况下创建了多少个Object? 如果创建了一个对象,那么 Super() 如何在内部调用父类的构造函数?

【问题讨论】:

每个new 创建一个新对象。 我知道编译器会在每个构造函数中添加 Super() 作为第一条语句。但我想知道 super 是如何调用类构造函数的。它是否存储其他类对象的引用或他能够调用的方式 【参考方案1】:

很好的问题。您正在探索的是 Java 如何初始化对象 - 涉及到许多步骤。

我知道构造函数也是一种方法(也许我错了)。

几乎正确。构造函数是一种特殊的方法。如果你反编译一个类文件,你会看到构造函数被重命名为<init><init> 的处理方式与其他方法不同,例如,不能显式调用,除非使用关键字 newsuper。这是非常基础的,以至于它是在 JVM 本身中实现的,而不是在 Java 语言中定义的东西。

在这种情况下创建了多少个 Object。

创建了一个对象 - C 的一个实例。

C 还同时是B 的一个实例和A 以及Object 的一个实例。

如果创建了一个对象,那么 super() 在内部如何调用父类 Constructor 。 Super 是如何调用父类构造函数的。

这是我们进入初始化的地方 - 初始化是 JVM 创建对象的新实例并设置所有成员值的方式 - 特定类的成员值和超类的成员值。涉及几个阶段:

加载所有引用的类并初始化这些类。类初始化本身并不重要,所以我不会在这里介绍它。值得一读。 分配一块内存来保存实例的成员,这将包括ABC的所有成员。 注意 这解释了您问题的一个方面:基类及其子类的构造函数如何更新或引用同一个对象 - 所有类的实例的所有成员都存储一个在同一块内存中的另一个之后。 将所有成员初始化为它们的默认值。例如,intfloat 成员将设置为 0 和 0.0f。

执行或计算成员初始化器,例如:

private int a = 10;
private int b = a * 5;
private String c = Singleton.getInstance().getValue();

注意 (1) 成员初始化严格按照成员在类中声明的顺序进行。这意味着声明后面对成员的引用被破坏:

private int a = b * 5; // Forward reference; won't compile
private int b = 10;

注意 (2) 在 Java 中有一个未充分使用的工具来运行任意代码来初始化值在执行构造函数之前。这些代码块此时再次严格按照声明顺序执行:

private int a;
private int b = 1;

    // Initization occurs after b but before c.
    // c cannot be referenced here at all
    int i = SomeClass.getSomeStatic();
    a = i * 2;

private int c = 99;

执行C的构造函数。构造函数必须直接从超类调用构造函数,否则编译器将自动添加super() 作为构造函数的第一行。这意味着构造函数是严格按顺序执行的:

    Object A B C

对象现在已经初始化并且可以使用了。如果您使用实例方法初始化值,您可能会做一些危险的事情:

public class Wrong 
    int a = getB(); // Don't do this!
    int b = 10;
    public int getB() 
         return b;
    

这里,a 被初始化为 0。这是因为,在调用getB() 时,Java 已将b 的值清除为默认值(0),但尚未在初始化的第二阶段将其设置为10

总而言之-只有一个对象,它是分阶段创建和初始化的。在这些阶段,根据定义,对象并未完全定义。

【讨论】:

我在职业生涯中看到的关于 Java 的最佳答案之一。【参考方案2】:

    将创建一个且只有一个对象,即。一个对象。

    你可以想象,当 A 类扩展 B 时,所有的方法和变量都被复制到 A 类中。

【讨论】:

【参考方案3】:

在代码中只会创建一个对象并超级调用父类构造函数。

对象创建的证明:

package one;

public class A 
    public static A super_var;

    public A() 
        super_var = this;
        System.out.println("Constrcutor of A invoked");
    


package two;

public class B extends A 
    public static A sub_var;

    public B() 
        sub_var = this;
        System.out.println("Constructor of B invoked");
    

    public void confirm() 
        if (sub_var == A.super_var)
            System.out.println("There is only one object is created");
        else
            System.out.println("There are more than one object created");
    

    public static void main(String Args[]) 
        B x = new B();
        x.confirm();
    

这将证明只会创建一个对象。

关于Super()。 我知道它叫 Parent class constructor 。并且每个构造函数都将Super() 作为您在代码中提到的第一条语句。让你知道

我不知道它是如何在内部调用超类构造函数的。

希望这会让你明白只有你在程序中创建的实例

【讨论】:

编译器会自动将super()添加到每个没有显式调用super(...)的构造函数的开头 @RobEarl 。是的,你是对的,我只知道 Super () 作为每个类构造函数中的第一条语句。但我知道他在问Super() 是如何做到这一点的。【参考方案4】:
    仅在您的情况下,将创建 1 个对象。 调用子类构造函数时,会在内部调用超类的构造函数来初始化超类的成员。

调用构造函数并不意味着您正在创建对象。调用构造函数时已经创建了对象。对象首先由JVM创建(即在堆上分配内存,然后调用构造函数)。

构造函数用于初始化对象的成员。

【讨论】:

【参考方案5】:

您的课程将在内部转换为类似的内容

class A

    A()
        super(); 
        System.out.println("class A");
    


class B extends A
    B()
        super(); 
        System.out.println("class B");
    


public class C extends B

    public static void main(String args[])
    
        C c  = new C(); //Parent constructor will get call 
    

在这种情况下创建了多少个 Object。

只有一个,它是C的实例,调用super()只是调用 父类构造函数而不是创建对象

如果创建了一个对象,那么 Super() 内部如何调用 Parent 类构造函数。 Super如何能够调用父类 构造函数。

当您创建C 的实例时。 C的构造函数被调用,它首先调用B的构造函数,然后又调用A的构造函数

【讨论】:

他已经在代码中提到了内部代码将被转换的内容以及关于 Super()。你说的和她问的一样。他的问题是 C 类构造函数如何调用 B 类构造函数。我希望我对他的要求是正确的。 我在回答 OP 的第二个问题。我看到 OP 评论了 super() 调用【参考方案6】:
How many number of Object is created in this case.

当您通过C cInstance = new C(); 创建 C 类的实例时,将创建 C 类的单个实例(对象)(A 和 B 均未创建)。然而,由于 C 扩展了 B 和 B 扩展了 A,C 将具有 A 类和 B 类的所有方法(实际上取决于使用的访问修饰符,但在这种情况下,它们是公共的或默认的)。

If one object is created then how internally Super() is calling Parent class Constructor
. How Super is able to call parent class constructor.

这就是继承的工作原理。当创建一个新对象时,它将调用它的超类构造函数,而该超类将调用它的超类构造函数,依此类推。在其他普通函数中,您必须显式调用 super()。所以调用超类构造函数是自下而上的,而执行是继承层次树的自上而下的方式

【讨论】:

【参考方案7】:

如果您按照this SO 答案查看对象分配的动态,则必须清楚使用new 运算符,每个语句只创建一个对象。为了进一步澄清只有一个对象被创建的疑问,请通过这个程序:

public class A 
    public static int aInstanceCount=0;
    public static A aInstance;
    public String aInstanceVariable;
    A() 
//Super();
        aInstanceCount++;
        aInstanceVariable="aInstanceVar";
        System.out.println("class A");
        aInstance=this;
    


class B extends A 
    public static int bInstanceCount=0;
    public static B bInstance;
    public String bInstanceVariable;
    B() 
//Super();
        bInstanceCount++;
        bInstanceVariable="bInstanceVar";
        System.out.println("class B");
        bInstance=this;
    


class C extends B 
    public static void main(String args[]) 
        int instanceCount=0;
        C c = new C(); //Parent constructor will get call
        if(A.aInstance!=null)
            instanceCount++;
            System.out.println("Value of aInstanceVariable: "+A.aInstance.aInstanceVariable);

        
        if(B.bInstance!=null)
            instanceCount++;
            System.out.println("Value of bInstanceVariable: "+B.bInstance.bInstanceVariable);
        
        A a=A.aInstance;
        B b=B.bInstance;
        System.out.println("bInstanceVariable of B earlier: " + B.bInstance.bInstanceVariable);
        //Now we are changing the bInstanceVariable of c which is inherited from B
        c.bInstanceVariable="bInstance After modified by C";
        System.out.println("bInstanceVariable of B after: " + B.bInstance.bInstanceVariable);
        System.out.println("aInstanceVariable of A earlier: " + A.aInstance.aInstanceVariable);
        //Now we are changing the aInstanceVariable of c which is inherited from A
        c.aInstanceVariable="aInstance After modified by C";
        System.out.println("bInstanceVariable of A after: " + A.aInstance.aInstanceVariable);
    

输出:

class A
class B
Value of aInstanceVariable: aInstanceVar
Value of bInstanceVariable: bInstanceVar
bInstanceVariable of B earlier: bInstanceVar
bInstanceVariable of B after: bInstance After modified by C
aInstanceVariable of A earlier: aInstanceVar
bInstanceVariable of A after: aInstance After modified by C

如果你能注意到,超级构造函数在每次创建子类对象时都会被隐式调用,但由于new 运算符只使用一次,因此只有一个对象实际分配了空间。而通过C对象c修改aInstanceVariable,我们实际上是在改变aInstanceaInstanceVariable。所以它清楚地证明了实际上存在一个对象。

【讨论】:

【参考方案8】:

调用构造函数创建对象时创建对象的步骤:

    使用 init 分配内存已完成。此 init 进行系统调用以分配内存以创建对象。

    然后调用你的构造函数来初始化对象的字段。

    然后它调用超类构造函数(如果有任何超类)并重复步骤1到3。

当您使用 javap 反编译类文件时看到的内容显示了要进行的不同调用。 init 进行系统调用以初始化内存分配,但在运行构造函数的代码时会初始化对象的字段。

【讨论】:

【参考方案9】:

我不确定多态性/覆盖在 GC 时是如何工作的。

但是应该值得尝试在所有类中覆盖 finalize 方法并检查 JVM 何时退出 main 方法。

如果只创建了C 对象,它应该为'C'调用finalize。 如果创建了所有A,B,C对象,它应该为A,B,C调用finalize

我认为这是您可以申请的最简单的检查。

class A 
    A() 
        //Super(); 
        System.out.println("class A");
    

    public void finalize()
    System.out.println("Class A object destroyed");
    

class B extends A 
    B() 
       //Super(); 
        System.out.println("class B");
    

    public void finalize()
    System.out.println("Class B object destroyed");
    

class C extends B 
    public static void main(String args[]) 
        C c = new C(); //Parent constructor will get call 
    

    public void finalize()
    System.out.println("Class C object destroyed");
     

【讨论】:

【参考方案10】:

我同意之前发布的答案,但想添加对这个问题的最终权威 Java 语言规范的引用。

表达式new C() 是一个“类实例创建表达式”。 15.9.4 Run-time Evaluation of Class Instance Creation Expressions 部分描述了创建对象所涉及的运行时步骤。请注意,它指的是“对象”,并且只分配一次空间,但声明“接下来,调用指定类类型的选定构造函数。这导致为类类型的每个超类调用至少一个构造函数。”

通过区分创建新对象和调用构造函数,这一切变得更加清晰。调用构造函数只完成对象创建的一部分、运行初始化程序、超类构造函数和构造函数主体的部分。因为 C 也是 B,所以 B 构造函数必须在创建 C 期间运行。

【讨论】:

【参考方案11】:

super 关键字使子类能够调用其超类的方法和字段。它不是超类对象的实例,而是一种告诉编译器要引用哪些方法或字段的方法。效果与子类调用它自己的方法之一一样。 例子:

考虑一个扩展其超类 Person 的子类 Employee:

public class Employee extends Person

   public Employee()
   
     //reference the superclass constructor 
     super(); 
   

   public String getName()
   
     //reference superclass behaviors
     return super.getFirstName() + " " + super.getLastName();
   
  

super 关键字可用于引用 Person 类的构造函数或它有权访问的任何行为或字段(例如 getFirstName() 和 getLastName())。

【讨论】:

【参考方案12】:

    在您的案例一中创建对象

    在执行以下操作时,此 super() 将由编译器隐式提供

    class A 
    A() 
        System.out.println("class A");
    
    
    class B extends A 
    B() 
        System.out.println("class B");
    
    
    class C extends B 
    public static void main(String args[]) 
        C c = new C(); //
    
    
    

这类似于在你的方法中调用 super()

    B() 
        super();
        System.out.println("class B");
    

当一个方法在当前类中被重写,但你想调用超类方法时,也可以使用 super 关键字。

super() 将使所有构造函数都引用一个类。 (为了便于理解:就像所有的成员函数都属于同一个类。) 它只会调用所有的构造方法。

所以它只完成了调用构造函数的工作,所以 super() 不会创建任何对象。它只是指成员函数。

【讨论】:

【参考方案13】:

如果您再添加一行代码System.out.println(this.hashCode()) 将消除您的困惑。

在所有情况下hashCode() 将始终打印相同的hashCode。这意味着创建了一个且只有一个唯一的Object

class A 
    A() 
        // super(); 
        System.out.println(this.hashCode()); // it will print 2430287
        System.out.println("class A");
    

class B extends A 
    B() 
        // super(); 
        System.out.println(this.hashCode()); // it will print 2430287
        System.out.println("class B");
    

class C extends B 
    public static void main(String args[]) 
        C c = new C(); //Parent constructor will get called
        System.out.println(this.hashCode()); // it will print 2430287
    

但是有两个构造函数被调用来初始化Parent 成员变量。我想如果你知道super() 关键字的概念,它调用parent 类的构造函数并初始化parent 类的成员变量。

【讨论】:

【参考方案14】:

3 个构造函数将调用

代码:

class A

    A()
    
        System.out.println("In A");
    


class B extends A

    B()
    
        System.out.println("In B");
    


class C extends B

    C()
    
        System.out.println("In C");
    


public class InheritanceTest 
    public static void main(String args[])



    
        C c1=new C();
    


输出:

在 A

在 B 中

在 C 中

【讨论】:

以上是关于由于java中的继承而创建了多少个对象?的主要内容,如果未能解决你的问题,请参考以下文章

一个类的对象(使用单/多继承)将有多少个 vptr?

Scala笔记整理:Scala面向对象—类详解2(继承相关)

java——复用代码组合继承(java编程思想)

Java使用

Java的四个基本特性及相关问题

Java的四个基本特性及相关问题