八、 Java程序初始化的顺序(一)
今天在写构造器方法的时候,遇到了一个小问题,由这个问题引发了一连串的思考,在一个Java类中变量与类的初始化执行顺序是什么样的呢?
## 发现问题
class Student{
private String name;
void setName(String name){
this.name = name;
}
String getName(){
return name;
}
Student(){
//this(this.name);
this(name);
System.out.println("题目要求写一个无参的构造器");
}
Student(String name){
this.name = name;
}
}
class TestStudent{
public static void main(String[] args){
Student stu1 = new Student();
System.out.print(stu1.getName());
Student stu2 = new Student("老大");
System.out.println(stu2.getName());
}
}
此时会报错:无法在调用超类型构造器之前引用name. 在使用构造器创建对象时,此时的成员变量name的值是否已经完成初始化,无参构造中调用它时报的这个错意味着什么。我们本篇博客就来讨论一下,一个类创建对象时到底做了哪些事? ## 思考问题
首先,对于一个类来说加载分为五个部分,分别是静态变量,静态代码块,非静态变量,非静态代码块以及构造器。 ### 单个类成员加载顺序
测试代码:
class Student{
// 静态变量
static String name;
// 静态代码块
static{
System.out.println("刚运行到静态代码块时的静态变量值:"+name);
name = "静态name值";
System.out.println("静态代码块结束时的静态变量值:"+name);
}
//定义一个无参构造器
Student(){
System.out.println("刚运行到构造器时的静态变量值:"+name);
name = "这是一个无参的构造器";
System.out.println("构造器结束时的静态变量值:"+name);
}
//定义一个非静态变量
String name2;
//定义一个非静态代码块
{
System.out.println("刚运行到非静态代码块时的非静态变量值:"+name2);
name2 = "非静态name值";
System.out.println("非静态代码块结束时的非静态变量值:"+name2);
}
}
class TestStudent{
public static void main(String[] args){
Student stu = new Student();
}
}
此时编译代码执行的结果是:
```
刚运行到静态代码块时的静态变量值:null
静态代码块结束时的静态变量值:静态name值
刚运行到非静态代码块时的非静态变量值:null
非静态代码块结束时的非静态变量值:非静态name值
刚运行到构造器时的静态变量值:静态name值
构造器结束时的静态变量值:这是一个无参的构造器
```
由此可以看出,当我们声明的类成员变量是一个静态成员变量的时候,在调用构造器之前,我们的静态成员变量已经生成并初始化成相应的数据类型的默认值(即此处String对象的默认值位null)。
然后在静态代码块中,我们将静态变量赋值,然后程序跳转到非静态变量声明与赋值。再执行非静态代码块,最后直行到程序的无参构造器。 所以,通过此程序代码,我们得出结论:单个类的程序加载顺序是:静态变量-->静态代码块-->非静态变量-->非静态代码块-->构造器。 也就是说调用构造器时,静态与非静态的属性都已经完成初始化工作了,this(name)调用报错与name属性本身没有关系。 ### 深入思考类加载顺序
既然说到加载顺序,那么我们继续完成类成员的加载顺序。关于变量与代码块之间的关系,或者说根据我们上面的这段代码得出这个初步的结论我们还有待商榷,因为,我们的程序加载的顺序是自上而下的,也就是说,我们的得到的这个结论有可能是因为我们习惯性的排版导致的,我们声明各部分的顺序偶可能影响我们得出的结论。为了确定我们程序的严谨性,我们需要进一步的调整代码的顺序,来加强验证我们代码实验的逻辑严谨性。
public class Student2 {
// 静态代码块放到前面,此时name还未声明,所以会报错
static{
System.out.println("刚运行到静态代码块时的静态变量值:"+name);
name = "静态name值";
System.out.println("静态代码块结束时的静态变量值:"+name);
}
// 静态变量
static String name;
//定义一个无参构造器
Student2(){
System.out.println("刚运行到构造器时的静态变量值:"+name);
name = "这是一个无参的构造器";
System.out.println("构造器结束时的静态变量值:"+name);
}
//定义一个非静态代码块
{
System.out.println("刚运行到非静态代码块时的非静态变量值:"+name2);
name2 = "非静态name值";
System.out.println("非静态代码块结束时的非静态变量值:"+name2);
}
//定义一个非静态变量
String name2;
}
class TestStudent{
public static void main(String[] args){
Student stu = new Student();
Student2 stu2 = new Student2();
}
}
上面代码运行的结果:
```
Error:(6, 48) java: 非法前向引用
```
此时将代码块拿到变量声明的前面我们的代码出现了错误提示,这说明了我们一开始得到的结论并不严谨,我们这里可以得出代码块的执行是在变量声明之前的。
所以,我们可以根据常识大胆的猜想,单个类程序加载的顺序是静态-->非静态-->构造器,其中变量声明与代码块的执行顺序与代码前后位置有关,并没有严格的前后之分,程序员将代码写在前边的的先执行。 ### 验证猜想
public class Student3 {
//定义一个无参构造器
Student3(){
System.out.println("刚运行到构造器时的静态变量值:"+name);
name = "这是一个无参的构造器";
System.out.println("构造器结束时的静态变量值:"+name);
}
//定义一个非静态代码块
{
name2 = "非静态name值";
System.out.println("非静态代码块结束时的静态变量值:"+name);
}
//定义一个非静态变量
String name2;
// 静态代码块
static{
System.out.println("运行到静态代码块");
// name = "静态代码块里赋的值";
}
// 静态变量
static String name;
}
class TestStudent{
public static void main(String[] args){
Student3 stu3 = new Student3();
}
}
上面代码执行的结果:
```
运行到静态代码块
非静态代码块结束时的静态变量值:null
刚运行到构造器时的静态变量值:null
构造器结束时的静态变量值:这是一个无参的构造器
```
基本验证了我们的猜想是正确的,但是在结尾我又做了一个有趣的测试。 ### 测试
public class Student3 {
//定义一个无参构造器
Student3(){
System.out.println("刚运行到构造器时的静态变量值:"+name);
name = "这是一个无参的构造器";
System.out.println("构造器结束时的静态变量值:"+name);
}
//定义一个非静态代码块
{
name2 = "非静态name值";
System.out.println("非静态代码块结束时的静态变量值:"+name);
}
//定义一个非静态变量
String name2;
// 静态代码块
static{
System.out.println("运行到静态代码块");
name = "静态代码块里赋的值";// 按道理说,我们这里没有声明就直接赋值操作了
}
// 静态变量
static String name;
}
class TestStudent{
public static void main(String[] args){
Student3 stu3 = new Student3();
}
}
上面代码执行的结果:
```
运行到静态代码块
非静态代码块结束时的静态变量值:静态代码块里赋的值
刚运行到构造器时的静态变量值:静态代码块里赋的值
构造器结束时的静态变量值:这是一个无参的构造器
```
也就是说在静态代码块里,我们无法引用后面的静态变量,但是我们编译之前可以对他进行赋值,并且在后面的非静态代码块里我们还可以取到里面的值,再次做出假设,这是java虚拟机在编译时不让向前引用,此时的变量其实已经完成了声明初始化等一系列操作(都是存在方法区),只是通过不了编译而已。
所以我认为,我们最早得到的结论应该才是正确的Java程序整个加载流程的顺序。 这中间也可能时JDK和IDE一起努力做了点什么,但是实际上也不影响,我们的代码块的存在本来就是为了完成变量的初始化工作的,所以将代码块放到属性声明之前是毫无意义的操作,所以这里只是遇到了测试一下而已,实际操作中毫无意义。 ## 得出结论
总结:在一个类中,初始化顺序为:
1. 静态变量,静态变量初始化;
2. 静态代码块;
3. 非静态变量初始化;
4. 非静态代码块;
5. 构造器。 ##最后
到这里,我们理清楚了单个类中的各部分的加载顺序,但是我们文章一开始提到的问题并没有解决,如果构造器执行是在静态和非静态属性及代码块之后的话,此时的成员变量应当已经有了初始化值了,再不济成员变量还有一个初始的null值,但是这里报了无法在调用超类型构造器之前引用name。
这说明这里调用name关系到了这个类的父类构造器,所以我们后面继续探讨类加载在继承中的加载顺序,就可以解决这个问题了。
八、 Java程序初始化的顺序(一)的更多相关文章
- Java程序初始化的顺序
Java程序初始化的顺序 java程序初始化工作可以在许多不同的代码块中来完成(例如:静态代码块.构造函数等),他们执行的顺序如下: 父类静态变量 父类静态代码块 子类静态变量 子类静态代码块 父类非 ...
- 《Java程序员面试笔试宝典》之Java程序初始化的顺序是怎样的
在Java语言中,当实例化对象时,对象所在类的所有成员变量首先要进行初始化,只有当所有类成员完成初始化后,才会调用对象所在类的构造函数创建对象. Java程序的初始化一般遵循以下三个原则(以下三原则优 ...
- 《Java程序猿面试笔试宝典》之Java程序初始化的顺序是如何的
在Java语言中.当实例化对象时.对象所在类的全部成员变量首先要进行初始化,仅仅有当全部类成员完毕初始化后,才会调用对象所在类的构造函数创建对象. Java程序的初始化一般遵循以下三个原则(以下 ...
- 九、 Java程序初始化的顺序(二)
之前的一篇博客里我写了关于在一个类中的程序初始化顺序,但是在Java的面向对象里,类之间还存在着继承的关系.所以关于程序的初始化顺序,我们可以再细划分为:父类静态变量,父类的静态代码块,父类构造器,父 ...
- java程序初始化顺序
使用场景: 在java程序中,当实例化对象时,对象的所在类的所有成员变量首先要进行初始化,只有当所有类成员完成初始化后, 才会调用对象所在类的构造函数创建对象. 初始化的原则: (1)静态对象优先于 ...
- Java中程序初始化的顺序
1,在一个类的内部(不考虑它是另一个类的派生类):很多人认为,类的成员变量是在构造方法调用之后再初始化的,先不考虑这种观点的正确性,先看一下下面的代码: class Test01...{ public ...
- Java的初始化执行顺序(父类static变量->子类static变量->父类成员变量->父类构造器->成员变量->构造器->main函数)
1. 引言 了解Java初始化的顺序,有助于理解Java的初始化机制和内存机制. 顺序:父类static变量->子类static变量->父类成员变量->父类构造器->成员变量- ...
- 谁动了我的奶酪?--java实例初始化的顺序问题
故事背景 有一天,老鼠小白发现了一个奇怪的问题,它的奶酪的生产日期被谁搞丢了,不知道奶酪是否过期,可怎么吃呀? 让我们来看看吧 import java.util.Date;public class C ...
- AJPFX总结Java 程序初始化过程
觉得Core Java在Java 初始化过程的总体顺序没有讲,只是说了构造器时的顺序,作者似乎认为路径很多,列出来比较混乱.我觉得还是要搞清楚它的过程比较好.所以现在结合我的学习经验写出具体过程: 过 ...
随机推荐
- python代码notepad++不变色问题。
原来是文档后缀名是.txt造成的,应该改成.py,疏忽了...
- C++ 11 从C++ primer第五版的学习笔记
1. auto (page107) auto 推断会忽略const const int ci = i, & cr = ci; auto b = ci; // b is an int (to ...
- 给B公司的一些建议(又一篇烂尾的文章)
感慨:太多太多的悲伤故事,发生在自己身上,发生在自己的身边.因此,为了避免总是走"弯路",走"错误"的道路,最近一直在完善自己的理论模型. 烂尾说明:本文是一篇 ...
- daily algorithm 判断链表是否有环
public static boolean isLoopLink(ListNode head) { if (head == null) { return false; } ListNode fast ...
- exkmp略解
推导 ext[i]表示母串s[i..lens]和子串t[1..lent]的最长公共前缀. nxt[i]表示t[i..lent]和t[1..lent]的最长公共前缀. 假设ext[1..k]已经算好,现 ...
- java web知识点
java web知识点 1.Java知识点 基本数据类型,面向对象,异常,IO,NIO,集合,多线程,JVM,高级特性. 2.web知识点 JSP,Serlvet,JDBC,Http 掌握Cookie ...
- 在 Amazon AWS 搭建及部署网站:序
最近玩了把 AWS,实现了服务器的创建.PHP+MySql运行环境.代码部署等.一方面,后面的项目会反复重复这个流程,需要一份手册,另一方面,也给自己一个记录.于是把整个过程和要点整理一下,发到自己的 ...
- 【Swap Nodes in Pairs】cpp
题目: Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1-> ...
- [转]jQuery DOM Ready
一直以来,各种JS最佳实践都会告诉我们,将JS放在HTML的最后,即</body>之前,理由就是:JS会阻塞下载,而且,在JS中很有可能有对DOM的操作,放在HTML的最后,可以尽可能的保 ...
- 从零开始到设计Python+Selenium自动化测试框架-如何开始
如何开始学习web ui自动化测试?如何选择一门脚本语言?选择什么自动化测试工具? 本人已经做测试快5年,很惭愧,感觉积累不够,很多测试都不会,三年多功能测试,最近两年才开始接触和学习自动化测试.打算 ...