目录
1、static的特性
-
static修饰的是类资源,其用法主要是方便在没有创建对象的情况下调用其变量即方法;
-
static不能调用非static的方法,反过来OK;
-
对于用static修饰的成员变量及方法,没有this指针,不用依赖对象的创建,只要类被加载了,就可以访问;
-
被static修饰的静态成员变量和方法存储在共享内存方法区,也称为永久代;
2、作用及使用
-
1、方便在没有创建对象的情况下来进行调用(方法/变量),是没有this的,提高调用效率;
-
2、静态变量是类共享的,只有一份,节约内存;
-
3、仅在类加载的时候加载一次进行初始化,可以用静态代码块来优化程序预加载性能;
使用建议
(1)将类的公共属性,只需要一次初始化和不经常变的变量或者对象操作放在静态代码块中,这样避免重复的生成,造成内存空间的浪费;
(2)
如果有些资源比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,除了使用Spring容器事件监听方法外,就可以使用静态代码块,这种代码是主动执行的。
(3)工具类:如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,StringUtil类工具类等,就可以使用静态方法。
3、static的加载
static比较关键的作用是静态代码块,用来
优化程序的性能。static代码块可以放在类的任何地方,并且可以有多个静态代码块。
3.1、java对象的生命周期
一个java对象的类的生命周期及加载过程如下:
(1)、加载:通过类加载器加载源文件.java;
(2)、编译:将源文件.java编译生成*.class文件;
(3)、连接:
-
验证:verification-是否符合字节码的规范-cafebaby,字节码-元数据-文件是否准确,有没有危害jvm的代码;
-
准备 :preparation -为类变量【静态变量】分配内存且赋默认初值:0值;
-
public static int a = 10; //此时 a = 0;
-
-
解析:Resolution- 将符号引用变为直接引用的过程,常量引用类-接口-方法
(4)、初始化:Initialization
:
(java代码具体实际的值-static的值)-
-
static变量a赋java的初始值,a=10;
(5)、使用:Using :构造方法当我们使用new关键字的时候才调用;
(6)、卸载:Unloading,被jvm回收;
3.2、static变量加载时机
静态代码块主要在类生命周期的
连接和
初始化两个阶段执行,此时jvm会扫描这些静态变量,并把它们放入到方法区:
-
静态变量在 准备阶段默认为0值;
-
在 初始化阶段进入java程序代码,赋java初始值,也可能是0值;
如果有多个静态代码块,加载顺序将按代码块的定义顺序进行加载,并且只会加载一次,且在非静态代码块之前执行。静态成员的初始化阶段,具体分两种情况:
-
是否显示初始化;
-
是否 被final修饰;
3.2.1、显示初始化的static变量
静态代码块在初始化阶段执行:本质上,static int num1 =2 分两步完成:
static int num1; //准备阶段 赋0值
static{
num1 = 2; //初始化阶段:静态代码块执行,对静态变量真正赋值2
}
3.2.2、没有显示的初始化
private static int num2; // 没有显示初始化 - 准备阶段 0值,之后不会再变,为0;
3.2.3、被final修饰
private static final int num3 = 12 //被final修饰 - 准备阶段就是12,因为值已经确定不会再改变;
被final修饰的static变量,在类加载的准备阶段num3的值会被赋值为12而不是0;这时因为num3被final修饰,此时“类字段的字段属性表中就存在ConstantValue”,而准备阶段就会被赋上所指定的值。
4、静态内部类
修饰内部类 static修饰类的时候变为了
静态内部类。
静态内部类与非静态内部类的最大区别就是:
非静态内部类:在编译完成后隐含的保存了一个指向外部类的指针,在构造函数中默认添加了指向外部类的指针,所以非静态内部类可以访问外部类的所有成员;也就是说非静态内部类的创建依赖于外部类的创建,如果外部类没有创建对象,则内部类是无法创建对象的。
静态内部类:就没有这个依赖关系了,因为它没有此指向外部类的指针,这也就意味着:
-
静态内部类的创建不依赖于外部类,相当与一个静态方法;
-
静态内部类不能访问外部类的非静态成员变量;
5、实例练习
//父类:
public class Father {
//todo 多个static块,按定义的顺序执行;
static {
System.out.println("father static run1");
}
static { //静态代码块
System.out.println("father static run2");
}
{
System.out.println("father non-static run");
}
Father() {
System.out.println("Father constructor run");
}
}
//子类
public class Son extends Father {
//todo here 静态代码块可以提前初始化一个数据参数;数据库的sql的驱动会自己去注册报道一下 永久区
//Class.forName("com.mysql.jdbc.Driver");
static {//静态代码块
System.out.println("son static run");
}
{
System.out.println("son non-satatic run");
}
Son() {
System.out.println("Son constructor ");
}
public static void main(String[] args) {
System.out.println("main is run");
new Son();
}
子类和父类的代码块执行运行结果:
father static run1 //父类的静态代码块按顺序执行1;
father static run2 //父类的静态代码块按顺序执行2;
son static run //子类的静态代码块按顺序执行2;
main is run
father non-static run //父类的非静态代码块执行;
Father constructor run //父类的构造函数执行;
son non-satatic run //子类的非静态代码块执行;
Son constructor //子类的构造函数执行;
从上面示例可以得到:
-
多个static代码块,按照定义的顺序加载;
-
先执行父类静态代码块然后再执行子类代码块;
-
静态代码块在非静态代码块之前执行;
-
构造函数在最后执行;
6、static在不同语言区别
C语言:
(1)定义一个静态变量,就是改变了它的生命作用域,全局可见,作用域不在栈上,也可防止同名的出现;
(2)定义一个静态函数,被定义的函数的作用域限定在当前的文件模块中全局使用;
(3)static全局变量初始化为0,且只初始化一次;
C++(唯一性):
(1)成员变量属于类,不属于任何对象,需在类外初始化;
(2)成员函数,没有this指针,所有对象共享;
(3)static函数不能声明为虚函数;
注意:java中static的关键字不会改变变量的作用域,这个和C++要区分开;主要影响加载时机和生命周期,属于类资源,且不允许修饰局部变量。
7、问题思考
问题一:静态代码块和非静态代码块的区别
相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。
-
静态代码块只在对象初始化时执行一次,之后不再执行;
-
而非静态代码块在每new一次就执行一次。
-
非静态代码块可在普通方法中定义(不过作用不大),而静态代码块属于类资源不行。
问题二:非静态代码块与构造函数的区别:
非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化相同的初始值,而构造函数是给对应的对象初始化,更具有针对性,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。