博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java:初始化类、变量、程序块加载探讨
阅读量:6962 次
发布时间:2019-06-27

本文共 8352 字,大约阅读时间需要 27 分钟。

1.
基本类型数据的初始值
InitialValues.java
public
 
class
 InitialValues {
    
boolean
 
t
;
    
char
 
c
;
    
byte
 
b
;
    
short
 
s
;
    
int
 
i
;
    
long
 
l
;
    
float
 
f
;
    
double
 
d
;
 
    
void
 print(String s) {
       System.
out
.println(s);
    }
 
    
void
 printInitialValues() {
       print(
"boolean  "
 + 
t
);
       print(
"char  "
 + 
c
);
       print(
"byte  "
 + 
b
);
       print(
"short  "
 + 
s
);
       print(
"int  "
 + 
i
);
       print(
"long  "
 + 
l
);
       print(
"float  "
 + 
f
);
       print(
"double  "
 + 
d
);
    }
 
    
public
 
static
 
void
 main(String[] args) {
       InitialValues iv = 
new
 InitialValues();
       iv.printInitialValues();
    }
}
 
结果:
boolean  false

char  _

byte  0

short  0

int  0

long  0

float  0.0

double  0.0
2.
变量初始化
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧在任何方法(包括构造器)被调用之前得到初始化。看下面的代码:
OrderOfInitialzation.java(
执行顺序在代码中已标出,按类标注,罗马字母标注主类中执行顺序。)
class
 Tag {
    Tag(
int
 marker) {
       System.
out
.println(
"Tag("
 + marker + 
")"
);
    
}
}
 
class
 Card {
    Tag 
t1
 = 
new
 Tag(1);
// 
Ⅰ①
 
    Card() {
       System.
out
.println(
"Card()"
);
// 
Ⅰ④
       
t3
 = 
new
 Tag(33);
// 
Ⅰ⑤
    }
 
    Tag 
t2
 = 
new
 Tag(2);
// 
Ⅰ②
 
    
void
 f() {
       System.
out
.println(
"f()"
);
// 
Ⅱ⑥
    }
 
    Tag 
t3
 = 
new
 Tag(3);
// 
Ⅰ③
}
 
public
 
class
 OrderOfInitialzation {
    
public
 
static
 
void
 main(String[] args) {
       Card t = 
new
 Card();
// 
       t.f();
// 
    }
}
 
结果:
Tag(1)

Tag(2)

Tag(3)

Card()

Tag(33)

f()
3.
静态数据初始化
看下面的代码:
StaticInitialization .java
class
 Bowl {
    Bowl(
int
 marker) {
       System.
out
.println(
"Bowl("
 + marker + 
")"
);
    }
 
    
void
 f(
int
 marker) {
       System.
out
.println(
"f("
 + marker + 
")"
);
    }
}
 
class
 Table {
    
static
 Bowl 
b1
 = 
new
 Bowl(1);
// 
Ⅰ①
 
    Table() {
       System.
out
.println(
"Table()"
);
// 
Ⅰ③
       
b2
.f(1);
// 
Ⅰ④
    }
 
    
void
 f2(
int
 marker) {
       System.
out
.println(
"f2("
 + marker + 
")"
);
    }
 
    
static
 Bowl 
b2
 = 
new
 Bowl(2);
// 
Ⅰ②
}
 
class
 Cupboard {
    Bowl 
b3
 = 
new
 Bowl(3);
// 
Ⅱ③
Ⅳ①
Ⅵ①
 
    
static
 Bowl 
b4
 = 
new
 Bowl(4);
// 
Ⅱ①
 
    Cupboard() {
       System.
out
.println(
"Cupboard"
);
// 
Ⅱ④
Ⅳ②
Ⅵ②
       
b4
.f(2);
// 
Ⅱ⑤
Ⅳ③
Ⅵ③
    }
 
    
void
 f3(
int
 marker) {
       System.
out
.println(
"f3("
 + marker + 
")"
);
    }
 
    
static
 Bowl 
b5
 = 
new
 Bowl(5);
// 
Ⅱ②
}
 
public
 
class
 StaticInitialization {
    
public
 
static
 
void
 main(String[] args) {
       System.
out
.println(
"Creating new Cupboard() in main"
);
// 
       
new
 Cupboard();
// 
       System.
out
.println(
"Creating new Cupboard() in main"
);
// 
       
new
 Cupboard();
// 
       
t2
.f2(1);
// 
       
t3
.f3(1);
// 
    }
 
    
static
 Table 
t2
 = 
new
 Table();
// 
 
    
static
 Cupboard 
t3
 = 
new
 Cupboard();
// 
}
 
结果:
Bowl(1)

Bowl(2)

Table()

f(1)

Bowl(4)

Bowl(5)

Bowl(3)

Cupboard

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard

f(2)

Creating new Cupboard() in main

Bowl(3)

Cupboard

f(2)

f2(1)

f3(1)
由输出可见,静态初始化只有在必要时刻才会进行。如果不创建Table 
对象,也不引用Table.b1
Table.b2
,那么静态的Bowl b1 
b2 
永远都不会被创建。只有在第一个Table 
对象被创建(或者第一次访问静态数据)的时候,它们才会被初始化。此后,静态对象不会再次被初始化。
初始化的顺序是先“静态”,(如果它们尚未因前面的对象创建过程而被初始化),后“非静态”。从输出结果中可以观察到这一点。
4.
静态块的初始化
Java 
允许你将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫作“静态块”)。与其他静态初始化动作一样,这段代码仅执行一次:当你首次生成这个类的一个对象时,或者首次访问属于那个类的一个静态成员时(即便从未生成过那个类的对象)。看下面的代码:
class
 Cup {
    Cup(
int
 marker) {
       System.
out
.println(
"Cup("
 + marker + 
")"
);
    }
 
    
void
 f(
int
 marker) {
       System.
out
.println(
"f("
 + marker + 
")"
);
    }
}
 
class
 Cups {
    
static
 Cup 
c1
;
 
    
static
 Cup 
c2
;
    
static
 {
       
c1
 = 
new
 Cup(1);
       
c2
 = 
new
 Cup(2);
    }
 
    Cups() {
       System.
out
.println(
"Cups()"
);
    }
}
 
public
 
class
 ExpilicitStatic {
    
public
 
static
 
void
 main(String[] args) {
       System.
out
.println(
"Inside main()"
);
       Cups.
c1
.f(99);
// (1)
    }
    
// static Cups x=new Cups();//(2)
    
// static Cups y=new Cups();//(2)
}
 
结果:
Inside main()

Cup(1)

Cup(2)

f(99)
无论是通过标为(1)
的那行程序访问静态的 c1
对象,还是把(1)
注释掉,让它去运行标为(2) 
的那行,Cups 
的静态初始化动作都会得到执行。如果把(1)
(2)
同时注释掉,Cups 
的静态初始化动作就不会进行。此外,激活一行还是两行(2)
代码都无关紧要,静态初始化动作只进行一次。
5.
非静态实例初始化
看下面的代码:
class
 Mug {
    Mug(
int
 marker) {
       
System.
out
.println(
"Mug("
 + marker + 
")"
);
    
}
 
    
void
 f(
int
 marker) {
       System.
out
.println(
"f("
 + marker + 
")"
);
    }
}
 
public
 
class
 Mugs {
    Mug 
c1
;
 
    Mug 
c2
;
    {
       
c1
 = 
new
 Mug(1);
       
c2
 = 
new
 Mug(2);
       System.
out
.println(
"c1&c2 initialized"
);
    }
 
    Mugs() {
       System.
out
.println(
"Mugs()"
);
    }
 
    
public
 
static
 
void
 main(String[] args) {
       System.
out
.println(
"Inside main()"
);
       
new
 Mugs();
       System.
out
.println(
"===new Mugs again==="
);
       
new
 Mugs();
    }
}
 
结果:
Inside main()

Mug(1)

Mug(2)

c1&c2 initialized

Mugs()

===new Mugs again===

Mug(1)

Mug(2)

c1&c2 initialized

Mugs()
从结果可以看到,非静态的代码块被执行了2
次。所以只要实例化一个类,该类中的非静态代码块就会被执行一次。
6.
数组初始化
注意区分基本类型数据与类数据的初始化。看以下代码:
int
[] a; 
a = 
new
 
