颠覆你思维的静态加载顺序

一.关键字static


1.static可以修饰成员变量、成员方法,不能修饰构造函数,构造函数是在创建对象时使用的。而static是类的属性。

2.当一个函数没有访问实例变量数据时,才能被static修饰。因为静态不能访问非静态的,在静态加载时,有可能还没有创建对象,成员变量和成员方法都是和对象有关的。


二.静态函数注意事项


  1.被static修饰的函数称为类函数,非静态函数也称为实例函数
2.static可以修饰成员变量、成员方法,但是不能修饰构造函数

3.静态函数是在类加载的时候就在内存中加载完成,可以直接运行的函数。(只要是函数最终都要进入栈内存,方法是什么时候调用,什么时候执行,不调用不执行。)

4.非静态函数在类加载完成之后,通过new在堆中开辟内存空间,然后通过对象调用函数。

5.静态不能访问非静态

因为静态函数在类加载完成可以直接通过类名调用,而这时有可能还没有创建对象,非静态函数是依赖对象的。

6.非静态可以访问静态

因为当非静态函数可以运行,那么说明类中一定创建了对象,说明对象所在的类已经加载完成,那么非静态就可以访问静态。


三.类和对象的加载过程


1.类加载过程:

① JVM启动,加载所需要的class文件
② JVM加载class文件时,会把所有的静态内容(静态成员变量、静态方法、静态代码块)都先加载到方法区中的静态区中。
③ 静态加载完成之后,JVM开始给所有的成员变量默认初始化,静态成员变量开辟空间。
④ 当给类中的所有静态成员变量默认初始化完成,开始按照代码的顺序依次执行(遇到静态代码块就执行,遇到静态成员变量就显示初始化)
⑤ 静态都执行完毕,类才彻底加载完成


2.对象的加载过程:


① 当类加载完成,使用new关键字创建对象,在堆给对象分配内存空间
② 给对象所属的类的非静态成员变量分配空间并进行默认初始化
③ 在JVM自动调取构造函数时先执行隐式三步
  • super()区访问父类构造,对父类进行初始化
  • 给非静态成员变量进行显示赋值
  • 执行构造代码块
④ 在执行构造函数中的其它代码
⑤ 构造函数执行完毕,对象创建完成。


四.静态内存图解


StaticDemo类

package cn.jason03;
/**
 * 这是static的用法
 * @author Jason
 *
 */
class Demo {
	int x;
	static int y = 3;
	// 静态代码块
	static {
		System.out.println("静态代码块");
	}
	// 定义构造代码块
	{
		System.out.println("我是构造代码块");
		System.out.println("x=" + x);
	}
	//构造函数
	public Demo() {
	}
	
	static void print() {
		System.out.println("y=" + y);
	}

	void show() {
		System.out.println("x=" + x + "  y=" + y);
	}
}

class StaticDemo {
	public static void main(String[] args) {
		//类名调用print方法
		Demo.print();
		//创建对象
		Demo d = new Demo();
		//给成员变量x赋值
		d.x = 10;
		//用对象调用show方法
		d.show();
	}
}


StaticCode类

package cn.jason03;

/**
 * 静态属性执行顺序1
 * 
 * @author Jason
 */
class StaticCode {
	static int x = 10;
	static int y = show();

	static int show() {
		System.out.println("show..........x = " + x);
		System.out.println("show..........y = " + y);
		return 100;
	}

	// 静态代码块
	static {
		System.out.println("静态代码块运行....y= " + y);
	}

	void print() {
		System.out.println("....................");
	}
}

public class StaticCodeDemo {
	public static void main(String[] args) {
		new StaticCode().print();
	}
}

内存图解:



五.颠覆思维的小题目

第一道题:

package cn.jason07;

public class StaticInitTest {
	/*static {
		value = 10;
		print("静态代码块");
	}*/
	static int value = getValue();
	static { // 通过静态初始化块为name变量初始化
		System.out.println("静态代码块中value的值=" + value);
		name = "周杰伦";
	}

	static {
		value = 10;
		print("静态代码块");
		
	}

	static String name = "林青霞"; // 定义静态变量

	public static void print(String s) {
		System.out.println("value的值=" + value + " " + "名字是:"+name);
	}

	public static int getValue() {
		return ++value;
	}

