`

Java对象的实例化过程

    博客分类:
  • java
 
阅读更多

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

本文将对Java类实例化对象的过程进行详细的总结,在阅读本文后,你会了解JVM生成对象的执行过程。

1、普通的类

/**

* 一个简单的类

* @author zhang xl

*

*/

public class SimpleObject

{

   private String name;

   private int age;

   public SimpleObject(){}

   public SimpleObject(String name,int age){

      this.name=name;

      this.age=age;

   }

   /**

    *

    * 主函数用来测试

    */

   public static void main(String[] args)

   {

      //生成简单对象

      SimpleObject simpleObject=new SimpleObject();

      System.out.println("age="+simpleObject.age);

      System.out.println("name="+simpleObject.name);

      SimpleObject simpleObject2=new SimpleObject("lisi",15);

      

      System.out.println("age="+simpleObject2.age);

      System.out.println("name="+simpleObject2.name);

   }

}

此处,类中声明的两个成员变量没有显示的赋值,那么在执行SimpleObject simpleObject=new SimpleObject();的时候,将直接执行无参的构造函数,但是在执行之前,如果你断点调试,那会发现在进行入无参构造函数之前,你单击step into,会跳转到Object类的那个无参构造函数去执行(如果你的类文件没有关联到源文件,那么你会看到Class File Editor透视图,此时你若仔细观察,仍然可以看到提示The source attachment does not contain the source for the file Object.class.)为什么呢?因为SimpleObject默认继承自Object类,所以在执行对应的构造函数之前会默认先调用一下父类的那个构造函数(相当于super();这条语句在构造函数中被调用),执行 SimpleObject simpleObject2=new SimpleObject("lisi",15);的时候,也是先进入到Object类的无参构造方法中,然后才会执行构造函数。

1.2如果此时给成员变量进行显示的初始化:

private String name=”zhangsan”;

   private int age=”10”;

    那么生成对象的流程与上面的流程基本相同,但是在执行父类的构造函数之后,会进阶着执行显示初始化,然后才会执行构造函数,因此如果构造函数中进行了赋值操作,那么就会覆盖掉显示初始化的赋值。

2、含有静态成员变量

private static String name="zhangsan";

private int age=10;

那么在断点调试的过程中,我们会发现,private static String name="zhangsan";这条语句在父类的构造函数执行之后,不会执行到,而是跳到private int age=10;这条语句上去执行,为什么呢?因为静态的成员变量和静态代码块是在类加载器加载对应类的时候就执行了,由于我们的main函数在这个类中,所以,在执行main函数之前,类加载器(实际上是sun.misc.Launcher$AppClassLoader这个类加载器),将Java编译器已经编译好的SimpleObject.class文件加载到内存,此时,静态代码块已经被执行(并且static修饰的代码只会被执行一次),所以我们在断点调试时,会发现,JVM将不会在执行那条语句。

3、具有继承关系的类的对象的加载机制

3.1在上面的类的基础上,添加一个其子类

 

 

class ChildSimpleObject extends SimpleObject

{

   private char gender;

   public ChildSimpleObject(){}

   public ChildSimpleObject(String name,int age,char gender){

      super(name,age);

      this.gender=gender;

   }

}

那么,在执行ChildSimpleObject child=new ChildSimpleObject();语句的时候,会先执行父类的父类的构造函数,如果父类还有父类,那么还要先执行父类的父类的构造函数,直到执行完Object的构造函数,在执行构造函数之前还会检查该类的成员变量是否进行了显示初始化,那么还要先执行显示初始化语句,然后在执行构造函数,按照次执行顺序,直到执行完当前类的构造函数。

3.2如果有static修饰的成员变量或代码块,那么也都不在执行,原因同上面的解释。

4、比较复杂的一种情况(上转型的多态):

    我们都知道,如果子类重写了父类的某个方法,那么上转型对象在调用该方法时,执行的是子类重写后的方法,那么对于成员变量呢?

 

public class ComplexObject

{

   int age=50;

   public ComplexObject(){}

   public int getAge()

   {

      return age;

   }

   public void setAge(int age)

   {

      this.age = age;

   }

   public static void main(String[] args)

   {

      ComplexObject obj=new ChildComplexObject();

      System.out.println(obj.age);

      ChildComplexObject obj2=(ChildComplexObject) obj;

      System.out.println("obj2="+obj2.age);

   }

}

class ChildComplexObject extends ComplexObject

{

   public ChildComplexObject(){

      

   }

   public ChildComplexObject(int age){

      this.setAge(age);

   }

}

此时,上转型对象得到的age是父类中显示初始化的50.

如果在子类中也添加一个相同的成员变量呢?

即在子类中添加public int age=80;,此时,执行主函数中的方法,得到的仍是50,而此时,如果在将上转型对象在转回到ChildComplexObject类型时,此时,调用的age是子类中自己定义的80,但是如果将System.out.println("obj2="+obj2.age);这条语句改为System.out.println("obj2="+obj2.getAge());结果打印的仍然是50.

这是为什么呢?

因为如果子类中声明了一个和父类一样的成员变量,那么在生成子类对象的时候,这个子类对象对应的堆内存中会保留两份age空间,一份继承自父类,一份是自己的成员变量,上转型对象,其实质上仍然是子类new的对象,只不过用父类的引用来指向它,但是在访问成员变量,即直接访问.age的时候,其获取的是内存中父类那份的值50,而如果此时再向下转型,转为将刚new的子类的对象转为子类的引用,那么此时若再直接用.age来访问,那么访问的将是子类自己的那份age的值。而若用getAge的方法来访问,即使用子类对象的引用来访问,仍然是父类的值,因为getAge方法是从父类继承过来的,在执行该方法的时候,代码会转到父类去执行,在父类执行的时候,那个getAge方法中返回的age就是父类对象的那个age,值为50。如果在子类中写一个和父类一抹一样的getAge方法,那么再次执行的时候,得到的就是子类自己的age的值80.

原因安在啊?

因为在开辟内存的时候,类中的成员方法是只开辟一块内存的,即生成的多个对象中都有对成员方法的地址引用(每个对象中都有着对应类中成员方法的入口地址),当子类没有重写父类的方法时,子类对象保存的就是父类方法中方法地址,因此,如果访问该方法,那么就是访问父类的方法,对应的成员变量的内存空间也都是父类的。如果子类重写了父类的方法,那么子类中的地址则指向的是子类自己定义的方法的地址,那么此时子类对象(包括上转型对象)访问的都是子类中重写的方法。

如果仅仅是声明一个对象,比如:User u;那么在断点调试的时候,我们可以发现,这条语句不会被单步执行到,为甚么?

声明变量,只是说,这个变量名被占用了,而并没有开辟内存的动作,因此这句话不会执行加载User类的动作。

分享到:
评论

相关推荐

    java实例化对象的过程

    工厂方法是一种设计模式,它提供了一种创建对象的抽象,允许子类改变实例化过程。单例模式确保一个类只有一个实例,并提供全局访问点。 总的来说,Java实例化对象是编程中的基本操作,它涉及到类的构造,内存分配,...

    java 基础 类对象创建实例化过程 实例解析

    继承关系:类的实例化顺序 * 执行过程为:启动类是否为继承关系树中的一个,如果是则先执行启动类的所有父类的静态语句块;然后执行启动类的静态语句块static{} -> * 执行启动类的main函数 -> 创建对象的继承树从...

    Java子类对象的实例化过程分析

    "Java子类对象的实例化过程分析" Java子类对象的实例化过程是Java面向对象程序设计中一个非常重要的概念。它涉及到Java类的继承、构造函数的调用、对象的实例化等多方面的知识。在这篇文章中,我们将详细介绍Java...

    Java面向对象(进阶)- super关键字的使用与子类对象实例化全过程

    本篇文章将深入探讨`super`关键字的使用以及子类对象实例化的全过程。 一、super关键字的使用 1. **为什么需要super?** 当子类继承父类后,可能出现以下情况: - 子类重写了父类的方法,但仍需在子类中调用...

    浅析JSP、JAVASCRIPT及JSP与JAVA组件实例化过程分析.pdf

    "浅析JSP、JAVASCRIPT及JSP与JAVA组件实例化过程分析" 一、JSP、JAVASCRIPT和JSP与JAVA组件实例化过程分析概述 近十年来,客户端网页脚本语言JAVASCRIPT、动态网页技术JSP和JAVA开发组件经历了长足的发展。在实际...

    java对象创建过程

    在整个过程中,可以看到类加载与对象实例化之间的紧密联系。了解这些细节对于深入理解Java编程至关重要,特别是对于内存管理和性能优化方面有着不可忽视的作用。 总结而言,Java对象的创建过程涉及类加载、内存分配...

    计算机后端-Java-Java核心基础-第14章 面向对象06 03. 复习:子类对象实例化过程及多态性.avi

    计算机后端-Java-Java核心基础-第14章 面向对象06 03. 复习:子类对象实例化过程及多态

    Java对象序列化标准最新版

    ### Java对象序列化标准知识点详解 #### 一、系统架构概览 **1.1 概览** Java 对象序列化是一种将Java对象的状态转换成字节流的过程,以便于在网络上传输或存储到磁盘上。Java序列化标准定义了一套规则来描述如何...

    JAVA对象序列化保存为XML文件的工具类

    【JAVA对象序列化保存为XML文件的工具类】 在Java编程中,对象序列化是一种将对象的状态转换为字节流的过程,以便可以存储或在网络上传输。而在反序列化时,这个字节流又可以恢复为原来的对象。Java提供了一个方便...

    java对象映射器(基于jackson将Java对象转为json,或者将json转为Java对象)

    答:将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象] 从Java对象生成JSON的过程称为 [序列化Java对象到JSON] 为什么用它? 答:我数据库中的主键是使用雪花算法生成的,就是因为用id的位数太多,导致在...

    java 对象序列化

    除了基本的`ObjectOutputStream`和`ObjectInputStream`,Java还提供了`ObjectOutput`和`ObjectInput`接口,以及它们的实现如`ObjectDataOutput`和`ObjectDataInput`,允许更精细的控制序列化过程。 总之,Java对象...

    java对象的串行化

    【Java对象的串行化】是指将Java对象的状态转换为字节序列的过程,这使得对象可以在需要时重新创建,从而实现对象的持久化。对象串行化是Java平台的一个核心特性,它允许对象的状态被保存并能够在不同的时间点或者在...

    关于 Java 对象序列化您不知道的 5 件事

    这是因为静态变量属于类,而非特定的对象实例。序列化时,只有那些非transient和非static的字段会被写入字节流。 3. **自定义序列化行为** 开发者可以通过重写`writeObject()`和`readObject()`方法来自定义序列化...

    java内存对象分配过程研究

    这行代码仅仅声明了一个类型为`Color`的引用变量`colory`,但并未创建真正的对象,因此该引用暂时没有指向任何具体的对象实例。 ##### 3.2 构造对象 构造对象涉及两个关键步骤:内存分配和初始化。 1. **内存分配...

    通过实例学习Java对象的构造过程

    本文将通过一个实际项目的案例,探讨Java对象的构建与初始化过程,并从中总结出设计Java类时需遵循的重要原则。 #### 案例描述 案例源自一个项目中的抽象对话框基类——`BaseDlg`的设计与实现。该基类旨在简化...

    Java让泛型实例化的方法

    然而,在 Java 中使用泛型时,会出现一个问题:如何实例化泛型对象?在 Java 中,泛型擦除机制使得编译器无法确定泛型所对应的真实类型,因此无法直接实例化泛型对象。下面将详细介绍 Java 让泛型实例化的方法。 ...

    java对象序列化.ppt

    `transient`关键字确保在序列化和反序列化过程中,这些属性的值不会被保存或恢复,从而提供了一种保护数据隐私的方式。需要注意的是,即使`transient`变量不被序列化,但它的引用如果指向一个可序列化的对象,那么这...

    计算机后端-Java-Java核心基础-第13章 面向对象05 15. 子类对象实例化的全过程.avi

    计算机后端-Java-Java核心基础-第13章 面向对象05 15. 子类对象实例化的全过程.avi

    精通Hibernate:Java对象持久化技术详解.pdf

    《精通Hibernate:Java对象持久化技术详解》这本书深入剖析了Hibernate这一流行的Java对象关系映射(ORM)框架,旨在帮助开发者全面理解并熟练掌握Hibernate的使用。Hibernate是Java开发中的重要工具,它简化了...

Global site tag (gtag.js) - Google Analytics