0x00 背景知识
1、虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法(下文将这三种统称为静态代码块)进行一次初始化
具体过程是:
①装(加)载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区中,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,之后可以用Class对象进行相关的反射操作。
②连接分为三个子步骤
验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
解析: 把类中的符号引用转换为直接引用
③初始化为为类的静态变量赋予正确的初始值
2、类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分,然后是父类的构造方法;再执行本类继承的子类的初始化块,最后是子类的构造方法
3、类实例销毁时候,首先销毁子类部分,再销毁父类部分
成员变量和静态变量的区别:
1,成员变量所属于对象。所以也称为实例变量。
静态变量所属于类。所以也称为类变量。
2,成员变量存在于堆内存中。
静态变量存在于方法区中。
3,成员变量随着对象创建而存在。随着对象被回收而消失。
静态变量随着类的加载而存在。随着类的消失而消失。
4,成员变量只能被对象所调用 。
静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。
静态代码块:就是一个有静态关键字标示的一个代码块区域。定义在类中。
作用:可以完成类的初始化。静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。(静态成员就是静态代码块)
//静态代码块:在java中使用static关键字声明的代码块。静态块用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。由于JVM在加载类时会执行静态代码块,所以静态代码块先于主方法执行。
//如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。
//注意:1 静态代码块不能存在于任何方法体内。2 静态代码块不能直接访问静态实例变量和实例方法,需要通过类的实例对象来访问。
class Code{
{
System.out.println("Code的构造块");
}
static{
System.out.println("Code的静态代码块");
}
public Code(){
System.out.println("Code的构造方法");
}
}
public class CodeBlock03{
{
System.out.println("CodeBlock03的构造块");
}
static{
System.out.println("CodeBlock03的静态代码块");
}
public CodeBlock03(){
System.out.println("CodeBlock03的构造方法");
}
public static void main(String[] args){
System.out.println("CodeBlock03的主方法");
new Code();
new Code();
new CodeBlock03();
new CodeBlock03();
}
}
/*
CodeBlock03的静态代码块
CodeBlock03的主方法
Code的静态代码块
Code的构造块
Code的构造方法
Code的构造块
Code的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
CodeBlock03的构造块
CodeBlock03的构造方法
*/
构造代码块:类中的独立代码块,与对象相关,对象调用一次就执行一次。
作用:给所有的对象进行初始化。
//构造块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。
public class CodeBlock02{
{
System.out.println("第一代码块");
}
public CodeBlock02(){
System.out.println("构造方法");
}
{
System.out.println("第二构造块");
}
public static void main(String[] args){
new CodeBlock02();
new CodeBlock02();
new CodeBlock02();
}
}
/*
*
执行结果:
第一代码块
第二构造块
构造方法
第一代码块
第二构造块
构造方法
第一代码块
第二构造块
构造方法
*/
0x01先来几个小测试:
测试一
class Singleton {
private static Singleton sin = new Singleton();
public static int counter1;
public static int counter2 = 0;
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getInstance() {
return sin;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = Singleton.getInstance();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
测试二:
class Singleton {
public static int counter1;
public static int counter2 = 0;
private static Singleton sin = new Singleton();
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getInstance() {
return sin;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = Singleton.getInstance();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
测试三:
class Singleton {
private static Singleton sin = new Singleton();
public static int counter1;
public static int counter2 = 0;
public Singleton() {
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = new Singleton();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
测试四:
class Singleton {
public static int counter1;
public static int counter2 = 0;
private static Singleton sin = new Singleton();
{
counter1=3;
}
public Singleton() {
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = new Singleton();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
结果:
测试一:1,0
测试二:1,1
测试三:2,1
测试四:4,2
0x02类加载
测试一分析:
按类加载的流程走一遍:
①装(加)载类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区中,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构,之后可以用Class对象进行相关的反射操作。(都一样)
②连接分为三个子步骤
验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
测试一中:sin=NULL;counter1=0;counter2=0;
解析: 把类中的符号引用转换为直接引用
③初始化为为类的静态变量赋予正确的初始值
测试一中第一句:
private static Singleton sin = new Singleton();//执行后counter1=1;counter2=2;
第二句:public static int counter1;//执行后counter1=1;
第二句:public static int counter2 = 0;//执行counter2=0;
所以最后结果为1,0
测试二同理。
0x03类的实例化
类实例创建过程简单总结一下就是(new一个类的时候到底发生了什么?):
默认初始化(对成员赋0或NULL)—>进构造函数—>super()【父类也是该过程】—>显式初始化—>构造代码块—>构造函数中其它代码
class Singleton {
/*
*静态代码块(静态成员)
*/
private static Singleton sin = new Singleton();
public static int counter1;
public static int counter2 = 0;
/*
*构造代码块
*/
{
counter1=3;
}
/*
*造函数
*/
public Singleton() {
//类Singleton 的super class为object
super();
//构造函数中其它代码
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = new Singleton();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
根据上面总结的流程走一遍:
类加载阶段:跟测试一一样,该阶段结束后counter1=1;counter2=0;
类实例化阶段:
默认初始化(对成员赋0或NULL)//没有普通成员
—>进构造函数//开始进入Singleton()
—>super()【父类也是该过程】//进入super(),即Object加载
—>显式初始化//super()执行完后开始显示为成员赋值,本测试中没有普通的非static成员
—>构造代码块 //没有
—>构造函数中其它代码// counter1++;counter2++;结束时counter1=2,counter2=1
测试四也走一遍:
类加载阶段:跟测试一一样,该阶段结束后counter1=1;counter2=1;
类实例化阶段:
默认初始化(对成员赋0或NULL)//没有普通成员
—>进构造函数//开始进入Singleton()
—>super()【父类也是该过程】//进入super(),即Object加载
—>显式初始化//super()执行完后开始显示为成员赋值,本测试中没有普通的非static成员
—>构造代码块 //counter1=3;
—>构造函数中其它代码// counter1++;counter2++;结束时counter1=4,counter2=2
0x04 自测题
public class Farther
{
static
{
System.out.println("静态代码块 farther");
}
{
System.out.println("构造代码块 farther");
}
public Farther()
{
System.out.println("call farther counter1="+Singleton.counter1+" counter2="+Singleton.counter2);
Singleton.counter1++;
}
}
class Singleton extends Farther {
static
{
System.out.println("静态代码块1 counter1="+Singleton.counter1 +" counter2="+Singleton.counter2);
}
public static int counter1;
public static int counter2 = 1;
static
{
System.out.println("静态代码块2 counter1="+counter1 +" counter2="+counter2);
}
private static Singleton sin = new Singleton();
static
{
System.out.println("静态代码块3 counter1="+counter1 +" counter2="+counter2);
}
{
counter1++;
System.out.println("构造代码块 counter1="+counter1+" counter2="+counter2);
}
public Singleton() {
System.out.println("call Singleton");
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
//Singleton sin = Singleton.getInstance();
Singleton sin = new Singleton();
System.out.println("counter1="+sin.counter1);
System.out.println("counter2="+sin.counter2);
}
}
/*
静态代码块 farther
静态代码块1 counter1=0 counter2=0
静态代码块2 counter1=0 counter2=1
构造代码块 farther
call farther counter1=0 counter2=1
构造代码块 counter1=2 counter2=1
call Singleton
静态代码块3 counter1=3 counter2=2
构造代码块 farther
call farther counter1=3 counter2=2
构造代码块 counter1=5 counter2=2
call Singleton
counter1=6
counter2=3
*/
执行过程就是:
1.先加载 Farther
打印了:“静态代码块 farther”
2.后加载Singleton
先打印了:“静态代码块1 counter1=0 counter2=0“
”静态代码块2 counter1=0 counter2=1”
静态代码块: private static Singleton sin = new Singleton();调用了Singleton的构造函数,
先执行super(),所以“call farther counter1=0 counter2=1”被打印;
再执行构造代码块,所以“构造代码块 counter1=2 counter2=1”被打印
再执行构造函数中其他的代码,所以“call Singleton”被打印,构造函数调用结束。
接着执行静态代码块,所以“静态代码块3 counter1=3 counter2=2”被打印。
3.在main函数中调用Singleton sin = new Singleton();
默认初始化
—>进构造函数
—>super()【父类也是该过程,可看做该过程的一次递归】,//所以“构造代码块 farther”和“call farther counter1=3 counter2=2”被打印
—>显式初始化
—>构造代码块 //所以“构造代码块 counter1=5 counter2=2”被打印
—>构造函数中其它代码 //所以“call Singleton”被打印
最后打印:counter1=6
counter2=3