一、定义
1、静态代码块(静态初始化块,静态域):
使用static关键字和{}声明的代码块,格式如下:
public class People{
static{
System.out.println("静态代码块");
}
}
作用:
需要在项目启动时就执行的代码,一般放在静态代码块中。比如一个项目启动时需要加载大量的配置文件,静态资源等信息。并且静态代码块指初始化一次,可以有效的避免内存空间的浪费。
注意:静态代码块只能存在于类中,不能存放在任何方法中。
静态代码块是在类加载的时候就执行的(主动执行)。
普通方法需要通过加载类之后,通过new创建对象,然后通过对象调用该方法(被动执行)。
静态方法,在类加载时,静态方法也已经加载了,但是我们必须要通过类名或者对象名才能访问(被动执行)。
2、静态方法
使用static关键字修饰的方法:
public class People{
public static void method(){
System.out.println("静态方法");
}
}
3、静态变量(静态域,类变量):
使用static关键字修饰的变量,它是属于类所有,在jvm的静态区分配空间
public static int flag = 1;
private static String name = "张三";
4、非静态代码块(非静态域,初始化块,构造代码块)
在java类中使用{}声明的代码块,和静态代码块的区别是少了static关键字,格式如下:
{
System.out.println("构造代码块");
}
作用:
和构造函数的作用类似,都可以对对象进行初始化。
只要创建一个对象,非静态代码块都会执行一次。但是,构造函数则不一定每个对象建立时都执行,在多个构造函数情况下,建立对象时传入的参数不同则初始化使用对应的构造函数。因此,可进行创建对象次数的统计。
5、非静态变量(普通变量,实例域)
public int age = "1863";
public String name = "曹操";
public boolean flag;
6、构造函数(构造器,构造方法)
(1)和类同名,无返回值。普通函数可以和构造函数同名,但是必须带有返回值。
(2)主要用于在类的对象创建时定义初始化的状态。
(3)构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用(主动调用);而一般的方法是在程序执行到它的时候被调用的(被动调用);
public class User{
public int age;
public String name;
public User(){
System.out.println("构造方法");
age = 1862;
name = "曹操";
}
}
二、执行时机
首先,明白程序的两个阶段,加载阶段和执行阶段。程序先加载,然后再执行。
注意:
1.静态的东西和类相关联,非静态的东西和对象相关联。
2.非静态的东西都会在某个类创建的每个对象中占有内存空间
3.静态的的东西为这个类所有,所有根据这个类创建的对象共享这个类静态的东西。
加载阶段:java程序编译成.class文件,执行类相关资源(静态代码块,静态变量)
执行阶段:在内存中分配空间,执行对象相关的资源(非静态代码块,非静态变量,构造函数)
1、 静态代码块,静态变量
在类的 加载阶段 执行,并且只加载一次。静态静态代码块和静态变量它们之间的加载顺序从上到下依次执行,谁先声明谁先执行。
2、非静态代码块,非静态变量
在 执行阶段 ,根据对象实例化时执行。每次创建对象都会执行。它们之间的执行顺序也是从上到下依次执行,谁先声明谁先执行。
3、构造方法
在 执行阶段 ,根据对象实例化时执行。但是会根据对象创建时的方式,调用对应的构造方法。在非静态代码块和非静态变量执行之后才会执行。
总结:
大体的初始化顺序为:
(静态代码块,静态变量)>>(非静态代码块,非静态变量)>>构造方法
注意:
在一个括号内的,执行顺序不分先后,都是谁先声明谁先执行。
这只是一个大概的顺序,下面会讲解一些实例。
三、重点来了,下面是例子
首先看一个简单的实例:
实例一:
public class SimpleDemo {
//一个静态变量
private static int a = 10;
//一个普通变量
private int b = 20;
//无参构造函数
public SimpleDemo(){
System.out.println("无参构造函数, a="+a+", b="+b);
b = b + 1;
}
//构造代码块
{
System.out.println("构造代码块, a="+a+", b="+b);
a = a + 1;
}
//静态代码块
static{
System.out.println("静态代码块, a = "+a);
}
//普通方法,其中包含一个普通的代码块
public void say(){
{
System.out.println("普通代码块, a="+a+", b="+b);
}
}
//普通方法
public void show(){
System.out.println("普通方法, a="+a+", b="+b);
}
public static void main(String[] args) {
System.out.println("执行了main方法");
System.out.println("---------------");
SimpleDemo simple = new SimpleDemo();
simple.say();
System.out.println("---------------");
SimpleDemo demo = new SimpleDemo();
demo.show();
}
}
根据上面得出的结论:
(静态代码块,静态变量)>>(非静态代码块,非静态变量)>>构造方法
可以写一下上述代码的运行结果:
结果分析:
首先注意一点,如果只写main方法,其他什么都不写,那么只执行静态资源,例如:
public static void main(String[] args) { }
结果:
所以,程序有入口mian方法执行时,都会执行静态资源。
- 首先加载静态资源,a变量和static代码块执行,并且只执行一次。并且不能访问非静态资源b。
- 执行非静态资源,非静态代码块,非静态变量,
//构造代码块
{
System.out.println("构造代码块, a="+a+", b="+b);
a = a + 1;
}
静态变量属于类所有,a=11;非静态变量为对象所有,b=20;所以会出现结果:
3. 执行无参构造函数,
//无参构造函数
public SimpleDemo(){
System.out.println("无参构造函数, a="+a+", b="+b);
b = b + 1;
}
4.然后,执行simple.say()方法,输出:
5.然后上述程序,创建一个demo对象,进行对象实例化,由于这个类已经被加载过,所以demo对象创建时,只会执行非静态资源和构造方法。
a属于静态资源,属于类所有,所以a的值,还是经过操作之后的值。但是b的值是属于对象的,simple对象中属性b的值为21,但是demo对象中b的值,会在demo对象初始化时,初始化为20;
实例二:
这是一道阿里的面试题:
public class SingleSort {
public static int k = 0;
public static SingleSort t1 = new SingleSort("t1");
public static SingleSort t2 = new SingleSort("t2");
public static int i = print("i");
public static int n = 99;
public int j = print("j");
{
print("构造块");
}
static{
print("静态块");
}
public SingleSort(String str){
// System.out.print("Test1: ");
System.out.println((++k)+":"+str+" i="+i+" n="+n);
++i;
++n;
}
public static int print(String str){
// System.out.print("print: ");
System.out.println((++k)+":"+str+" i="+i+" n="+n);
++n;
return ++i;
}
public static void main(String[] args) {
SingleSort singleSort = new SingleSort("init");
}
}
结果:
在这个类中,k的作用是计数,如果看不懂的话,可以把代码中的两行注释解注,这样的结果为:
- 首先,先加载资源没有错,但是这里执行到:
public static SingleSort t1 = new SingleSort("t1");
这行代码会确定去调用new SingleSort(“t1”); 它会先去创建一个实例对象,也就是说进入到创建实例对象过程中。但是,这行代码还没执行完,等把new SingleSort(“t1”);这个对象创建完成后,赋给t1,这行代码才算结束。
在创建实例对象的过程中,从这个类的第一行开始,不会执行静态资源,会执行非静态代码块,非静态变量,构造函数等,所以会执行下列代码:
//首先执行
public int j = print("j"); //它会输出 print: 1:j i=0 n=0
//然后执行
{
print("构造块"); //它会输出 print: 2:构造块 i=1 n=1
}
//最后执行,它会输出 Test1: 3:t1 i=2 n=2
public SingleSort(String str){
System.out.print("Test1: ");
System.out.println((++k)+":"+str+" i="+i+" n="+n);
++i;
++n;
}
注意:变量i,n会先声明,也就是只会执行等号左边的内容,等号右边的先不会执行。
public static int i = print("i"); public static int n = 99;
所有i,n的值会先初始化为0。
然后,到此,这句话才执行完成:
public static SingleSort t1 = new SingleSort("t1");
之后,和上边一样,去执行:
public static SingleSort t2 = new SingleSort("t2");
然后接着去执行静态资源,结果的第7行,它执行的是:
public static int i = print("i");
之后,执行:
public static int n = 99;
到此时n才被初始化为99,并且是静态的,属于类的。接下来该执行静态代码块,也就是结果的第8行,
之后才去执行:
SingleSort singleSort = new SingleSort("init");
结果的9,10,11行都是在main方法中,创建new SingleSort(“init”);实例对象的结果。
实例三:
public class SubClass extends Parent {
/**
* 静态代码块是在类加载时自动执行的,非静态代码块是在创建对象时自动执行的代码,
* 不创建对象不执行该类的非静态代码块。且执行顺序为:
* 静态代码块---非静态代码块---构造函数。
*
* 静态代码块是自动执行的; 静态方法是被调用的时候才执行的.静态代码块
* 可用来初始化一些项目最常用的变量或对象;
* 静态方法可用作不创建对象也可能需要执行的代码
*/
// 静态变量
public static String s_StaticField = "子类--静态变量";
// 变量
public String s_Field = "子类--变量";
// 静态初始化块
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
// 初始化块
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
// 构造器
public SubClass() {
System.out.println("子类--构造器");
}
// 程序入口
public static void main(String[] args) {
new SubClass();
}
}
class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
// 变量
public String p_Field = "父类--变量";
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
// 构造器
public Parent() {
System.out.println("父类--构造器");
}
}
执行结果:
这个实例涉及到子类继承父类时,静态资源,非静态资源,构造方法之间执行的顺序。
这类初始化问题,只要记住,有了父类才有子类,没有父类哪来的子类,父类优先于子类,即一般情况下:父类>>子类。
另外,静态资源由于是加载时刻就会执行,而且代码必须先都加载完成之后,才可能去执行,所以父类的静态资源和子类的静态资源都会在所有的非静态资源前面执行。
即:
实例四:(最难的一道)
public class StudentSort extends Person{
private static int num = 20;
private String school;
{
System.out.println("student初始化块");
school = "大学";
System.out.println(school);
}
static {
System.out.println("student静态块");
}
public StudentSort(){
}
public StudentSort(String name,int age){
super(name,age);
}
public static void main(String[] args) {
Person p1 = new StudentSort();
Person p2 = new StudentSort("张三",40);
}
}
class Person{
private String name;
private static Person person = new Person();
private static int n = 10;
private int age = setAge();
{
System.out.println("初始化块");
System.out.println("age = "+ age);
}
static {
System.out.println("静态块");
System.out.println("n = "+ n);
}
public static int setAge(){
return n++;
}
public Person(){
System.out.println("默认构造函数");
System.out.println("age = "+ age);
}
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println("有参构造函数");
System.out.println("age = "+age);
}
}
执行结果:
根据上面总结的,执行顺序:
父类静态资源 >> 子类静态资源 >> 父类非静态资源 >> 父类构造方法 >> 子类静态资源 >> 子类构造方法
注意:
- 当执行到下面这句话时。回去创建一个Person实例对象,所以回去执行Person类的非静态资源和构造方法
private static Person person = new Person();
- n的值,在没有加载的时候,即没有执行静态资源的时候,会先在栈内存中有一个n的变量名字,它的值会被默认为0。只有当执行静态资源的时候,才会在静态区设置它的值。
故执行效果图为: