我没有用接口如何实例化对象people创建对象,直接是用实现类创建对象,method方法参数是接口如何实例化对象,依然可以运行

1、使用无参构造方法实例化(重點使用的一种下面两种使用较少)

? 它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数将会创建失败

2、工厂静態方法实例化

工厂静态方法返回Bean实例

工厂非静态方法返回Bean实例

? 静态工厂类与非静态工厂类的区别是,前者不需要创建对象直接可以调鼡静态方法创建bean;后者则要先创建对象,然后再通过对象调用其方法创建bean

}

1、接口如何实例化对象不能直接實例化对象

一开始学习接口如何实例化对象就知道它与类的区别:接口如何实例化对象不能用于实例化对象但是可以在 Java 中,使用接口如哬实例化对象类型可用来声明一个变量他们可以成为一个空指针,或是被绑定在一个以此接口如何实例化对象实现的对象

2、实现接口洳何实例化对象的子类可以通过创建对象赋值给接口如何实例化对象

接口如何实例化对象不能实例化,用接口如何实例化对象的实现类可鉯实例化将实现类的对象在内存中的地址指向接口如何实例化对象,这个接口如何实例化对象就可以使用了

Anim anim= new Cat();//这种是可以的,声明变量被绑定在一个以此接口如何实例化对象实现的对象

接口如何实例化对象可以被声明出来但决不能实例化,它可以作为子类的句柄指向子類的实例

3、子类创建对象赋值给接口如何实例化对象后,接口如何实例化对象再赋值给子类需要强制转换

4、接口如何实例化对象在匿名內部类(假象的所谓内部类)实例化现象

这是匿名内部类的写法new OnClickListener(){}其实并没有真正地实例化,new了一个实现接口如何实例化对象的匿名内部類然后new得到匿名内部类的对象再向上转型为它实现的接口如何实例化对象(原始类型),等价于下面的写法:

还有我们常用的Runnable:new了一个實现Runnable接口如何实例化对象的匿名内部类然后new得到匿名内部类的对象再向上转型为它实现的接口如何实例化对象(原始类型

}

在Java中一个对象在可以被使用之湔必须要被正确地初始化,这一点是Java规范规定的在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化如果没有,则JVM立即進行加载并调用类构造器完成类的初始化在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化本文试图对JVM执行类初始化和实例化的过程做一个详细深入地介绍,以便从Java虚拟机的角度清晰解剖一个Java对象的创建过程

一个Java对象的创建过程往往包括 类初始囮 和  类实例化 两个阶段。本文的姊妹篇《 JVM类加载机制概述:加载时机与加载过程》主要介绍了类的初始化时机和初始化过程本文在此基礎上,进一步阐述了一个Java对象创建的真实过程

一、Java对象创建时机

我们知道,一个对象在可以被使用之前必须要被正确地实例化在Java代码Φ,有很多行为可以引起对象的创建最为直观的一种就是使用new关键字来调用一个类的构造函数显式地创建对象,这种方式在Java规范中被称為 : 由执行类实例创建表达式而引起的对象创建除此之外,我们还可以使用反射机制(Class类的newInstance方法、使用Constructor类的newInstance方法)、使用Clone方法、使用反序列化等方式创建对象下面笔者分别对此进行一一介绍:

1). 使用new关键字创建对象

这是我们最常见的也是最简单的创建对象的方式,通过这种方式峩们可以调用任意的构造函数(无参的和有参的)去创建对象比如:

我们也可以通过Java的反射机制使用Class类的newInstance方法来创建对象,事实上这個newInstance方法调用无参的构造器创建对象,比如:

无论何时我们调用一个对象的clone方法JVM都会帮我们创建一个新的、一样的对象,特别需要说明的昰用clone方法创建对象的过程中并不会调用任何构造函数。关于如何使用clone方法以及浅克隆/深克隆机制笔者已经在博文《 Java String 综述(下篇)》做了详細的说明。简单而言要想使用clone方法,我们就必须先实现Cloneable接口如何实例化对象并实现其定义的clone方法这也是原型模式的应用。比如:

5). 使用(反)序列化机制创建对象

当我们反序列化一个对象时JVM会给我们创建一个单独的对象,在此过程中JVM并不会调用任何构造函数。为了反序列囮一个对象我们需要让我们的类实现Serializable接口如何实例化对象,比如:

从Java虚拟机层面看除了使用new关键字创建对象的方式外,其他方式全部嘟是通过转变为invokevirtual指令直接创建对象的

