一、概念
被static关键字修饰的不需要创建对象去调用,直接根据类名就可以去访问。方便在没有创建对象的情况下来进行调用。
static 可以修饰 内部类、方法、代码块、变量。
二、用法
2.1 修饰内部类
static只能修饰内部类,普通类是不允许声明为静态的。著名的单例模式就是静态内部类。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
2.2 修饰方法
修饰方法的时候,其实跟类一样,可以直接通过类名来进行调用。
public static void main(String[] args)//main方法是所以方法的入口
2.3 修饰代码块
public class Father {
private static int i = 1;
private int j = 1;
static {
System.out.println("父类静态代码块,i:" + i);
}
{
System.out.println("父类普通代码块,j:" + j);
}
public Father() {
System.out.println("父类构造器");
}
}
public class Son extends Father {
private static int i = 2;
private int j = 2;
static {
System.out.println("子类静态代码块,静态属性 i:" + i);
}
{
System.out.println("子类普通代码块,普通属性 j:" + j);
}
public Son() {
System.out.println("子类构造器");
}
public static void main(String[] args) {
new Son();
}
}
output:
父类静态代码块,静态属性 i:1
子类静态代码块,静态属性 i:2
父类普通代码块,普通属性 j:1
父类构造器
子类普通代码块,普通属性 j:2
子类构造器
从变量已经被赋值的输出来看,说明成员变量是优先代码块初始化的。
类初始化顺序:
父类静态变量、静态代码块 ->
子类静态变量、静态代码块 ->
父类普通变量、普通代码块、构造函数 ->
子类普通变量、普通代码块、构造函数
2.4 修饰属性
被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象。
public class Car {
private String name;
private String engine;
public static int numberOfCars;
public Car(String name, String engine) {
this.name = name;
this.engine = engine;
numberOfCars++;
}
// getters and setters
}
@Test
public void whenNumberOfCarObjectsInitialized_thenStaticCounterIncreases() {
new Car("Jaguar", "V8");
new Car("Bugatti", "W16");
assertEquals(2, Car.numberOfCars);
}
static不能修饰方法中的变量,不然就不是类级别的了。原理需要从Java内存模型来分析。
三、深入分析static关键字
先简单的看下Java内存区域划分
3.1 PC寄存器(程序计数器)
线程私有。保存当前线程所执行的字节码的行号指示器,执行环境上下文 。java多线程就是通过对线程的轮流切换并分配处理器的执行时间的方式来实现的,所以每条线程为了切换后能恢复到正确的执行位置,每条线程都会有一个独立的程序计数器,各条线程之间互不影响,独立存储。
3.2 JAVA虚拟机栈(Java方法栈)
线程私有 。在每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。局部变量表包括了编译器可知的局部变量基本数据类型、对象引用和returnAddress类型(指向了一个字节码指令的地址)。
3.3 本地方法栈
线程私有。执行native方法。
3.4 Java堆
线程共享。存放对象实例。java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC堆。 java堆还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
3.5 方法区(静态区)
线程共享。存储class二进制文件,包含了虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
常量池是方法区的一部分,主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近Java语言层面的常量概念:声明为final的常量、基本类型常量池、字符串常量池。而符号引用则属于编译原理方面的概念。
jdk1.6字符串常量池在方法区中,1.7之后在堆中。运行时常量池一直在方法区中。
jdk1.8开始,移除永久代概念,方法区用元空间实现。
3.6 堆、栈和方法区存储数据的区别
栈:在方法中声明的变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束。存储局部变量值(基本数据类型),局部变量引用。
堆:存放引用类型的对象、类的非静态成员变量值(基本数据类型)、非静态成员变量引用。
方法区:存放class二进制文件,包含类信息、静态成员变量,常量池(String字符串和final修饰的常量值等),类的版本号等基本信息。静态成员变量是在方法区的静态域里面,而静态成员方法是在方法区的class二进制信息里面。
这就是局部变量不能够static修饰的原因:任何方法每次被线程调用,都会在栈中开辟新的空间。同一方法的不同线程执行,方法与方法之间互不影响,同一方法在不同栈中传递的形参值也是不用的,局部变量的初始化和赋值自然在栈中执行,故不能用 static 关键字来修饰局部变量。
参考链接:
https://baijiahao.baidu.com/s?id=1636927461989417537&wfr=spider&for=pc
https://blog.csdn.net/qq_36470686/article/details/82957221
https://www.baeldung.com/java-static