Java object Initialization (class Instantiation) | 对象的初始化(即类的实例化)

[b]类实例即对象。对象的初始化过程也就是类的实例化过程。[/b]

inside JVM作者Bill Venners关于对象初始化的文章很棒:
[b]Object Initialization in Java:
[url]http://www.artima.com/designtechniques/initializationP.html[/url]
[/b]

一些名词概念:
[color=red][b]Class Instance Creation Expressions:[/b][/color]
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.9[/url]
用来创建类实例对象。
格式简单表述: [b]new nameOfClass(argument list)[/b][/quote]
[color=red][b]Explicit Constructor Invocations:[/b][/color]
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.8.7.1[/url][quote]指在一个构造方法中显式调用其他的构造方法。分两种:
1 Alternate constructor invocations - 通过 this(args)调用本类其他重载的构造方法
2 Superclass constructor invocations - 通过 super(args)调用(直接)基类的构造方法。[/quote][color=red][b]Instance initialization methods : <init>[/b][/color]
[url]http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.9[/url]
在编译阶段,编译器为类的每个构造方法创建一个名为<init>的方法。该<init>方法返回值为void,参数与对应的构造方法一致。<init>方法称为Instance initialization methods(实例初始化方法)。从字节码文件(.class)的角度考虑,<init>方法就是对象初始化的入口。
需要注意的是,<init>方法并不简单等同于其对应的构造方法。比如如果是在类内构造方法链的最后一个构造方法对应的<init>方法里编译器会将initializers (这里指Instance Initializers & Instance Variable Initializers)的代码添加入该<init>方法,并对父类构造方法对应的<init>方法做显式的调用。

[color=red][b]Instance Variable Initializers[/b][/color]
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.2[/url]
给个例子就明白了:

class CoffeeCup {
private int innerCoffee = 355; // "= 355" is an Instance Variable Initializers
// no constructor here
// ...
}
[color=red][b]Instance Initializers[/b][/color]
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.6[/url]
即实例初始化块(Instance Initialization Blocks)、非静态初始化块(Non-static Initialization Blocks)。
实例初始化块中不能有return语句。 [quote][url]http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html[/url] 里有这样一句话:
The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.
(java在编译的时候,会将初始化块拷贝到每个构造方法里。所以初始化块可以用来为类的多个构造方法提供共享的代码块)。
这句话是错误的。编译时不是将Instance Initializers拷贝到每个构造函数中!假设成立,那在子类中存在Constructor Chaining时,岂不是Instance Initializers要被多次执行!详见:
[url]http://stackoverflow.com/questions/12253712/java-explicit-constructor-invocation-instance-initializer[/url][/quote]静态初始化块(Static Initialization Blocks)见:[url]http://wuaner.iteye.com/blog/1669127[/url]

[b][color=red]Instance Variable Initializers 和Instance Initializers在对象初始化过程中的行为表现是一致的。如果我们统称它们为Initializers:
1 在一个对象的初始化过程中,每个Initializers只会被执行一次;
2 执行时机:在super(args)调用结束后立即执行。着重强调"立即"二字,意味着其执行时机是先于子类构造方法链的。
3 执行顺序:按Initializers在代码中出现的顺序依次执行。[/color][/b][quote]JLS 8.8.7.1. Explicit Constructor Invocations的最后,找到这段描述可真不容易(Let [b]C be the class being instantiated[/b], and let S be the direct superclass of C.):
[b][color=red]Finally, if the superclass constructor invocation statement completes normally, then all instance variable initializers of C and all instance initializers of C are executed. If an instance initializer or instance variable initializer I textually precedes another instance initializer or instance variable initializer J, then I is executed before J.[/color][/b]
Execution of instance variable initializers and instance initializers is performed regardless of whether the superclass constructor invocation actually appears as an explicit constructor invocation statement or is provided automatically. (An alternate constructor invocation does not perform this additional implicit execution.)[/quote]


