static
概述
static 表示静态的意思, 是 Java 的一个修饰符, 可以修饰成员方法或者成员变量.
被 static 修饰的成员变量叫做静态变量, 被 static 修饰的成员方法叫做静态方法 (static method)
静态变量被该类的所有对象共享, 所有对象的这个变量都是同一个值, 在一处修改了, 对所有的对象都有效果.
静态变量有两种访问方法, 一种是用类名访问, 一种是用类的对象来访问. 更推荐用类名访问.
静态方法多用于测试类和工具类中, Javabean 类中很少用静态方法. 静态方法有两种访问方法, 一种是用类名访问, 一种是用类的对象来访问. 更推荐用类名访问.
静态方法只能访问静态成员变量和静态成员方法.目前接触到的三种类:
Javabean 类: 用来描述一些事物, 如 Student, Users 等.
测试类: 用来检查其他的类书写是否正确, 包含 main() 方法, 是程序的主入口.
工具类: 不描述任何事物, 但是可以帮助我们做一些事情.
非静态方法可以访问静态变量或静态方法, 也可以访问非静态的成员变量和非静态的成员方法.
非静态方法访问变量或方法都是通过 this 对象来访问的, 而静态的变量或方法既可以用类访问也可以用对象来访问, 因此非静态的方法可以访问静态的方法或变量.
静态方法中不允许使用 this 关键字.静态方法和变量和非静态方法和变量的加载时机不同: 静态方法和静态变量是随着类的加载而加载到内存中的. 非静态方法和变量随着对象的加载而加载. 由此也能说明静态不能调用非静态, 因为如果这个时候还没有 new 出来一个对象, 那么对象就还不存在, 只有静态的方法和变量存在于内存中.
静态的成员变量或者方法, 即用类名调用的内容, 都存在于静态区中. 比如用类名调用静态方法, 那么该方法内用到的所有东西, 比如变量等, 都要去静态区查找.
从 JDK 8 开始, 静态区被放到了堆内存中.
如果静态方法中用到了非静态的变量, 而该变量不会存在于静态区中, 而是随着类的加载进入了方法区 (类的字节码文件加载进方法区) , 非静态的东西不存在于静态区中, 所以在静态区中找不到非静态的东西, 因此静态的方法不能调用非静态的变量或方法, 这是从内存的角度来解释的.
非静态的成员变量也被称为实例变量. 因此, 类中的成员变量分为实例变量 (非静态) 和静态变量 (用 static 修饰)
程序示例:
不使用静态变量的情况:
Javabean 类:
public class Student { // 私有化成员变量 private int age; private String name; private String gender; // 新增一个成员变量: 老师的姓名, 且是 public 的, 下面的 show() 方法也要修改, 打印出老师的姓名 public String teacherName; // 此处追求简单, 就暂时不给这个成员变量做构造方法和 getter 和 setter 的修改 // 构造方法 // 空参构造 public Student() { } // 全参构造 public Student(int age, String name, String gender) { this.age = age; this.name = name; this.gender = gender; } // getter and setter public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } // 其他行为 public void study() { System.out.println(name + "正在学习"); } public void show() { System.out.println(name + ", " + age + ", " + gender + ", " + teacherName); } }
测试类:
public class StudentTest { public static void main(String[] args) { // 使用空参构造创建第一个学生对象, 成员变量用 set 方法赋值 Student s1 = new Student(); s1.setName("张三"); s1.setAge(23); s1.setGender("男"); s1.teacherName = "王老师"; // 调用学生对象的成员方法 s1.study(); s1.show(); // 张三, 23, 男, 王老师 // 使用空参构造创建第二个学生对象, 成员变量用 set 方法赋值 Student s2 = new Student(); s2.setName("李四"); s2.setAge(24); s2.setGender("女"); // 调用学生对象的成员方法 s2.study(); s2.show(); // 李四, 24, 女, null // 第二个对象的 teacherName 属性没有赋值, 就保持了默认值 null // 此处显然是不合理的, 一个班级的同学, 老师这个属性显然是共享的 // 如果每一个对象都需要单独给这个共享的属性赋值, 显然太麻烦, 也容易出错 // 正确做法是只赋值一次, 就能让所有的对象都共享这个属性 // 做法是让这个属性成为私有的, 即加上 static 修饰符来修饰这个属性 // 这样就表示所有的 Student 对象都共享同一个 teacherName 属性 } }
程序示例:
Javabean 类:
public class Student { private String name; private int age; private static String teacherName; // private 的静态成员变量 public static String classes; // public 的静态成员变量 public Student() { } public Student(String name, int age, String teacherName, String classes) { this.name = name; this.age = age; this.teacherName = teacherName; this.classes = classes; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } /** * 获取 * @return teacherName */ public String getTeacherName() { return teacherName; } /** * 设置 * @param teacherName */ public void setTeacherName(String teacherName) { this.teacherName = teacherName; } /** * 获取 * @return classes */ public String getClasses() { return classes; } /** * 设置 * @param classes */ public void setClasses(String classes) { this.classes = classes; } public String toString() { return "Student{name = " + name + ", age = " + age + ", teacherName = " + teacherName + ", classes = " + classes + "}"; } }
测试类:
public class staticDemo1 { public static void main(String[] args) { Student s1 = new Student(); Student s2 = new Student(); s1.classes = "001"; // 用对象直接访问 public 的 static 成员变量 System.out.println(s1.classes); // 001 System.out.println(s2.classes); // 001, 修改了一个对象的 static 成员变量的值, 其他对象的这个值也变为相同的内容 Student.classes = "002"; // 用类访问 static 成员变量 System.out.println(s1.classes); // 002 System.out.println(s2.classes); // 002 s1.setTeacherName("xiao"); // 对象调用 set 方法来修改 private 的 static 成员变量的值 System.out.println(s1.getTeacherName()); // xiao System.out.println(s2.getTeacherName()); // xiao } }
非静态成员方法有一个自带的隐藏的形参 this. 这个形参不是在调用方法的时候我们手动赋值的, 这个形参不允许手动赋值. 在调用方法时, 虚拟机给这个形参赋值. 哪个对象调用了这个方法, 那么 this 就表示这个对象的地址值, 因此 this 的类型也跟随着对象的类型而变化. 这个隐藏的 this 形参位于形参列表的第一个位置.
this 代表这个对象, 因此 this 的类型就是当前所在的这个类名.
只要是用 static 修饰的内容, 都是随着类的加载而加载到静态区的, 是优先于对象而出现的.
程序示例:
public class Student { private String name; private int age; public void show1() { // 等价于 public void show1(Student this), 此处, this 的类型为 Student System.out.println(name + ", " + age); } public static void method() { System.out.println("静态方法"); } }
程序示例:
Javabean 类:
public class Student { private String name; private int age; public void show1() { // 等价于 public void show1(Student this) System.out.println("this: " + this); // 即便方法定义的括号内没有写 this 这个形参, 方法体内也可以使用 this 这个对象 } public static void method() { System.out.println("静态方法"); } }
测试类:
public class StudentTest { public static void main(String[] args) { Student s1 = new Student(); System.out.println("s1: " + s1); // s1: a02staticdemo2.Student@404b9385 s1.show1(); // this: a02staticdemo2.Student@404b9385 Student s2 = new Student(); System.out.println("s2: " + s2); // s2: a02staticdemo2.Student@6d311334 s2.show1(); // this: a02staticdemo2.Student@6d311334 } }
程序示例:
public class Student { private String name; private int age; public void show1() { System.out.println(name + ", " + age); // name 等价于 this.name, age 等价于 this.age, 此处没有重名, 所以 this 可以省略 show2(); // 调用其他方法, 相当于 this.show2() } public void show2(){ System.out.println("show2"); } public static void method() { System.out.println("静态方法"); } }
非静态的东西, 往往是和对象相关的, 比如里面的方法体, 是打印某个对象的 name 和某个对象的 age, 所以此处有 this, 是和对象相关的, this 指明了是哪个对象.
静态的东西是所有对象共享的, 不是和某个具体的对象有关系的. 所以 Java 在设计的时候, 在静态方法里面就没有 this 关键字.
静态和非静态的内容, 加载到内存中的时机是不同的. 静态的内容是随着类的加载而加载到内存中的. 非静态的东西是随着对象的创建而加载进内存中的.
静态方法在执行时, 只会去堆内存的静态区中去找它需要的东西, 而不是静态的内容就不会出现在静态区中, 所以静态方法只能访问静态的内容.
非静态的成员方法被调用时, 必须指定调用者, 用 this 指定. 静态方法中没有 this, 所以静态方法不能调用非静态方法.
static 内存图
程序示例:
Javabean 类:
public class Student { String name; int age; static String teacherName; public void show() { System.out.println(name + "..." + age + "..." + teacherName); } }
测试类:
public class staticDemo1 { public static void main(String[] args) { Student.teacherName = "xiaoming"; Student s1 = new Student(); // 第一个对象 s1.name = "zhangsan"; s1.age = 23; s1.show(); Student s2 = new Student(); // 第二个对象 s2.show(); } }
执行结果:
zhangsan...23...xiaoming null...0...xiaoming

第一步, 先执行 main() 方法, main() 方法先进栈.

第二步, 执行语句 Student.teacherName = "xiaoming";
这样一来就用到了 Student 这个类, 所以要把这个类的字节码文件加载到方法区, 并创建了一个单独存放静态变量的空间, 可以将这个空间称为静态存储位置或静态区. 将字节码文件加载到方法区中后, 静态区就出现了.
JDK 8 之前, 静态区是在方法区里面的, 到了 JDK 8 之后, 就将其放到了堆空间中. 在静态区中就存放着这个类的所有的静态变量. 静态变量的默认初始化值和普通成员变量的规则相同.
此时在内存中尚无对象, 因为代码还没有执行到 new 关键字. 只有 new 关键字执行了, 在内存中才有对象. 因此静态变量是优先于对象而存在的, 是随着类的加载而加载的. 类一旦加载进了内存, 这个静态变量就会出现. 只要是用 static 修饰的, 不限于成员变量, 都随着类的加载而加载, 优先于对象出现在内存中.

第三步, 执行 Student s1 = new Student();
等号左边在 main() 方法中定义了一个变量 s1, 等号右边在堆内存开辟了一个空间, 这个空间存储了所有的非静态的成员变量. 这个空间的地址被赋值给了变量 s1. 如果想要通过 s1 去访问静态变量 teacherName, 就要去静态区去找对应的变量.

第四步, 执行 s1.name = "zhangsan";
和 s1.age = 23;

第五步, 执行 s1.show();
此时, show() 方法被加载进栈. show() 方法的调用者是 s1. 执行完毕之后 show() 方法出栈.

第六步, 执行 Student s2 = new Student();
等号左边在 main() 方法中定义了一个变量 s2, 等号右边在堆内存又开辟了一个新的空间, 这个空间存储了所有的非静态的成员变量. 这个空间的地址被赋值给了变量 s2. 如果想要通过 s1 去访问静态变量 teacherName, 就要去静态区去找对应的变量.

第七步, 执行 s2.show();
此时, show() 方法被加载进栈. show() 方法的调用者是 s2. 执行完毕之后 show() 方法出栈.
静态区中的变量是对象共享的, 在内存中只有一份, 谁要用谁去拿. 非静态区的变量, 是每一个对象所独有的, 在每一个对象内单独存放.
另一个示例:

先执行 main 方法, main 方法进栈, 然后执行第一条语句 Student.teacherName = "阿玮老师";
, 用到了 Student 类, 则将 Student 类的字节码文件 Student.class 加载到方法区中, 这里面有所有的成员变量和成员方法, 在 JDK 7 之前, 不管是静态的还是非静态的, 都在方法区中, 在 JDK 7 之后, 将静态的成员变量移动到了堆内存的静态区中.
静态成员变量的初始化规则没有改变, String 类型首先初始化为 null. 在第一条语句中这个静态变量又被赋值为了 "阿玮老师", 替换了初始值 null.
注意, 静态方法并不在静态区中, 还是在方法区中, 和非静态的成员变量和成员方法都在一起, 只有静态成员变量被移动到了静态区中.

然后开始执行第二行代码 Student.method();
. 因此, method 方法进栈. method 方法是用类名 Student 来调用的, 因此会去静态区中寻找这个方法需要的东西, 此处 method 方法需要 name 和 teacherName. 然而, name 并不存在于静态区中, 因此调用失败.
静态方法不能调用非静态方法:

非静态的可以访问所有:


【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术