热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

对象实例化的顺序

对象实例化的顺序分类:java,基础日期:2012-11-08作者:ticmyhttp:www.ticmy.com?p302

对象实例化的顺序

分类:java, 基础日期:2012-11-08作者:ticmy

  http://www.ticmy.com/?p=302
 
 

创建一个对象大概有以下几种方式:
1、通过new关键字,如new Object();
2、通过某些反射类的newInstance方法,如Class#newInstance、Constructor#newInstance;
3、如果对象是Cloneable的,通过clone方法;
4、通过ObjectInputStream#readObject反序列化;
以上是通过java程序可以创建出对象的方式,jvm中还有一些隐式创建对象的地方,譬如:
1、启动一个类,main方法的参数String数组是隐式创建的,如果指定了一个或多个String对象,还要创建这些String对象;
2、读入一个class二进制数据的时候,创建一个与之对应的java.lang.Class类的对象;
3、在使用“+”进行字符串变量连接时,可能会创建StringBuffer/StringBuilder对象;
如此等等。

那么,在程序中通过new或newInstance创建对象(后面说创建对象均指这两种方式)的时候,构造方法、实例变量、父类构造方法、父类实例变量等的执行顺序是怎样的?

在创建对象的时候,首先会分配内存,此时所有实例变量均为默认值,然后做初始化实例变量、构造方法调用等操作。对于类变量,在创建对象之前,加载类的时候已经做掉了,这里为避免干扰,忽略掉。

先来一个例子:

public class Init {
     public static void main(String[] args) throws Exception {
         S s = new S();
         System.out.println(s.getV2());
     }
}
 
class P {
     private int v1 = 5 ;
     private int v2 = getV1();
     public P() throws Exception {
         System.out.println( "P" );
     }
     
     public int getV1() {
         return v1;
     }
     public int getV2() {
         return v2;
     }
}
 
class S extends P {
     private int value1 = 4 ;
     
     public int getV1() {
         return value1;
     }
     
     public S() throws Exception {
         this ( "S()" );
     }
     
     public S(String msg) throws Exception {
         System.out.println(msg);
     }
     
     public S( int v) throws Exception {
         super ();
         System.out.println( "abc" );
     }
}

执行结果如下:

P
S()
0

调用s.getV2()的值为0,是为什么呢,内部的机制是怎样的?先来了解点其它内容。
在编译代码的时候,会为每个构造方法生成一个对应的方法,方法名叫。但并不是直接将构造方法体作为方法的内容,它有这样的规则:

如果构造方法中的第一条语句是通过this调用本类的其它构造方法,如类S的第一个构造方法,其完整的构造方法体就是对应的方法的方法体。编译器不会为其添加一个super调用了。

如果构造方法中的第一条语句不是通过this调用本类的其它构造方法,会按以下内容与顺序组成方法体:
1、超类方法的调用。如果是显式的调用了超类构造方法,将会使用对应的超类方法,如果没有写,编译器会生成一个超类无参方法的调用;
2、实例变量初始化代码,按实例变量在类中出现的顺序;
3、构造方法中的其它方法体(如果第一句是super(…)调用,则不包含该句)。

如果构造方法中包含super(…)或this(…)调用,那么它们只能作为该构造方法的第一条语句,也就是说连try…catch都不可以有。因为必须为第一条语句,所以super(…)和this(…)调用是不会出现在一起的。

用javap -c S反编译获得各方法的字节码如下:

public S() throws java.lang.Exception;
Code:
    0 : aload_0
    1 : ldc           # 2                  // String S()
    3 : invokespecial # 3                  // Method "":(Ljava/lang/String;)V
    6 : return
 
public S(java.lang.String) throws java.lang.Exception;
Code:
    0 : aload_0
    1 : invokespecial # 4                  // Method P."":()V
    4 : aload_0
    5 : iconst_4
    6 : putfield      # 1                  // Field value1:I
    9 : getstatic     # 5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   12 : aload_1
   13 : invokevirtual # 6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   16 : return
 
public S( int ) throws java.lang.Exception;
Code:
    0 : aload_0
    1 : invokespecial # 4                  // Method P."":()V
    4 : aload_0
    5 : iconst_4
    6 : putfield      # 1                  // Field value1:I
    9 : getstatic     # 5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   12 : ldc           # 7                  // String abc
   14 : invokevirtual # 6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   17 : return

可以看出,在类S的第一个构造方法s()中,字节码表现出的只是调用了本类的(String)方法;从第二个构造方法S(String)其对应的字节码中可以看出,虽然没有用super(…)显式的调用超类的构造方法,编译器还是生成了偏移量为0,1的指令用于调用超类的方法,偏移量为4,5,6的指令是给变量value1赋值为4,后面即为构造方法体——唯一的一条打印语句;再看S(int)对应的方法,构造方法的第一条语句为super(…)调用,对应着中的偏移量为0,1的指令;偏移量为4,5,6的指令是为实例变量value1赋值为4;最后是打印字符串abc。