int
[rand.nextInt(20)];
Integer[] a = 
new
 Integer[rand.nextInt(20)]; 
for
(
int
 i = 0; i < a.
length
; i++) {
 
     a[i] = 
new
 Integer(rand.nextInt(500)); 
}
对于类数据类型的初始化,每个数组子成员都要重新new
一下。
7.
涉及继承关系的初始化
当创建一个导出类的对象时,该对象包含了一个基类的子对象。看下面代码:
class
 Art {
    Art() {
       System.
out
.println(
"Art constructor"
);
    }
}
 
class
 Drawing 
extends
 Art {
    Drawing() {
       System.
out
.println(
"Drawing constructor"
);
    }
}
 
public
 
class
 Cartoon 
extends
 Drawing {
    
public
 Cartoon() {
       System.
out
.println(
"Cartoon constructor"
);
    }
 
    
public
 
static
 
void
 main(String[] args) {
       
new
 Cartoon();
    }
}
 
结果:
Art constructor

Drawing constructor

Cartoon constructor
可以发现,构建过程是从基类“向外”扩散的,所以基类在导出类构造器可以访问它之前,就已经完成了初始化。
如果类没有缺省的参数,或者想调用一个带参数的基类构造器,就必须用关键字super
显示地调用基类构造器的语句,并且配以适当的参数列表。看下面代码:
class
 Game {
    Game(
int
 i) {
       System.
out
.println(
"Game constructor"
);
    }
}
 
class
 BoardGame 
extends
 Game {
    BoardGame(
int
 i) {
       
super
(i);
       System.
out
.println(
"BoardGame constructor"
);
    }
}
 
public
 
class
 Chess 
extends
 BoardGame {
    Chess() {
       
super
(11);
       System.
out
.println(
"Chess constructor"
);
    }
 
    
public
 
static
 
void
 main(String[] args) {
       
new
 Chess();
    }
}
 
结果:
Game constructor

BoardGame constructor

Chess constructor
如果不在BoardGame
()中调用基类构造器,编译器将无法找到符合Game()
形式的构造器。而且,调用基类构造器必须是你在导出类构造器中要做的第一件事。
8.
构造器与多态
8.1
构造器的调用顺序
基类的构造器总是在导出类的构造过程中被调用的,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊的任务:检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常是private
类型)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。这正是编译器为什么要强制每个导出类部分都必须调用构造器的原因。
 
8.2
构造器内部的多态方法的行为
看下面代码:
abstract
 
class
 Glyph {
    
abstract
 
void
 draw();
 
    Glyph() {
       System.
out
.println(
"Glyph() before draw()"
);
       draw();
       System.
out
.println(
"Glyph() after draw()"
);
    }
}
 
class
 RoundGlyph 
extends
 Glyph {
    
private
 
int
 
radius
 = 1;
 
    RoundGlyph(
int
 r) {
       
radius
 = r;
       System.
out
.println(
"RoundGlyph.RoundGlyph(),radius="
 + 
radius
);
    }
 
    
void
 draw() {
       System.
out
.println(
"RoundGlyph.draw(),radius="
 + 
radius
);
    }
}
 
public
 
class
 PolyConstructors {
    
public
 
static
 
void
 main(String[] args) {
       
new
 RoundGlyph(5);
    }
}
 
结果:
Glyph() before draw()

RoundGlyph.draw(),radius=0

Glyph() after draw()

RoundGlyph.RoundGlyph(),radius=5
Glyph
中,draw
()方法是抽象的,这样设计是为了覆盖该方法。我们确实在RoungGlyph
中强制覆盖了draw
()。但是Glyph
构造器会调用这个方法,结果导致了对RoundGlyph.draw
()的调用,这看起来似乎是我们的目的。但是如果看到输出结果,我们会发现当Glyph
的构造器调用draw
()方法时,radius
不是默认初始值1
,而是0
解决这个问题的关键是初始化的实际过程:
1
)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制零。
2
)如前所述那样调用基类构造器。此时,调用被覆盖后的draw
()方法(要在调用RoundGlyph
构造器之前调用),由于步骤1
的缘故,我们此时会发现radius
的值为0
3
)按照声明的顺序调用成员的初始化方法。
4
)调用导出类的构造器主体。
9.
初始化及类的加载
看以下代码:
class
 Tag {
    Tag(
int
 marker) {
       System.
out
.println(
"Tag("
 + marker + 
")"
);
    
}
}
 
