- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Java类初始化和实例化中的2个“雷区”由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
在考虑类初始化时,我们都知道进行子类初始化时,如果父类没有初始化要先初始化子类。然而事情并没有一句话这么简单。 首先看看Java中初始化触发的条件:
(1)在使用new实例化对象,访问静态数据和方法时,也就是遇到指令:new,getstatic/putstatic和invokestatic时; (2)使用反射对类进行调用时; (3)当初始化一个类时,父类如果没有进行初始化,先触发父类的初始化; (4)执行入口main方法所在的类; (5)JDK1.7动态语言支持中方法句柄所在的类,如果没有初始化触发起初始化; 。
经过编译后生成一个<clinit>方法,类的初始化就在这个方法中进行,该方法只执行,由JVM保证这一点,并进行同步控制; 其中条件(3),从方法调用的角度来看,是子类的<clinit>会在开始时递归的调用父类的<clinit>,这类似与我们在子类构造器中必须首先调用父类的构造器; 但需要注意的是“触发”并不是完成初始化,这意味着有可能子类的初始化会提前于父类初始化结束,这就是“危险”的所在.
1. 一个类初始化的例子: 这个例子我使用一个外围类包含2个有继承关系的静态成员类,因为外围类的初始化和静态成员类没有因果关系,因此这样展示是安全和方便的; 父类A和子类B分别包含main函数,由上面的触发条件(4)可知,通过分别调用这个两个main函数来触发不同的类初始化路径; 这个例子的问题在于父类包含子类的static引用并在定义处进行初始化的问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
class
WrapperClass {
private
static
class
A {
static
{
System.out.println(
"类A初始化开始..."
);
}
//父类包含子类的static引用
private
static
B b =
new
B();
protected
static
int
aInt =
9
;
static
{
System.out.println(
"类A初始化结束..."
);
}
public
static
void
main(String[] args) {
}
}
private
static
class
B
extends
A {
static
{
System.out.println(
"类B初始化开始..."
);
}
//子类的域依赖于父类的域
private
static
int
bInt =
9
+ A.aInt;
public
B() {
//构造器依赖类的static域
System.out.println(
"类B的构造器调用 "
+
"bInt的值"
+ bInt);
}
static
{
System.out.println(
"类B初始化结束... "
+
"aInt的值:"
+ bInt);
}
public
static
void
main(String[] args) {
}
}
}
|
情景一:入口为类B的main函数时输出结果:
1
2
3
4
5
6
7
|
/**
* 类A初始化开始...
* 类B的构造器调用 bInt的值0
* 类A初始化结束...
* 类B初始化开始...
* 类B初始化结束... aInt的值:18
*/
|
分析:可以看到,main函数的调用触发了类B的初始化,进入类B的<clinit>方法,类A作为其父类先开始初始化进入了A的<clinit>方法,其中有一个语句new B();这时会进行B的实例化,这是已经在类B的<clinit>中了,main线程已经获得锁开始执行类B的<clinit>,我们开头说过JVM会保证一个类的初始化方法只被执行一次,JVM收到new指令后不会再进入类B的<clinit>方法而是直接进行实例化,但是此时类B还没有完成类初始化,所以可以看到bInt的值为0(这个0是类加载中准备阶段分配方法区内存后进行的置零初始化); 因此,可以得出,再父类中包含子类类型的static域并进行赋值动作,会可能导致子类实例化在类初始化完成前进行; 。
情景二:入口为类A的main函数时输出结果:
1
2
3
4
5
6
7
|
/**
* 类A初始化开始...
* 类B初始化开始...
* 类B初始化结束... aInt的值:9
* 类B的构造器调用 bInt的值9
* 类A初始化结束...
*/
|
分析:经过情景一的分析,我们知道,由类B的初始化触发类A的初始化,会导致类A中类变量b的实例化在类B初始化完成前进行,那如果先初始化类A是不是就可以在类变量实例化的时候先触发类B的初始化,从而使得初始化在实例化前呢?答案是肯定的,但是这仍然有问题。 根据输出,可以看到,类B的初始化在类A的初始化完成前进行了,这导致了像类变量aInt的变量在类B初始化完成后才进行初始化,所以类B中的域bInt获取到的aInt的值是“0”,而不是我们预期的“18”; 。
结论:综上,可以得出,在父类中包含子类类型的类变量,并在定义出进行实例化是非常危险的行为,具体情况可能不会向例子一样直白,调用方法在定义处赋值一样隐含着危险,即使要包含子类类型的static域,也应该通过static方法进行赋值,因为JVM可以保证在static方法调用前完成所有的初始化动作(当然这种保证也是你不应该包含static B b = new B();这样的初始化行为); 。
2. 一个实例化的例子: 首先需要知道对象创建的过程: (1)遇到new指令,检查类是否完成了加载,验证,准备,解析,初始化(解析过程就是符号引用解析成直接引用,比如方法名就是一个符号引用,可以在初始化完成后使用这个符号引用的时候进行,正是为了支持动态绑定),没有完成先进行这些过程; (2)分配内存,采用空闲列表或者指针碰撞的方法,并将新分配的内存“置零”,因此所有的实例变量在此环节都进行了一次默认初始化为0(引用为null)的过程; (3)执行<init>方法,包括检查调用父类的<init>方法(构造器),实例变量定义出的赋值动作,实例化器顺序执行,最后调用构造器中的动作.
这个例子可能更为大家所熟知,也就是它违反了“不要在构造器,clone方法和readObject方法中调用可被覆盖的方法”。其原因就在于Java中的多态,也就是动态绑定。 父类A的构造器中包含一个protected方法,类B是其子类.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
class
WrongInstantiation {
private
static
class
A {
public
A() {
doSomething();
}
protected
void
doSomething() {
System.out.println(
"A's doSomething"
);
}
}
private
static
class
B
extends
A {
private
int
bInt =
9
;
@Override
protected
void
doSomething() {
System.out.println(
"B's doSomething, bInt: "
+ bInt);
}
}
public
static
void
main(String[] args) {
B b =
new
B();
}
}
|
输出结果:
1
2
3
|
/**
* B's doSomething, bInt: 0
*/
|
分析:首先需要知道,在没有显示提供构造器时Java编译器会生成默认构造器,并在开始处调用父类的构造器,因此类B的构造器开始会先调用类A的构造器。 类A中调用了protected方法doSomething,从输出结果中我们看到实际上调用的是子类的方法实现,而此时子类的实例化还未开始,因此bInt并没有如“预期”那样是9,而是0; 这就是由于动态绑定,doSomething是一个protected方法,因此它是通过invokevirtual指令调用的,该指令根据对象实例的类型找到对应的方法实现(这里就是B的实例对象,对应方法就是类B的方法实现)执行,故而有此结果.
结论:正如前面说的“不要在构造器,clone方法和readObject方法中调用可被覆盖的方法”.
以上就是为大家介绍的Java类初始化和实例化中的2个“雷区”,希望对大家的学习有所帮助.
最后此篇关于Java类初始化和实例化中的2个“雷区”的文章就讲到这里了,如果你想了解更多关于Java类初始化和实例化中的2个“雷区”的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我是 Spring 新手,这就是我想要做的事情: 我正在使用一个基于 Maven 的库,它有自己的 Spring 上下文和 Autowiring 字段。 它的bean配置文件是src/test/res
我在我的测试脚本中有以下列表初始化: newSequenceCore=["ls", "ns", "*", "cm", "*", "ov", "ov", "ov", "ov", "kd"] (代表要在控
这个问题在这里已经有了答案: 关闭 11 年前。 Possible Duplicate: Class construction with initial values 当我查看 http://en.
我得到了成员变量“objectCount”的限定错误。编译器还返回“ISO C++ 禁止非常量静态成员的类内初始化”。这是主类: #include #include "Tree.h" using n
我有如下所示的a.h class A { public: void doSomething()=0; }; 然后我有如下所示的b.h #include "a.h" class b: publi
我需要解析 Firebase DataSnapshot (一个 JSON 对象)转换成一个数据类,其属性包括 enum 和 list。所以我更喜欢通过传递 DataSnapshot 来手动解析它进入二
我使用 JQuery 一段时间了,我总是使用以下代码来初始化我的 javascript: $(document).ready( function() { // Initalisation logic
这里是 Objective-C 菜鸟。 为什么会这样: NSString *myString = [NSString alloc]; [myString initWithFormat:@"%f", s
我无法让核心数据支持的 NSArrayController 在我的代码中正常工作。下面是我的代码: pageArrayController = [[NSArrayController alloc] i
我对这一切都很陌生,并且无法将其安装到我的后端代码中。它去哪里?在我的页脚下面有我所有的 JS? 比如,这是什么意思: Popup initialization code should be exec
这可能是一个简单的问题,但是嘿,我是初学者。 所以我创建了一个程序来计算一些东西,它目前正在控制台中运行。我决定向其中添加一个用户界面,因此我使用 NetBeans IDE 中的内置功能创建了一个 J
我有 2 个 Controller ,TEST1Controller 和 TEST2Controller 在TEST2Controller中,我有一个initialize()函数设置属性值。 如果我尝
据我所知, dependentObservable 在声明时会进行计算。但如果某些值尚不存在怎么办? 例如: var viewModel ={}; var dependentObservable1 =
我正在阅读 POODR 这本书,它使用旧语法进行默认值初始化。我想用新语法实现相同的功能。 class Gear attr_reader :chainring, :cog, :wheel de
我按照 polymer 教程的说明进行操作: https://www.polymer-project.org/3.0/start/install-3-0 (我跳过了可选部分) 但是,在我执行命令“po
很抱歉问到一个非常新手的Kotlin问题,但是我正在努力理解与构造函数和初始化有关的一些东西。 我有这个类和构造函数: class TestCaseBuilder constructor(
假设我们有一个包含 30 列和 30 行的网格。 生命游戏规则简而言之: 一个小区有八个相邻小区 当一个细胞拥有三个存活的相邻细胞时,该细胞就会存活 如果一个细胞恰好有两个或三个活的相邻细胞,那么它就
我是 MQTT 和 Android 开放附件“AOA” 的新手。在阅读教程时,我意识到,在尝试写入 ByteArrayOutputStream 类型的变量之前,应该写入 0 或 0x00首先到该变量。
我有 2 个 Controller ,TEST1Controller 和 TEST2Controller 在TEST2Controller中,我有一个initialize()函数设置属性值。 如果我尝
我有一个inotify /内核问题。我正在使用“inotify” Python项目进行观察,但是,我的问题仍然是固有的关于inotify内核实现的核心。 Python inotify项目处理递归ino
我是一名优秀的程序员,十分优秀!