文章目录
Java是如何保证所有变量的初始化的?
- Java会尽可能保证所有变量的初始化
- 类变量和类的成员变量,在JVM的类加载子系统中保证了初始化!
- 方法中的局部变量,会以编译器编译不通过的方式来保证变量初始化!
Java中的0值初始化情况
- 对于引用对象(包括自定义的对象,String,包装类型,数组)的默认初始化为null,因此如果没有进行显式的初始化,那么不能直接使用…会空指针!
- 对基本类型0值初始化,差不多都是赋0值,char也是赋0,boolean是赋false字面量;
可以将方法的返回值赋值给变量进行初始化
- 初始化期间是可以通过方法进行赋值的(如下)
显式初始化时“向前引用”的错误
- 注意一下,(如下图)y先进行初始化,然后y在赋值给x,是合法的!
- 但是,一旦这两行交换位置,那么就会编译不通过,出现了“向前引用”的警告,注意一下就好…
成员变量的初始化顺序
- 在类的内部,变量定义的顺序决定了他们初始化的顺序(即使变量散布在方法和构造器之间,它们也是要优先于方法和构造器调用进行初始化的!)
举个例子证明一下看看:
我们来看看,输出了什么?
说明了x,y,z分别依次进行初始化后,solution对象才调用了构造器方法!
静态数据的初始化
- 首先需要知道!如论创建多少个对象!静态数据只占一份存储空间!
- static关键字并不能作用于局部变量上!
- static能作用于类变量上,且在加载阶段会进行初始化(类加载子系统的:加载-连接-初始化,之后总结JVM的时候再详细说说)
- 对于类中的静态变量初始化,如果不创建该类对象,或者不通过Class为入口调用来调用类中的静态变量,那么这个变量是不会进行初始化的!
- 类中的静态变量只会初始化一次!如果之前进行初始化了,下次再new该类对象,或者调用静态变量,静态变量不会二次初始化!
静态代码块以及代码块
- 这个静态代码块也是只在类创建,或者Class调用静态变量的时候加载一次,仅仅一次。
- 非静态的实例初始化代码块,就是少了个static的代码块!它的执行时机是在构造方法前!
类中整体的初始化顺序
假设这个类中之前没有创建过对象,并且也没有通过类名来调用其中的静态变量!
- 初始化顺序是先静态对象,后非静态对象。
- 构造器其实也是static的!虽然没有显式的用static进行修饰!
写一个例子来看看究竟加载顺序是什么!
class Loader {
public Loader(int x) {
System.out.println("现在加载的是:" + x);
}
}
class Father {
public Father() {
Loader loader0 = new Loader(0);
System.out.println("father加载");
}
static Loader loader1 = new Loader(1);
Loader loader2 = new Loader(2);
static {
Loader loader3 = new Loader(3);
}
Loader loader4 = new Loader(4);
{
Loader loader5 = new Loader(5);
}
static Loader loader6 = new Loader(6);
}
class Son extends Father {
public Son() {
Loader loader7 = new Loader(7);
System.out.println("son加载");
}
static Loader loader8 = new Loader(8);
Loader loader9 = new Loader(9);
static {
Loader loader10 = new Loader(10);
}
Loader loader11 = new Loader(11);
{
Loader loader12 = new Loader(12);
}
static Loader loader13 = new Loader(13);
}
public class Solution {
public static void main(String[] args) {
Son son = new Son();
}
}
输出的顺序是:
现在加载的是:1
现在加载的是:3
现在加载的是:6
现在加载的是:8
现在加载的是:10
现在加载的是:13
现在加载的是:2
现在加载的是:4
现在加载的是:5
现在加载的是:0
father加载
现在加载的是:9
现在加载的是:11
现在加载的是:12
现在加载的是:7
son加载
我们这个时候可以总结一下规律了!
如果有个Son类继承了Father类:
1.首先从父类中上到下顺序初始化static变量(static静态代码块可以看做普通的static修饰静态变量,只按照从上到下的静态变量声明顺序,不会将静态代码块的优先级分别初始化,可以参考一下上面的loader2、3、6对象)
2.然后轮到子类中的static变量按照声明的顺序初始化,static代码块中的声明和外面单独声明一视同仁没有区别:参考loader8、10、13
3.然后轮到父类中的非静态变量按照声明顺序初始化,包括非静态代码块的,参考loader2、4、5
4.【注意】下一个是轮到父类的构造器执行!并执行其中的初始化,参考loader0
5.然后才到子类的非静态变量初始化,参考loader9、11、12
6.最后才是子类的构造器方法的调用,并执行其中的初始化,参考loader7
父类静态变量->子类静态变量->父类非静态变量->父类构造器->子类非静态变量->子类构造器
按照《Java编程思想》所描述的是:
step1:首次创建类的对象时,Java的解释器必须找到类的路径!定位类的class字节码文件
step2:载入类的Class文件,加载的时候会创建一个Class的模板对象,有关静态初始化的所有操作都会在这个时候执行,因此所有的静态初始化只会在Class对象加载的时候进行一次!
step3:当用new 创建对象的时候,首先会在堆上为对象分配足够的内存空间
step4:这块存储空间会被清0,这就自动地将对象中所有基本类型数据都设置成了默认值,引用类型也置为null
step5:执行所有字段定义处的显式初始化操作。
step6:执行构造器(这里会先执行父类的(如果有父类)在执行子类的构造器)。
关于数组初始化
- 在网上看到一个有趣的回答:
- 不知道说的是什么语言…也不知道除了HotSpot虚拟机别的虚拟机会不会有这样的操作出现。但是hotspot虚拟机确实是没有这个操作。。。
- 还是那句话!一切的对象都在堆中!
- new可以省略,那是因为编译器会自动给我们补上这个new
- 另外注意实例保存的其实是数组的引用,而实例和实例之间是可以互相赋值的,那么就相当于引用的值拷贝!
int[] a = {1,2,3,4};
int[] b = a;
-
那么a和b指向的是堆中同一个数组对象,操作的时候也是操作的同一个数组对象!除非再new一个新数组!(入下图,arr1将引用传给arr2,然后对arr2中的每个元素+1,再回头输出arr1,发现arr1数组中的元素都改变了!)
-
基本类型的数组和Integer类型数组可以这样初始化↓
【end】