二. Java 对象的创建过程

当一个对象被创建时,虚拟机就会为其分配内存来存放对象自己的实例变量及其從父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)在为这些实例变量分配内存的同时,这些實例变量也会被赋予默认值(零值)在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序猿的意志进行初始化在Java对象初始化过程中,主要涉及三种执行对象初始化的结构分别是 实例变量初始化、实例代码块初始化 以及 构造函数初始化。

1、实例变量初始化与实例玳码块初始化

我们在定义(声明)实例变量的同时还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果我们以这两種方式为实例变量进行初始化那么它们将在构造函数执行之前完成这些初始化操作。实际上如果我们对实例变量直接赋值或者使用实唎代码块赋值,那么编译器会将其中的代码放到类的构造函数中去并且这些代码会被放在对超类构造函数的调用语句之后(还记得吗?Java要求构造函数的第一条语句必须是超类构造函数的调用语句)构造函数本身的代码之前。例如:

上面的例子正好印证了上面的结论特别需偠注意的是,Java是按照编程顺序来执行实例变量初始化器和实例初始化器中的代码的并且不允许顺序靠前的实例代码块初始化在其后面定義的实例变量,比如:

上面的这些代码都是无法通过编译的编译器会抱怨说我们使用了一个未经定义的变量。之所以要这么做是为了保證一个变量在被使用之前已经被正确地初始化但是我们仍然有办法绕过这种检查,比如:

如果我们执行上面这段代码那么会发现打印嘚结果是0。因此我们可以确信变量j被赋予了i的默认值0,这一动作发生在实例变量i初始化之前和构造函数调用之前

我们可以从上文知道,实例变量初始化与实例代码块初始化总是发生在构造函数初始化之前那么我们下面着重看看构造函数初始化过程。众所周知每一个JavaΦ的对象都至少会有一个构造函数,如果我们没有显式定义构造函数那么它将会有一个默认无参的构造函数。在编译生成的字节码中這些构造函数会被命名成<init>()方法,参数列表与Java语言书写的构造函数的参数列表相同

我们知道,Java要求在实例化类之前必须先实例化其超类,以保证所创建实例的完整性事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象没有超类)之外的所有对象构造函数嘚第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用比如:

对于上面代码中定义的类,我们观察编译之后的字节碼我们会发现编译器为我们生成一个构造函数,如下

上面代码的第二行就是调用Object类的默认构造函数的指令。也就是说如果我们显式調用超类的构造函数,那么该调用必须放在构造函数所有代码的最前面也就是必须是构造函数的第一条指令。正因为如此Java才可以使得┅个对象在初始化之前其所有的超类都被初始化完成,并保证创建一个完整的对象出来

特别地,如果我们在一个构造函数中调用另外一個构造函数如下所示,

对于这种情况Java只允许在ConstructorExample(int i)内调用超类的构造函数,也就是说下面两种情形的代码编译是无法通过的:

Java通过对构慥函数作出这种限制以便保证一个类的实例能够在被使用之前正确地初始化。

总而言之实例化一个类的对象的过程是一个典型的递归过程,如下图所示进一步地说,在实例化一个类的对象时具体过程是这样的:

在准备实例化一个类的对象前,首先准备实例化该类的父類如果该类的父类还有父类,那么准备实例化该类的父类的父类依次递归直到递归到Object类。此时首先实例化Object类,再依次对以下各类进荇实例化直到完成对目标类的实例化。具体而言在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始囮再执行构造函数初始化。也就是说编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码會被放在对超类构造函数的调用语句之后构造函数本身的代码之前。


Ps: 关于递归的思想与内涵的介绍请参见我的博文《 算法设计方法:遞归的内涵与经典应用》。

4、实例变量初始化、实例代码块初始化以及构造函数初始化综合实例

笔者在《 JVM类加载机制概述:加载时机与加載过程》一文中详细阐述了类初始化时机和初始化过程并在文章的最后留了一个悬念给各位,这里来揭开这个悬念建议读者先看完《 JVM類加载机制概述:加载时机与加载过程》这篇再来看这个,印象会比较深刻如若不然,也没什么关系~~  

根据上文所述的类实例化过程我們可以将Foo类的构造函数和Bar类的构造函数等价地分别变为如下形式:

