对象实例是何时被创建的?

开发 后端
对象实例何时被创建,这个问题也许你用一句话就能回答完了。但是它的潜在陷阱却常常被人忽视,这个问题也许并不像你想的那么简单,不信请你耐心看下去。

对象实例何时被创建,这个问题也许你用一句话就能回答完了。但是它的潜在陷阱却常常被人忽视,这个问题也许并不像你想的那么简单,不信请你耐心看下去。

我前几天问一个同学,是不是在调用构造函数后,对象才被实例化?他不假思索的回答说是。

请看下面代码:

Java代码

  1. Date date=new Date();     
  2.     
  3. em.out.println(date.getTime()); 

 

新手在刚接触构造函数这个概念的时候。他们常常得出这样的结论:对象实例是在调用构造函数后创建的。因为调用构造函数后,调用引用(date)的实例方法便不会报NullPointerException的错误了。

二、经验者的观点

然而,稍稍有经验的Java程序员便会发现上面的解释并不正确。这点从构造函数中我们可以调用this关键字可以看出。

请看下面代码:

Java代码

  1. public class Test     
  2.     
  3. {     
  4.     
  5.   public Test()     
  6.     
  7.   {     
  8.     
  9.       this.DoSomething();     
  10.     
  11.   }     
  12.     
  13.        
  14.     
  15.   private void DoSomething()     
  16.     
  17.   {     
  18.     
  19.       System.out.println("do init");     
  20.     
  21.   }     
  22.     
  23. }    

 

这段代码中我们在构造函数中已经可以操作对象实例。这也就证明了构造函数其实只是用于初始化,早在进入构造函数之前。对象实例便已经被创建了。

三、父类构造函数

当创建一个有父类的子类的时候。对象的实例又是何时被创建的呢?我们也许接触过下面经典的代码:

Java代码

  1. public class BaseClass     
  2. {     
  3.     public BaseClass()     
  4.     {     
  5.        System.out.println("create base");     
  6.     }     
  7. }     
  8. public class SubClass     
  9. {     
  10.     public SubClass()     
  11.     {     
  12.        System.out.println("create sub");     
  13.     }     
  14.     public static void main(String[] args)     
  15.     {     
  16.        new SubClass();     
  17.     }     
  18. }    

 

结果是先输出create base,后输出create sub。这个结果看起来和现实世界完全一致,先有老爸,再有儿子。因此我相信有很多程序员跟我一样会认为new SubClass()的过程是:实例化BaseClass->调用BaseClass构造函数初始化->实例化SubClass->调用SubClass构造函数初始化。然而非常不幸的是,这是个错误的观点。

四、奇怪的代码

以下代码是为了驳斥上面提到的错误观点。但是这种代码其实在工作中甚少出现。

Java代码

  1. public class BaseClass     
  2. {     
  3.     public BaseClass()     
  4.     
  5.     {     
  6.     
  7.        System.out.println("create base");     
  8.     
  9.        init();     
  10.     
  11.     }     
  12.     protected void init() {     
  13.     
  14.        System.out.println("do init");     
  15.     }     
  16. }     
  17.     
  18. //     
  19.     
  20. public class SubClass     
  21. {     
  22.     public SubClass()     
  23.     {     
  24.     
  25.        System.out.println("create sub");     
  26.     
  27.     }     
  28.     @Override    
  29.     
  30.     protected void init()     
  31.     
  32.     {     
  33.     
  34.        assert this!=null;     
  35.     
  36.        System.out.println("now the working class is:"+this.getClass().getSimpleName());     
  37.     
  38.        System.out.println("in SubClass");     
  39.     
  40.     }     
  41.  
  42.     public static void main(String[] args)     
  43.     
  44.     {     
  45.     
  46.        new SubClass();     
  47.     
  48.     }     
  49.     
  50. }   

 

这段代码运行的结果是先调用父类的构造函数,再调用子类的init()方法,再调用子类的构造函数。

这是一段奇妙的代码,子类的构造函数居然不是子类***个被执行的方法。我们早已习惯于通过super方便的调用父类的方法,但是好像从没这样尝试从父类调用子类的方法。

再次声明,这只是个示例。是为了与您一起探讨对象实例化的秘密。通过这个示例,我们再次印证了开头的观点:早在构造函数被调用之前,实例便已被创造。若该对象有父类,则早在父类的构造函数被调用之前,实例也已被创造。这让java显得有些不面向对象,原来老子儿子其实是一块儿出生的。

五、奇怪但危险的代码

本篇是对上篇奇怪代码的延续。但是这段代码更加具有疑惑性,理解不当将会让你出现致命失误。

请看下面代码:

Java代码

  1. public class BaseClass {     
  2.  
  3.     public BaseClass()     
  4.     
  5.     {     
  6.     
  7.        System.out.println("create base");     
  8.     
  9.        init();     
  10.     
  11.     }     
  12.  
  13.     protected void init() {     
  14.     
  15.        System.out.println("in base init");     
  16.     
  17.     }     
  18.     
  19. }     
  20.     
  21. public class SubClass extends BaseClass{     
  22.  
  23.     int i=1024;     
  24.     
  25.     String s="13 leaf";     
  26.     
  27.       
  28.     
  29.     public SubClass()     
  30.     
  31.     {     
  32.     
  33.        System.out.println("create sub");     
  34.     
  35.        init();     
  36.     
  37.     }     
  38.  
  39.     @Override    
  40.     
  41.     protected void init() {     
  42.     
  43.        assert this!=null;     
  44.     
  45.        System.out.println("now the working class is:"+this.getClass().getSimpleName());     
  46.     
  47.        System.out.println("in SubClass");     
  48.     
  49.        /////////////great line/////////////////     
  50.     
  51.        System.out.println(i);     
  52.     
  53.        System.out.println(s);     
  54.     
  55.     }     
  56.  
  57.     public static void main(String[] args) {     
  58.     
  59.        new SubClass();     
  60.     
  61.        //oh!my god!!     
  62.     
  63.     }     
  64.     
  65. }   

 

这段代码相比上一篇,只是在子类中添加了一些成员变量。而我们的目标正是集中在讨论成员变量初始化的问题上。

这段代码的执行顺序是:父类、子类实例化->调用父类构造函数->调用子类init()方法->调用子类构造函数->调用子类init()方法。最终的输出结果向我们揭示了成员变量初始化的秘密。

当父类构造函数调用子类的init()方法的时候。子类的成员变量统统是空的,这个空是指的低级初始化。(值类型为0,布尔类型为false,引用类型为null)。而当子类构造函数调用init()方法的时候,成员变量才真正被初始化。这是一个危险的讯息,那就是使用父类构造函数调用子类时存在成员变量未初始化的风险。

我们的讨论也到此为止了。再次回顾,总结一下实例何时被创建这个问题。我得出了以下结论:

本文到此便结束了。鉴于本人才疏学浅,若是专业术语有错误,或是哪里讲的不对,也欢迎各位高手拍砖。

附上第五篇中SubClass的部分字节码,方便大家深入理解:

Java代码

  1. public SubClass();     
  2.     
  3.       aload_0 [this//aload_0是啥?     
  4.     
  5.       invokespecial ques.BaseClass() [26//调用父类构造函数     
  6.     
  7.       aload_0 [this]     
  8.     
  9.      sipush 1024 //初始化i成员变量     
  10.     
  11.      putfield ques.SubClass.i : int [28]     
  12.     
  13.       aload_0 [this]     
  14.     
  15.      ldc "13 leaf"> [30//初始化s成员变量     
  16.     
  17.      putfield ques.SubClass.s : java.lang.String [32]     
  18.     
  19.      getstatic java.lang.System.out : java.io.PrintStream [34]     
  20.     
  21.      ldc "create sub"> [40]     
  22.     
  23.      invokevirtual java.io.PrintStream.println(java.lang.String) : void [42]     
  24.     
  25.      aload_0 [this]     
  26.     
  27.     invokevirtual ques.SubClass.init() : void [48//调用init     
  28.     
  29.      return    

【编辑推荐】

  1. Java序列化的机制和原理
  2. Java Socket通信的序列化和反序列化代码介绍
  3. Java输入数据流详解
  4. Java语言深入 文件和流
  5. Java对象序列化
责任编辑:金贺 来源: ITEYE博客
相关推荐

2011-04-15 17:07:13

Java

2024-05-29 08:46:19

2010-10-08 10:52:36

JavaScript对

2021-08-10 07:27:42

Python引用计数法

2020-02-25 16:00:28

JavaScript数据技术

2024-04-01 08:23:20

代码Javajavascript

2010-09-10 15:37:44

SQL函数

2010-11-19 09:48:48

ORACLE创建实例

2009-08-20 17:22:45

C# FileSyst

2019-09-09 16:19:42

智慧快递箱末端投递末端网点

2010-11-19 09:30:01

Oracle创建实例

2010-11-19 10:01:08

Oracle创建实例

2010-08-26 11:01:05

DHCP服务器

2024-08-28 10:04:17

2020-10-21 14:54:02

RustGolang开发

2020-04-24 16:05:06

Javascript代码前端

2015-07-30 09:46:42

开源项目

2010-06-17 18:57:11

UML对象关系

2010-04-20 15:47:25

Oracle实例

2022-01-24 16:56:47

数字卢布数字钱包货币
点赞
收藏

51CTO技术栈公众号