今天在学习一些jvm内存划分相关知识的时候涉及到了一些静态变量的知识,回忆起来总觉得不够系统,所以又复习总结了一下Java中static关键字的相关知识点。static关键字作为Java基础知识中比较难理解的一个点,一直学习得不够深刻,也比较容易遗忘,这篇博客会尽量详尽地归纳相关知识。
1. static的作用
1.1 修饰方法
static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。
——《Java编程思想》P86
这句话说明了static修饰的方法(静态方法),可以通过 类名.方法名 直接调用(当然也可以像普通方法一样通过实例化出来的对象进行调用),static修饰方法的主要用途也在于此。好处在于,避免实例化对象造成jvm堆资源的浪费(实例对象在jvm中存储在堆中),当然是在这个对象被实例化出来的作用仅仅是为了调用该方法的情况下。
由此也可以联想到,为什么所有的main方法必须要用static修饰,就是因为main方法作为程序执行的入口,再被jvm调用的时候根本没有事先实例化出来类的对象。
此外,在静态方法中,只能调用静态方法和静态成员变量,不允许调用非静态方法和非静态成员变量。
public class Test {
void printMethod(){
System.out.println(1);
}
static void staticMethod(){
printMethod(); //语法报错:Cannot make a static reference to the non-static method printMethod() from the type Test
}
public static void main(String args[]){
Test.staticMethod();
}
}
上面的代码中,在main方法中调用了静态方法staticMethod(),而静态方法中调用了非静态方法printMethod(),整个过程中没有Test的实例对象产生,而我们知道非静态方法的调用必须依赖于实例对象,产生了矛盾,所以Java不允许在静态方法中调用非静态方法。成员变量亦是如此,非静态成员变量作为实例对象的属性,自然也不可能在没有对象产生的情况下进行调用。
1.2 修饰变量
用static关键字修饰的变量(静态变量)与非静态变量的区别是:静态变量在该类首次被加载的时候被初始化(jdk8以前存放在方法区(永久代),jdk8之后元空间代替了永久代,静态变量存放在堆中),为该类所有的实例对象所共享,内存中仅存在一份副本,而非静态变量则是具体的实例对象的属性,在对象被创建的时候初始化,每个对象独享一份副本且互不影响。
public class Test {
//静态成员变量
static String staticStr = "this is a static string";
//非静态成员变量
String nomalStr = "this is a nomal string";
public static void main(String[] args){
System.out.println(nomalStr); //报错:Cannot make a static reference to the non-static field nomalStr
System.out.println(staticStr);
}
}
有趣的是,我们由此可以从另一个角度解释为什么静态方法中不能调用静态成员变量,例如上面的代码,由于静态方法main()的执行不需要依赖于对象的创建,而没有对象的创建jvm也不会去初始化非静态变量,故无法调用。
public class Test {
static int num =1;
//不同的对象共享静态成员变量
public static void main(String[] args){
Test t1 = new Test();
System.out.println(++t1.num); //2
Test t2 = new Test();
System.out.println(t2.num); //2
}
}
1.3 static代码块
static比较进阶的作用就是用来修饰代码块了,static代码块的特点是只会在类被类加载器加载的时候执行一次,而在初始化和创建对象的时候都不会再执行,适当使用static代码块可以减少jvm内存的占用。
public class Test {
void isPass(int score){
//及格线
int passLevel = 60;
System.out.println(score >= passLevel ? "及格" : "不及格");
}
public static void main(String[] args){
Test t1 = new Test();
t1.isPass(90); //第一次调用会产生一个变量passLevel
Test t2 = new Test();
t2.isPass(59); //第二次调用会再次产生一个变量passLevel
}
}
变量 passLevel 在两次调用中初始化了两次,存储在Java虚拟机栈对应栈帧的局部变量表中,造成资源的冗余。不如改成:
public class Test {
//及格线
static int passLevel;
static {
passLevel = 60; //类加载的时候初始化一次
}
void isPass(int score){
System.out.println(score >= passLevel ? "及格" : "不及格");
}
public static void main(String[] args){
Test t1 = new Test();
t1.isPass(90);
Test t2 = new Test();
t2.isPass(59);
}
}
2. 归纳与注意
2.1 static修饰的成员变量或者方法对其访问权限并不产生任何影响。
2.2 在方法内部不允许定义静态变量
2.3 静态变量全局共享,仅在类加载的时候初始化一次
2.4 静态变量可以通过类名直接调用,也可以通过对象调用
2.5 静态方法中不允许调用非静态变量和方法,也不能使用this和super关键字