一.关键字static
1.static可以修饰成员变量、成员方法,不能修饰构造函数,构造函数是在创建对象时使用的。而static是类的属性。
2.当一个函数没有访问实例变量数据时,才能被static修饰。因为静态不能访问非静态的,在静态加载时,有可能还没有创建对象,成员变量和成员方法都是和对象有关的。
二.静态函数注意事项
1.被static修饰的函数称为类函数,非静态函数也称为实例函数
2.static可以修饰成员变量、成员方法,但是不能修饰构造函数
3.静态函数是在类加载的时候就在内存中加载完成,可以直接运行的函数。(只要是函数最终都要进入栈内存,方法是什么时候调用,什么时候执行,不调用不执行。)
4.非静态函数在类加载完成之后,通过new在堆中开辟内存空间,然后通过对象调用函数。
5.静态不能访问非静态
因为静态函数在类加载完成可以直接通过类名调用,而这时有可能还没有创建对象,非静态函数是依赖对象的。
6.非静态可以访问静态
因为当非静态函数可以运行,那么说明类中一定创建了对象,说明对象所在的类已经加载完成,那么非静态就可以访问静态。
三.类和对象的加载过程
1.类加载过程:
① JVM启动,加载所需要的class文件② JVM加载class文件时,会把所有的静态内容(静态成员变量、静态方法、静态代码块)都先加载到方法区中的静态区中。
③ 静态加载完成之后,JVM开始给所有的成员变量默认初始化,静态成员变量开辟空间。
④ 当给类中的所有静态成员变量默认初始化完成,开始按照代码的顺序依次执行(遇到静态代码块就执行,遇到静态成员变量就显示初始化)
⑤ 静态都执行完毕,类才彻底加载完成
2.对象的加载过程:
② 给对象所属的类的非静态成员变量分配空间并进行默认初始化
③ 在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.执行是按照代码顺序执行的。