八、 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 初始化过程的总体顺序没有讲,只是说了构造器时的顺序,作者似乎认为路径很多,列出来比较混乱.我觉得还是要搞清楚它的过程比较好.所以现在结合我的学习经验写出具体过程: 过 ...
随机推荐
- webpack的配置处理
1.webpack对脚本的处理 1.Js用什么loader加载? 1>webpack 本身就支持js的加载, 2>通过babel-loader ES2015 加载js,再用 babel-p ...
- 将数组转化为json字符串(不使用json_encode函数)
将数组转化为json字符串(不使用json_encode函数) public function arrayToJson($arr,$jsonStr=''){ $jsonStr.='{'; foreac ...
- 解决cmd目录下pip命令不存在的问题
解决cmd目录下pip命令不存在的问题 注:pip.exe程序在Python安装目录下的scripts中1.在cmd命令中输入: 先输入:python -m ensurepip 再输入:python ...
- JVM——自定义类加载器
)以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理.这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着 ...
- mongoTemplate聚合操作Demo
package com.tangzhe.mongodb.mongotemplate; import com.mongodb.BasicDBObject; import com.mongodb.DBOb ...
- easyui-combogrid匹配查询
用到easyui-combogrid,数据比较少的情况,可以一页就显示完毕,然后直接下拉选择.但是对于数据量比较大的情况,一页显示全部显然不合适,好在从easyui-combogrid的数据加载方式可 ...
- IOS开发---菜鸟学习之路--(十八)-利用代理实现向上一级页面传递数据
其实我一开始是想实现微信的修改个人信息那样的效果 就是点击昵称,然后跳转到另外一个页面输入信息 但是细想发现微信的话应该是修改完一个信息后就保存了 而我做的项目可能需要输入多个数据之后再点击提交的. ...
- python中subprocess.Popen的args和shell参数的使用
subprocess模块定义了一个类: Popen class subprocess.Popen( args, bufsize=0, executable=None, ...
- mate viewport
<meta name="viewport" content="width=device-width,height=device-height,initial-sca ...
- 使用CORS解决flask前端页面跨域问题
from flask import Flask from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route(" ...