class
 Insect {
    
private
 
int
 
i
 = 9;
 
    
protected
 
int
 
j
m
;
 
    Insect() {
       System.
out
.println(
"i = "
 + 
i
 + 
", j = "
 + 
j
);
       
j
 = 39;
    }
 
    
private
 
static
 
int
 
x1
 = print(
"static Insect.x1 initialized"
);
 
    
static
 
int
 print(String s) {
       System.
out
.println(s);
       
return
 47;
    }
 
    Tag 
t1
 = 
new
 Tag(1);
}
 
public
 
class
 Beetle 
extends
 Insect {
    
private
 
int
 
k
 = print(
"Beetle.k initialized"
);
 
    
public
 Beetle() {
       System.
out
.println(
"k = "
 + 
k
);
       System.
out
.println(
"j = "
 + 
j
);
       System.
out
.println(
"m = "
 + 
m
);
    }
 
    
private
 
static
 
int
 
x2
 = print(
"static Beetle.x2 initialized"
);
 
    
public
 
static
 
void
 main(String[] args) {
       System.
out
.println(
"Beetle constructor"
);
       Beetle b = 
new
 Beetle();
    }
 
    Tag 
t2
 = 
new
 Tag(2);
}
 
结果:
static Insect.x1 initialized

static Beetle.x2 initialized

Beetle constructor

Tag(1)

i = 9, j = 0

Beetle.k initialized

Tag(2)

k = 47

j = 39

m = 0
你在Beetle 
上运行Java 
时,所发生的第一件事情就是你试图访问Beetle.main( ) 
(一个static 
方法),于是加载器开始启动并找出 Beetle 
类被编译的程序代码(它被编译到了一个名为Beetle .class 
的文件之中)。在对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字 extends  
告知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生。
如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的静态初始化(在此例中为Insect
)即会被执行,然后是下一个导出类,以此类推。这种方式很重要,因为导出类的静态初始化可能会依赖于基类成员能否被正确初始化的。
至此为止,必要的类都已加载完毕(静态变量和静态块),对象就可以被创建了。
首先,对象中所有的原始类型都会被设为缺省值,对象引用被设为null
——这是通过将对象内存设为二进制零值而一举生成的。然后,基类的构造器会被调用。在本例中,它是被自动调用的。但你也可以用super 
来指定对基类构造器的调用。基类构造器和导出类的构造器一样,以相同的顺序来经历相同的过程,即向上寻找基类构造器。在基类构造器完成之后(即根部构造器找到之后),实例变量(instance variables 
)按其次序被初始化(注意观察代码中的Tag()
)。最后,构造器的其余部分被执行。
本文转自zhangjunhd51CTO博客,原文链接:http://blog.51cto.com/zhangjunhd/20927,如需转载请自行联系原作者
你可能感兴趣的文章
我的第一个基于springboot的接口
查看>>
BCH易于使用不只是说说而已
查看>>
webpack配置(第四步:html篇(进阶篇))
查看>>
shell基础、命令操作、通配符
查看>>
深入理解JVM(五)——垃圾回收器
查看>>
spring mvc 环境搭建
查看>>
装饰者模式
查看>>
单身税的时代就要来临,你还没有用Python帮你找一个女朋友吗?
查看>>
Kafka的底层实现原理
查看>>
CAS实现单点登录实例源码
查看>>
JEESZ-Zookeeper集群安装
查看>>
Dubbo背景和简介
查看>>
vue-router的HTML5 History 模式设置
查看>>
Neo 虚拟机
查看>>
Pycharm上Django的使用 Day10
查看>>
node上的redis调用优化示例
查看>>
Jenkinsfile
查看>>
CSS:父子元素浮动分析和清除浮动
查看>>
springboot配置Druid数据源
查看>>
IT兄弟连 JavaWeb教程 过滤器与监听器经典面试题
查看>>