再看看P反编译后构造方法体:

public P() throws java.lang.Exception;
Code:
    0 : aload_0
    1 : invokespecial # 1                  // Method java/lang/Object."":()V
    4 : aload_0
    5 : iconst_5
    6 : putfield      # 2                  // Field v1:I
    9 : aload_0
   10 : aload_0
   11 : invokevirtual # 3                  // Method getV1:()I
   14 : putfield      # 4                  // Field v2:I
   17 : getstatic     # 5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   20 : ldc           # 6                  // String P
   22 : invokevirtual # 7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   25 : return

偏移量为0,1的指令调用Object的方法,也就是类P的超类;指令4,5,6为v1赋值为5,指令9,10,11,14是为实例变量v2获取值并赋值;剩余的是打印字符串P。

从上面几个构造方法与对应的方法分析中可以看出,它们都是符合前文所述的规则的。那么对于本文开头的例子v2的值为何为0就好分析了:
1、new S()的时候调用的是S类的()方法,它去调用了本类的(String)方法,在这个中,先去调用超类,也就是P的()方法,P的()方法又去调用P的超类Object的()方法,然后去给v1,v2赋值,v2赋值调用的是getV1(),而这个方法是一个多态方法,它会去调用new对象的那个类的getV1方法,也就是S中的getV1,S中的getV1返回的是value1的值,而此时value1赋值操作还没有做呢,所以返回的是它的默认值0.

如此,整个创建对象期间的顺序就一清二楚了。


推荐阅读
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 本文探讨了如何利用Java代码获取当前本地操作系统中正在运行的进程列表及其详细信息。通过引入必要的包和类,开发者可以轻松地实现这一功能,为系统监控和管理提供有力支持。示例代码展示了具体实现方法,适用于需要了解系统进程状态的开发人员。 ... [详细]
  • 本文总结了Java初学者需要掌握的六大核心知识点,帮助你更好地理解和应用Java编程。无论你是刚刚入门还是希望巩固基础,这些知识点都是必不可少的。 ... [详细]
  • oracle c3p0 dword 60,web_day10 dbcp c3p0 dbutils
    createdatabasemydbcharactersetutf8;alertdatabasemydbcharactersetutf8;1.自定义连接池为了不去经常创建连接和释放 ... [详细]
  • 字节流(InputStream和OutputStream),字节流读写文件,字节流的缓冲区,字节缓冲流
    字节流抽象类InputStream和OutputStream是字节流的顶级父类所有的字节输入流都继承自InputStream,所有的输出流都继承子OutputStreamInput ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • Java能否直接通过HTTP将字节流绕过HEAP写入SD卡? ... [详细]
  • 本文详细解析了客户端与服务器之间的交互过程,重点介绍了Socket通信机制。IP地址由32位的4个8位二进制数组成,分为网络地址和主机地址两部分。通过使用 `ipconfig /all` 命令,用户可以查看详细的IP配置信息。此外,文章还介绍了如何使用 `ping` 命令测试网络连通性,例如 `ping 127.0.0.1` 可以检测本机网络是否正常。这些技术细节对于理解网络通信的基本原理具有重要意义。 ... [详细]
  • 属性类 `Properties` 是 `Hashtable` 类的子类,用于存储键值对形式的数据。该类在 Java 中广泛应用于配置文件的读取与写入,支持字符串类型的键和值。通过 `Properties` 类,开发者可以方便地进行配置信息的管理,确保应用程序的灵活性和可维护性。此外,`Properties` 类还提供了加载和保存属性文件的方法,使其在实际开发中具有较高的实用价值。 ... [详细]
  • 在C#编程中,设计流畅的用户界面是一项重要的任务。本文分享了实现Fluent界面设计的技巧与方法,特别是通过编写领域特定语言(DSL)来简化字符串操作。我们探讨了如何在不使用`+`符号的情况下,通过方法链式调用来组合字符串,从而提高代码的可读性和维护性。文章还介绍了如何利用静态方法和扩展方法来实现这一目标,并提供了一些实用的示例代码。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 本文深入解析了WCF Binding模型中的绑定元素,详细介绍了信道、信道管理器、信道监听器和信道工厂的概念与作用。从对象创建的角度来看,信道管理器负责信道的生成。具体而言,客户端的信道通过信道工厂进行实例化,而服务端则通过信道监听器来接收请求。文章还探讨了这些组件之间的交互机制及其在WCF通信中的重要性。 ... [详细]
  • 本文全面解析了JavaScript中的DOM操作,并提供了详细的实践指南。DOM节点(Node)通常代表一个标签、文本或HTML属性,每个节点都具有一个nodeType属性,用于标识其类型。文章深入探讨了DOM节点的创建、查询、修改和删除等操作,结合实际案例,帮助读者更好地理解和掌握DOM编程技术。 ... [详细]
author-avatar
久居我心入我怀
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有