[b]显式的对象初始化[/b]:通过Class Instance Creation Expressions创建类实例。通俗的讲就是通过new关键字创建类实例。
[b]隐式的对象初始化[/b]:以下情况下会存在隐式的类实例创建:[quote]Loading of a class or interface that contains a String literal (§3.10.5) may create a new String object to represent that literal. (This might not occur if the same String has previously been interned (§3.10.5).) - 当创建一个包含String字面量的类或接口,且该String字面量未被interned的时候,会(在Method area)中创建一个该String字面量对应的String对象(参考String:[url]http://wuaner.iteye.com/blog/567970[/url])
Execution of an operation that causes boxing conversion (§5.1.7). Boxing conversion may create a new object of a wrapper class associated with one of the primitive types. - 当执行的操作会引发自动装箱时,会为基本类型创建其对应的包装类对象 (自动装箱:[url]http://wuaner.iteye.com/blog/1668172[/url])。
Execution of a string concatenation operator (§15.18.1) that is not part of a constant expression (§15.28) sometimes creates a new String object to represent the result. String concatenation operators may also create temporary wrapper objects for a value of a primitive type.[/quote]上述四种情况,都会调用相应类的相应构造方法来做对象的初始化。
以指定构造方法为入口,一个对象的初始化过程分为以下 5 steps(这是个递归的过程):[quote]Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:

1 Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.

2 If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.

3 This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.

4 Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.

5 Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.
待译。。。[/quote]有三种机制确保对象被正确的初始化:[list][*]constructors
[*]instance variable initializers
[*]instance initializers[/list]


[b]构造方法 Constructors[/b] :
[b]1[/b] 用来创建类的实例对象,与类同名且[b]没有返回值[/b]。
[b]2[/b] 若类中未定义任何构造方法,java默认提供一个方法体为空、没有throws子句的无参构造方法。该默认构造方法拥有和类一样的访问修饰符。这种机制保证了java中的每个类都有至少一个构造方法;若类中定义了构造方法,则不再提供(你定义的肯定也可以是无参的,此种情况下你可以理解成是对默认提供的无参构造方法的覆盖)。
[b]3[/b] 使用super(args)或this(args),可以在构造方法中对(直接)父类或本类重载构造方法做显式构造方法调用(概念定义见上面);这种调用必须是在当前构造方法的第一行(at begin of a Constructor body)(因为只能在第一行调,所以只能调一次,因为如果再调另一个构造方法,那就不是第一行了)。
使用this(args)调当前类的重载构造方法时需要注意:不能存在形如下的递归调用环:[quote]

//一 自己调自己,产生单个构造方法的递归调用环路
Child() {
this();
System.out.println("Child no-argument Constructor");

}

//二 3个构造方法组成的递归调用环路
Child() {
this(2);
System.out.println("Child no-argument Constructor");

}
Child(int i) {
this(10, i);
System.out.println("Child 1-argument Constructor");
}
Child(int i, int j) {
this();
System.out.println("Child 2-argument Constructor");
}
[/quote]
[b]4[/b] 如果构造方法(包括java默认提供的无参构造方法,虽然无参构造方法内肯定没有显式构造方法调用)内没有显式构造方法调用,则构造方法的第一行隐式调用(直接)父类的无参构造方法super();反之,若构造方法内存在显式构造方法调用,则不存在该隐式调用。
[b]5[/b] 构造方法不能被继承,因此不能重写。
[b]6[/b] 构造方法可以重载。
[b]从以上基本点我们可以得出以下一些结论:[/b][quote] 1 除了Object类的的那一个无参构造方法外,Java世界中所有类的所有构造方法都有且只有一次对其他构造方法(或父类的或本类的)的调用(或显式或隐式);
2 在一次子类对象实例化的过程中,子类的构造方法对父类构造方法的调用肯定有且只有一次(或显式或隐式)。(这里肯定排除Object类,因为它没有父类、不是任何类的子类)
3 对“首行构造方法调用(或显式或隐式)”调用的为父构造方法的子类构造方法来说:因为子类对父类构造方法的隐式调用调用的是其无参构造方法,所以当父类中没有无参构造方法时,这种调用就必须是显式的;或者理解为:如果子类构造方法中既没有显式调用基类构造方法,而基类中又没有无参的构造方法,则编译出错。
4 构造方法的调用组成一条构造方法链 Constructor chaining。如下:

class Parent {
Parent() {
System.out.println("Parent non-argument Constructor");
}
}

class Child extends Parent {
{
System.out.println("Child Instance Initialization Block");
}
Child() {
this(1);
System.out.println("Child no-argument Constructor");

}
Child(int i) {
this(10, i);
System.out.println("Child 1-argument Constructor");
}
Child(int i, int j) {
System.out.println("Child 2-argument Constructor");
}
Child(String s) {
}
}
其对应的Constructor Chaining:[img]http://dl.iteye.com/upload/attachment/0073/3071/a7f06fd7-ebc6-3b09-97e4-616f8ad6330c.png[/img][/quote]Srcs:
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.8[/url]
[url]http://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html[/url]


[b]this & super:[/b]
[b]this[/b]
表示当前对象的引用,只能用在实例初始化块(Instance Initializer)、非静态方法(instance method)和构造方法中。有两个用途:
一 [color=red][b]this.[/b][/color]的方式,访问当前对象的成员members(static fields/non-static fiels/class method/instance method)。一个常见的使用场景是:当实例方法(或构造方法)参数与实例变量同名,造成实例变量被遮蔽时,实例方法对实例变量的访问必须通过 [b][color=red]this.[/color][/b] :

int i;

//必须使用 this.
void m(int i) {
this.i = i;
}
二 用来做显式的构造方法调用(explicit constructor invocation):类中存在多个重载的构造方法时,在某一构造方法中使用[color=red][b]this(arguments)[/b][/color]的方式调用本类其他(一定是其他的!不能自己调自己)的构造方法:

public class ThisSuperTest {
private int i;

public ThisSuperTest() {
this(11);
}

public ThisSuperTest(int i) {
this.i = i;
}

public static void main(String[] args) {
ThisSuperTest o = new ThisSuperTest();
System.out.println(o.i); //11
}
}
因为通过这样的方式可以显式调用构造方法,那到底创建了本类的几个对象那?答案还是一个,输出为11就说明了这一点。所以可以得出一个结论:[b]一个类构造方法的被调用次数不等同于该类对象的创建个数[/b]。
Srcs:
JLS 15.8.3. this [url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.8.3[/url]
Java Tutorials - Using the this Keyword:[url]http://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html[/url]
[b]super[/b]
super表示什么那?这个不好说,你不能简单的说它是父类对象的引用,因为如果父类是抽象的话,你可以通过super调用父抽象类的非抽象方法,但父抽象类肯定没有对象实例存在。给super下定义很难,只有从其用途上掌握它。同this一样,只能用在实例初始化块(Instance Initializer)、非静态方法(instance method)和构造方法中。有两个用途:
一 通过 [b][color=red]super.[/color][/b] 的方式,访问层级继承树中上游的类的成员members(static fields/non-static fiels/class method/instance method)。最常见的使用场景是当子类重写父类方法时,使用 super. 调用父类的被重写方法。
class Super {

String instanceStr = "superInstanceStr";
static String staticStr;

static {
staticStr = "supberStaticStr";
}

void instanceMethod() {
System.out.println("Super instanceMethod() ");
}

static void staticMethod() {
System.out.println("Super staticMethod() ");
}
}

public class Sub extends Super {

String instanceStr = "subInstanceStr";

void m() {
System.out.println(super.instanceStr);
System.out.println(super.staticStr);//warning
super.staticMethod();
super.instanceMethod(); //调用父类的被重写方法
//间接基类Object的成员也同样可以通过super调
System.out.println(super.getClass());
}

@Override
void instanceMethod() {
System.out.println("Sub instanceMethod() ");
}

public static void main(String[] args) {
new Sub().m();
}
}
二 用来做显式的构造方法调用(explicit constructor invocation):在子类构造方法中,通过 [b][color=red]super(args)[/color][/b] 的方式,显式调用基类的构造方法:
class Father {
Father(String str) {

}
}

class Son extends Father {
Son() {
super("aaa");
}
}
Srcs:
Java Toturials - Using the Keyword super:
[url]http://docs.oracle.com/javase/tutorial/java/IandI/super.html[/url]
JLS 15.11.2. Accessing Superclass Members using super:
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.11.2[/url]


示例代码:

public class ObjectInitializeTest {
public static void main(String[] args) throws Exception {
Parent p = new Parent();
System.out.println();
Child c = new Child();
System.out.println();

//InitialBlockTest cc = new InitialBlockTest(); //StackOverflowError
}
}

class Parent {
{
System.out.println("Parent Instance Initializers 1");
}
static {
System.out.println("Parent Static Initializers 1");
}
{
System.out.println("Parent Instance Initializers 2");
}
static {
System.out.println("Parent Static Initializers 2");
}
public Parent(){
System.out.println("Parent 0-argument constructor");
}
}

class Child extends Parent {
int i;
{
System.out.println("Child Instance Initializers 1");
}
static {
System.out.println("Child Static Initializers 1");
}
{
System.out.println("Child Instance Initializers 2");
}
static {
System.out.println("Child Static Initializers 2");
}
Child() {
this(1);
System.out.println("Child 0-argument Constructor");
}

Child(int i) {
this(10, i);
System.out.println("Child 1-argument Constructor");
}

Child(int i, int j) {
this.i = 1;
System.out.println("Child 2-argument Constructor");
}

Child(String s) {
i = 2;
}
}

//StackOverflowError
class InitialBlockTest {
{
System.out.println("InitialBlockTest - Non-static Instance Initializers ");
new InitialBlockTest();
}
static {
System.out.println("InitialBlockTest - Static Initializers ");
}
InitialBlockTest() {
System.out.println("InitialBlockTest - Constructor");
}
}
输出:[quote]Parent Static Initializers 1
Parent Static Initializers 2
Parent Instance Initializers 1
Parent Instance Initializers 2
Parent 0-argument constructor

Child Static Initializers 1
Child Static Initializers 2
Parent Instance Initializers 1
Parent Instance Initializers 2
Parent 0-argument constructor
Child Instance Initializers 1
Child Instance Initializers 2
Child 2-argument Constructor
Child 1-argument Constructor
Child 0-argument Constructor[/quote]


Sources:
[b]JLS 12.5. Creation of New Class Instances:
[url]http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.5[/url]
Java Tutorials - Creating Objects:
[url]http://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html[/url][/b]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值