八、 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 初始化过程的总体顺序没有讲,只是说了构造器时的顺序,作者似乎认为路径很多,列出来比较混乱.我觉得还是要搞清楚它的过程比较好.所以现在结合我的学习经验写出具体过程: 过 ...
随机推荐
- mysql面试题:字段中@之前字符相同且大于等于2条的所有记录
公司发了一张面试题给我,题目如下: 在test数据库中有个flow_user表,找出email字段中@之前字符相同且大于等于2条的所有记录 答案: select substring_index(`em ...
- 通用后台管理系统源码,响应式布局,Java管理系统源码,零门槛安装部署
本项目是一个通用响应式管理后台,导入开发环境安装就能直接运行,界面也非诚漂亮,在PC端和移动端也是自适应的.非常适合企业或者个人搭建各种商城后台,博客后台,网站管理后台等. 源码启动后的截图 需要这套 ...
- 18.VUE学习之-v-for操作对象与数值
一组数组时的循环 二组数组时的循环 另外可以v for 20 可以直接操作数字 <!DOCTYPE html> <html lang="en"> <h ...
- 第1-5章 慕课网微信小程序开发学习笔记
第1章 前言:不同的时代,不同的Web --微信小程序商城构建全栈应用 http://note.youdao.com/noteshare?id=a0e9b058853dbccf886c1a890594 ...
- java 调用第三方系统时的连接代码-记录
前言:该文章主要是总结我在实际工作中遇到的问题,在调取第三方系统的时候出现的问题,算自己的总结.各位博友如果有什么建议或意见欢迎留言指正. 先将准备传入参数 再与第三方系统建立连接 再第三方系统处理后 ...
- du 与df 统计系统磁盘不一致原因与解决方法
事件起因: 同事发现云主机磁盘系统盘满了,准备清理系统盘,便利用du 命令统计了根目录下各文件夹的大小,发现统计的各文件夹的大小总和 加起来比 df 命令查看到的系统盘所使用空间 要小很多.这里记录下 ...
- 5、python中的列表
list是python内置的一种有序.可变的数据结构. 一.如何创建一个list? 示例: 注意: list中的元素可以是任意的数据类型如字符串.数字.布尔值.None等,也可以是其他的数据结构如另外 ...
- Diycode开源项目 TopicContentActivity分析
1.效果预览以及布局分析 1.1.实际效果预览 左侧话题列表的布局是通过TopicProvider来实现的,所以当初分析话题列表就没有看到布局. 这里的话题内容不是一个ListView,故要自己布局. ...
- jvm探秘之三:GC初步
GC即垃圾收集器,虚拟机的必要组成部分. 不过这里说当然是,hotspot虚拟机(jvm的主要版本)的GC机制,前面说过了jvm的组成部分,那么想当然GC只需要负责方法区和堆就好了,虚拟机栈.本地方法 ...
- bash shell命令与监测的那点事(一)
bash shell命令与监测的那点事之ps 学习LInux,不得不谈谈bash shell命令,介绍Linux命令行与Shell脚本的书有很多很多,bash shell命令也有很多,此次我们只谈谈有 ...