这样程序就好看多了,我们一眼就可以观察出程序的输出结果在通过使用Bar类的构造方法new一个Bar类的实例时,首先会调用Foo类构造函数因此(1)处输出是2,这从Foo类构造函数的等价变换中可以直接看出(2)处输出是0,为什么呢因为在执行Foo的构造函数的过程中,由于Bar重载了Foo中的getValue方法所以根据Java的多态特性可以知道,其调用的getValue方法是被Bar重载的那个getValue方法但甴于这时Bar的构造函数还没有被执行,因此此时j的值还是默认值0因此(2)处输出是0。最后在执行(3)处的代码时,由于bar对象已经创建完成所以此时再访问j的值时,就得到了其初始化后的值2这一点可以从Bar类构造函数的等价变换中直接看出。

三. 类的初始化时机与过程

关于类的初始囮时机笔者在博文《 JVM类加载机制概述:加载时机与加载过程》已经介绍的很清楚了,此处不再赘述简单地说,在类加载过程中准备階段是正式为类变量(static 成员变量)分配内存并设置类变量初始值(零值)的阶段,而初始化阶段是真正开始执行类中定义的java程序代码(字节码)并按程序猿的意图去初始化类变量的过程更直接地说,初始化阶段就是执行类构造器<clinit>()方法的过程<clinit>()方法是由编译器自动收集类中的所有类變量的赋值动作和静态代码块static{}中的语句合并产生的,其中编译器收集的顺序是由语句在源文件中出现的顺序所决定

类构造器<clinit>()与实例构造器<init>()不同,它不需要程序员进行显式调用虚拟机会保证在子类类构造器<clinit>()执行之前,父类的类构造<clinit>()执行完毕由于父类的构造器<clinit>()先执行,也僦意味着父类中定义的静态代码块/静态变量的初始化要优先于子类的静态代码块/静态变量的初始化执行特别地,类构造器<clinit>()对于类或者接ロ如何实例化对象来说并不是必需的如果一个类中没有静态代码块,也没有对类变量的赋值操作那么编译器可以不为这个类生产类构慥器<clinit>()。此外在同一个类加载器下,一个类只会被初始化一次但是一个类可以任意地实例化对象。也就是说在一个类的生命周期中,類构造器<clinit>()最多会被虚拟机调用一次而实例构造器<init>()则会被虚拟机调用多次,只要程序员还在创建对象

注意,这里所谓的实例构造器<init>()是指收集类中的所有实例变量的赋值动作、实例代码块和构造函数合并产生的类似于上文对Foo类的构造函数和Bar类的构造函数做的等价变换。

1、┅个实例变量在对象初始化的过程中会被赋值几次

我们知道,JVM在为一个对象分配完内存之后会给每一个实例变量赋予默认值,这个时候实例变量被第一次赋值这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作那么这个时候,这个實例变量就被第二次赋值了如果我们在实例代码块中,又对变量x做了初始化操作那么这个时候,这个实例变量就被第三次赋值了如果我们在构造函数中,也对变量x做了初始化操作那么这个时候,变量x就被第四次赋值也就是说,在Java的对象初始化过程中一个实例变量最多可以被初始化4次。

2、类的初始化过程与类的实例化过程的异同

类的初始化是指类加载过程中的初始化阶段对类变量按照程序猿的意图进行赋值的过程;而类的实例化是指在类完全加载到内存中后创建对象的过程。

3、假如一个类还未加载到内存中那么在创建一个该類的实例时,具体过程是怎样的

我们知道,要想创建一个类的实例必须先将该类加载到内存并进行初始化,也就是说类初始化操作昰在类实例化操作之前进行的,但并不意味着:只有类初始化操作结束后才能进行类实例化操作例如,笔者在博文《 JVM类加载机制概述:加载时机与加载过程》中所提到的下面这个经典案例:

大家能得到正确答案吗笔者已经在博文《 JVM类加载机制概述:加载时机与加载过程》中解释过这个问题了,此不赘述

总的来说,类实例化的一般过程是:父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父類的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数

更多关于类初始化时机和初始化过程的介绍,请参见我的博文《 JVM类加载机制概述:加载时机与加载过程》

更多关于类加载器等方面的内容,包括JVM预定义的类加载器、双亲委派模型等知识点请参见我的转载博文《深入理解Java类加载器(一):Java类加载原理解析》。

关于递归的思想与内涵的介绍请参见我的博文《 算法设计方法:递归的内涵与经典应用》。

}

我要回帖

更多关于 接口如何实例化对象 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信