根据《On Java 8》: 第六章初始化和清理-构造器初始化 总结
一、对象中数据(变量、方法、代码块)初始化的顺序(总结)
顺序:静态变量(静态代码块)> 非静态变量(非静态代码块)> 静态方法 > 非静态方法
无论创建多少个对象,静态数据都只占用一份存储区域,只初始化一次。
1、类中的变量:
- 类中的变量在类中任何方法(包括构造器)被调用之前得到初始化。【即,变量初始化在所有方法之前】
- 变量定义的顺序决定了它们的初始化顺序
- 静态变量的初始化优先于非静态变量(不考虑定义顺序)
2、类中的方法:
- 静态方法的初始化优先于非静态方法
- 构造器实际上是静态方法
3、对象:
- 静态对象的初始化优先于非静态对象(前提是:之前静态对象没有初始化,因为静态数据只会初始化一次。)
对于静态数据:
- 无论创建多少个对象,静态数据都只占用一份存储区域。
- 当加载完一个类对应的字节码(.class)文件后,有关静态初始化的所有动作都会执行。因此,静态初始化只会在首次加载 Class 对象时初始化一次。
静态代码块与非静态代码块:
- 静态代码块与其他静态初始化动作一样,仅执行一次
- 非静态代码块(不加static),在每一次创建对象时都会执行,创建一次执行一次。(可以使用它保证某些操作一定会发生)
- 实例初始化子句是在构造器之前执行的。(静态代码块也是,并且静态代码块相当于静态变量,他们之间的初始化顺序如同变量之间的初始化顺序)
- 就可以认为,在初始化顺序方面:可以把静态代码块当成静态变量,把非静态代码块当成普通变量来看待。
二、原文:构造器初始化
可以用构造器进行初始化,这种方式给了你更大的灵活性,因为你可以在运行时调用方法进行初始化。但是,这无法阻止自动初始化的进行,他会在构造器被调用之前发生。因此,如果使用如下代码:
// housekeeping/Counter.java
public class Counter {
int i;
Counter() {
i = 7;
}
//...
}
i 首先会被初始化为 0,然后变为 7。对于所有的基本类型和引用,包括在定义时已明确指定初值的变量,这种情况都是成立的。因此,编译器不会强制你一定要在构造器的某个地方或在使用它们之前初始化元素——初始化早已得到了保证。
初始化的顺序
(变量的初始化顺序:)在类中变量定义的顺序决定了它们初始化的顺序。即使变量定义散布在方法定义之间,它们仍会在任何方法(包括构造器)被调用之前得到初始化。例如:
// housekeeping/OrderOfInitialization.java
// Demonstrates initialization order
// When the constructor is called to create a
// Window object, you'll see a message:
class Window {
Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
class House {
Window w1 = new Window(1); // Before constructor
House() {
// Show that we're in the constructor:
System.out.println("House()");
w3 = new Window(33); // Reinitialize w3
}
Window w2 = new Window(2); // After constructor
void f() {
System.out.println("f()");
}
Window w3 = new Window(3); // At end
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f(); // Shows that construction is done
}
}
//输出:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
在 House 类中,故意把几个 Window 对象的定义散布在各处,以证明它们全都会在调用构造器或其他方法之前得到初始化。此外,w3 在构造器中被再次赋值。
由输出可见,引用 w3 被初始化了两次:一次在调用构造器前,一次在构造器调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。这乍一看可能觉得效率不高,但保证了正确的初始化。试想,如果定义了一个重载构造器,在其中没有初始化 w3,同时在定义 w3 时没有赋予初值,那会产生怎样的后果呢?(只是一个引用)
静态数据的初始化
无论创建多少个对象,静态数据都只占用一份存储区域。static 关键字不能应用于局部变量,所以只能作用于属性(字段、域)。如果一个字段是静态的基本类型,你没有初始化它,那么它就会获得基本类型的标准初值。如果它是对象引用,那么它的默认初值就是 null。
如果在定义时进行初始化,那么静态变量看起来就跟非静态变量一样。
下面例子显示了静态存储区是何时初始化的:
// housekeeping/StaticInitialization.java
// Specifying initial values in a class definition
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f1(int marker) {
System.out.println("f1(" + marker + ")");
} }
class Table {
static Bowl bowl1 = new Bowl(1);
Table() {
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard {
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
System.out.println("main creating new Cupboard()");
new Cupboard();
System.out.println("main creating new Cupboard()");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
//输出:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
Bowl 类展示类的创建,而 Table 和 Cupboard 在它们的类定义中包含 Bowl 类型的静态数据成员。注意,在静态数据成员定义之前,Cupboard 类中先定义了一个Bowl 类型的非静态成员 b3。(结果还是先显示的Bowl 类型的静态数据成员:bowl4、bowl5)。
由输出可见,静态初始化只有在必要时刻才会进行。如果不创建 Table 对象,也不引用 Table.bowl1 或 Table.bowl2,那么静态的 Bowl 类对象 bowl1 和 bowl2 永远不会被创建。只有在第一个 Table 对象被创建(或被访问)时,它们才会被初始化。此后,静态对象不会再次被初始化。
初始化的顺序先是静态对象(如果它们之前没有被初始化的话),然后是非静态对象,从输出中可以看出。要执行 main() 方法,必须加载 StaticInitialization 类,它的静态属性 table 和 cupboard 随后被初始化,这会导致它们对应的类也被加载,而由于它们都包含静态的 Bowl 对象,所以 Bowl 类也会被加载。因此,在这个特殊的程序中,所有的类都会在 main() 方法之前被加载。实际情况通常并非如此,因为在典型的程序中,不会像本例中所示的那样,将所有事物通过 static 联系起来。
概括一下创建对象的过程,假设有个名为 Dog 的类:
-
即使没有显式地使用 static 关键字,构造器实际上也是静态方法。所以,当首次创建 Dog 类型的对象或是首次访问 Dog 类的静态方法或属性时,Java 解释器必须在类路径中查找,以定位 Dog.class。
-
当加载完 Dog.class 后(后面会学到,这将创建一个 Class 对象),有关静态初始化的所有动作都会执行。因此,静态初始化只会在首次加载 Class 对象时初始化一次。
-
当用 new Dog() 创建对象时,首先会在堆上为 Dog 对象分配足够的存储空间。
-
分配的存储空间首先会被清零,即会将 Dog 对象中的所有基本类型数据设置为默认值(数字会被置为 0,布尔型和字符型也相同),引用被置为 null。
-
执行所有出现在字段定义处的初始化动作。
-
执行构造器。你将会在 “复用” 这一章看到,这可能会牵涉到很多动作,尤其当涉及继承的时候。
显式的静态初始化(静态代码块)
你可以将一组静态初始化动作放在类里面一个特殊的 “静态子句”(有时叫做静态块)中。像下面这样:
// housekeeping/Spoon.java
public class Spoon {
static int i;
static {
i = 47;
}
}
这看起来像个方法,但实际上它只是一段跟在 static 关键字后面的代码块。与其他静态初始化动作一样,这段代码仅执行一次:当首次创建这个类的对象或首次访问这个类的静态成员(甚至不需要创建该类的对象)时。例如:
// housekeeping/ExplicitStatic.java
// Explicit static initialization with "static" clause
class Cup {
Cup(int marker) {
System.out.println("Cup(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
} }
class Cups {
static Cup cup1;
static Cup cup2;
static {
cup1 = new Cup(1);
cup2 = new Cup(2);
}
Cups() {
System.out.println("Cups()");
} }
public class ExplicitStatic {
public static void main(String[] args) {
System.out.println("Inside main()");
Cups.cup1.f(99); // [1]
}
// static Cups cups1 = new Cups(); // [2]
// static Cups cups2 = new Cups(); // [2]
}
//输出:
Inside main
Cup(1)
Cup(2)
f(99)
无论是通过标为 [1] 的行访问静态的 cup1 对象,还是把标为 [1] 的行去掉,让它去运行标为 [2] 的那行代码(去掉 [2] 的注释),Cups 的静态初始化动作都会执行。如果同时注释 [1] 和 [2] 处,那么 Cups 的静态初始化就不会进行。此外,把标为 [2] 处的注释都去掉还是只去掉一个,静态初始化只会执行一次。
非静态实例初始化(非静态代码块)
Java 提供了被称为实例初始化的类似语法,用来初始化每个对象的非静态变量,例如:
// housekeeping/Mugs.java
// Instance initialization
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{ // [1]
mug1 = new Mug(1);
mug2 = new Mug(2);
System.out.println("mug1 & mug2 initialized");
}
Mugs() {
System.out.println("Mugs()");
}
Mugs(int i) {
System.out.println("Mugs(int)");
}
public static void main(String[] args) {
System.out.println("Inside main()");
new Mugs();
System.out.println("new Mugs() completed");
new Mugs(1);
System.out.println("new Mugs(1) completed");
}
}
//输出:
Inside main
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
看起来它很像静态代码块,只不过少了 static 关键字。这种语法对于支持 “匿名内部类”(参见 “内部类” 一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,实例初始化子句是在两个构造器之前执行的。
本文详细介绍了Java中对象的初始化过程,包括静态与非静态变量、方法及代码块的初始化顺序,探讨了构造器初始化的特点,并展示了如何使用静态与非静态初始化块。
991

被折叠的 条评论
为什么被折叠?