	public static void main(String[] args) {

		System.out.println("value的值:" + StaticInitTest.value);
		System.out.println("name的值:" + StaticInitTest.name);

	}
}
输出结果:

静态代码块中value的值=1
value的值=10 名字是:周杰伦
value的值:10
name的值:林青霞


package cn.jason07;

public class StaticInitTest {
	static {
		value = 10;
		print("静态代码块");
	}
	static int value = getValue();
	static { // 通过静态初始化块为name变量初始化
		System.out.println("静态代码块1中value的值=" + value);
		name = "周杰伦";
	}

	/*static {
		value = 10;
		print("静态代码块2");
		
	}*/

	static String name = "林青霞"; // 定义静态变量

	public static void print(String s) {
		System.out.println("value的值=" + value + " " + "名字是:"+name);
	}

	public static int getValue() {
		return ++value;
	}

	public static void main(String[] args) {

		System.out.println("value的值:" + StaticInitTest.value);
		System.out.println("name的值:" + StaticInitTest.name);

	}
}
输出结果是:

value的值=10 名字是:null
静态代码块1中value的值=11
value的值:11
name的值:林青霞


注意:

  • 静态函数是在类加载的时候就在内存中加载完成,可以直接运行的函数。静态属性优先于对象存在的,静态属性是类所共享的,静态成员变量赋值可以在定义静态变量之前,但是输出不能在定义静态变量之前。(只要是函数最终都要进入栈内存,方法是什么时候调用,什么时候执行,不调用不执行。)
  • 对于对象而言,栈内存的引用地址不是对象,仅仅是为堆内存中对象分配内存空间时随机分配的地址值而已,真正的对象在堆内存。指向只是方便使用成员属性。
  • 凡是对于静态可以不用创建对象,直接可以用类名调用。凡是对于非静态,如要访问非静态成员方法和成员属性,那么需要想方设法创建对象来访问。(为什么说想方设法呢?比如非静态的内部类,如果非静态内部类被private修饰,那么只能在外部类里创建对象来访问被private修饰的内部类属性和行为,在外部类之外是不能访问的。)

第二道题:

package cn.jason01;

/**
 * 静态加载顺序
 * 
 * @author Jason
 *
 */
public class StaticTest {
	public static void main(String[] args) {
		staticFunction();
		show();
	}

	static StaticTest st = new StaticTest();

	static {
		System.out.println("1");
	}

	{
		System.out.println("2");
	}

	public StaticTest() {
		System.out.println("3");
		System.out.println("构造函数中的.....a=" + a + " b=" + b);

	}

	public static void staticFunction() {
		System.out.println("4");
//		System.out.println("b=="+b);
	}

	int a = 110;

	static int b = 112;

	public static void show() {
		System.out.println("show..........b=" + b);
	}
}

输出结果:

2
3
构造函数中的.....a=110 b=0
1
4
show..........b=112


第二道题解析:
①运行时,JVM先加载main函数所在的类,也就是StaticTest类。加载就是把StaticTest类的字节码全部放在方法区,与此同时只有静态成员变量先默认初始化了,其他都没有执行只是放在里面。然后按照代码顺序进行执行。所以第一步先执行static StaticTest st = new StaticTest()。

②在堆内存new StaticTest()开辟一个空间,随机分配十六进制地址值,非静态成员变量这个空间在开辟一个小空间,默认初始化值,int a=0。

③现在JVM自动调用构造函数,所以public StaticTest() {}进栈内存,先执行隐式三步。隐式三步第二步给非静态成员显示初始化,这是int a=110;隐式三步第三步就是执行构造代码块(反编译之后一目了然),所以先输出2。隐式三步执行完,现在执行构造代码块中的其它代码,所以输出3,构造函数中的.........a=110,b=0

④按照代码顺序向下执行,那么开始执行静态代码块,输出1

⑤给静态变量b显示初始化,这时b=112,然后才调用方法,一定是先给静态显示初始化完毕才调用方法的(对于本题是这样的),输出4.如果把staticFunction函数中的注释放开,如果b=112,那么说明静态变量显示赋值在调用方法之前完成。放开注释结果正是b=112,说明验证是正确的。

⑥在调用show方法,这时也能验证执行时按照代码的顺序执行的。输出show..........b=112


总结:

1.加载时只对静态成员变量默认初始化值,其它内容都只加载不执行。

2.执行是按照代码顺序执行的。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值