目录
static关键字
什么是 static 关键字?
在 Java 中,static 是一个修饰符,用于将类的某些成员(变量或方法)定义为静态的。静态成员是属于类本身的,而不是某个对象的。这意味着,不需要创建类的实例(对象),就可以直接通过类名来访问这些静态成员。
静态成员的存储:
static 修饰符修饰的变量和方法被存储在方法区的静态常量池中。由于这些静态成员属于类,因此它们是所有对象共享的。例如,静态变量共享相同的内存空间,而静态方法可以被所有对象调用。
为什么 Java 要设置 static 关键字?
设置 static 关键字的主要目的是为了方便类本身调用自己的方法和变量。如果方法和变量没有被 static 修饰,那么它们是非静态的,只有在创建对象之后,才会为这些成员分配内存空间,并且只能通过对象来调用它们。
假设我们的方法和变量上面没有static关键字。
若方法和变量属于非静态,只有在对象中才会被分配内存空间,只有对象才能调用。
假设我们的方法和变量上有static关键字。
如果方法和变量属于静态,方法和变量会被存储在方法区的静态常量池中,能够直接被类调用。从上一张图可以看出如果没有static关键字,那么类是不可以调用自己的属性和方法的,只能用对象去调用。
如何使用 static 关键字?
使用 static 修饰的变量属于类变量,存储在静态常量池中,被所有对象共享。
使用 static 修饰的方法可以直接通过类名调用,无需创建对象。
static修饰变量
static 修饰的变量被称为类变量,它与普通的实例变量不同,类变量是属于整个类的,而不是某一个对象。类变量存储在方法区的静态常量池中,并且被所有对象所共享。
变量存储与内存模型分析
当我们在代码中使用 static 修饰一个变量时,该变量就不再属于某个对象实例,而是属于类本身。下面通过一个例子分析这种变量的存储位置和内存模型:
name 变量被 static 修饰,存储在方法区的静态常量池中,所有对象实例(如 a1 和 a2)共享此变量。
a1 和 a2 是两个对象实例,它们的 address 变量存储在各自的堆内存中,彼此独立。
当我们通过 a1 修改 name 的值时,static 变量的值就被修改为 "张三"。由于 name 是类变量,a2 再次修改它的值时,所有实例访问到的 name 都变成了 "石家庄"。
static修饰方法
在static修饰的静态方法中无法调用非静态方法
static 修饰的方法称为静态方法。静态方法与类关联,而不是与某个对象关联。这意味着我们可以在没有创建对象实例的情况下调用静态方法。
静态方法与非静态方法的互相调用
需要注意的是,静态方法中无法直接调用非静态方法。这是因为非静态方法依赖于对象实例,而静态方法不依赖于任何对象实例。
如何解决这一问题?
将非静态方法转换为静态方法:可以通过将非静态方法添加 static 关键字,使其成为静态方法。
通过对象调用非静态方法:在静态方法中创建一个对象实例,然后通过该对象来调用非静态方法。
在非静态方法中可以调用静态方法
为什么在非静态方法中可以调用静态方法?
非静态方法可以访问静态方法,因为在对象实例创建之前,类已经加载且静态方法已存在。静态方法是类的一部分,并且在类加载时就已经存在,因此在任何时候都可以被调用。
static修饰代码块
static 代码块(或称静态代码块)在类被加载时自动执行,通常用于在类加载时执行一些初始化工作。static 代码块在 main 方法执行之前运行,主要用于优化程序或进行一些预处理。
静态代码块的执行顺序
当程序启动时,首先会寻找含有 main() 函数的类,并启动该程序。启动过程如下:
扫描检查该类是否有父类。如果有,则继续扫描父类并加载父类(递归地进行)。
当找到最顶层的父类后,首先加载父类,并执行父类中的 static 代码块。
接着加载子类,并执行子类中的 static 代码块。
最后,进入 main() 方法,程序开始执行。
首先加载 Base 类并执行 Base 类中的 static 代码块。
然后加载 Derived 类并执行 Derived 类中的 static 代码块。
最后执行 main 方法中的内容,包括创建 Derived 对象,这时会先调用 Base 的构造方法,然后调用 Derived 的构造方法。
通过这种方式,Java 确保了类的初始化顺序,并保证在使用类之前,所有的静态初始化已经完成。
如何让方法被 main 方法调用?
将方法设为静态方法:
如果希望某个方法可以直接被 main 方法调用,只需在该方法前添加 static 关键字。
public class myclass {
public static void ceshi() {
System.out.println(1111);
}
}
public class Test {
public static void main(String[] args) {
myclass.ceshi(); // 直接调用静态方法
}
}
通过对象调用非静态方法:
如果方法是非静态的,则需要先创建对象,然后通过对象调用该方法。
public class myclass {
public void ceshi() {
System.out.println(2222);
}
}
public class Test {
public static void main(String[] args) {
myclass a = new myclass();
a.ceshi(); // 通过对象调用非静态方法
}
}
例题解析
来看一个具体的代码示例,分析 static 关键字的加载顺序和使用。
public class Demo {
public Demo(String aa) {
System.out.println("====" + aa);
}
static {
System.out.println("11");
}
public static Demo demo = new Demo("+++");
static {
System.out.println("22");
}
}
class Test {
public static void main(String[] args) {
Demo demo = new Demo("----");
}
}
解析:
在执行 main 方法中的语句 Demo demo = new Demo("----"); 时,需要调用 Demo 类的构造器。
由于 Demo 类被引用,JVM 会首先加载 Demo.class 到内存,并初始化其中的静态部分。
static 代码块和静态变量按顺序执行,因此输出顺序如下:
====+++(静态变量 demo 调用了 Demo 的构造函数)
====---(main 方法中 new 了一个 Demo 对象)