为了让static
关键字的概念更加浅显易懂,我们可以从一个简单的学生信息系统入手。假设我们需要创建一个 Student
类来存储每个学生的姓名、学号等信息,同时还需要记录学校的名字和统计当前有多少学生。这时候,static
就能发挥很好的作用。
静态变量
1. 没有 static
的场景:每个学生都有自己的一份数据
首先,让我们看看没有 static
的时候,所有信息(包括不应该重复存储的)都被每个学生对象独立保存的情况。
代码示例:
class Student {
// 学生的实例变量,每个学生都有独立的姓名和学号
String name;
int id;
String schoolName = "清华大学"; // 这里是学校的名字(每个对象都保存)
// 构造函数
public Student(String name, int id) {
this.name = name;
this.id = id;
}
// 显示学生信息
public void displayInfo() {
System.out.println("姓名: " + name + ", 学号: " + id + ", 学校: " + schoolName);
}
}
public class Main {
public static void main(String[] args) {
// 创建两个学生对象
Student student1 = new Student("小明", 101);
Student student2 = new Student("小红", 102);
// 显示学生信息
student1.displayInfo();
student2.displayInfo();
}
}
运行结果:
姓名: 小明, 学号: 101, 学校: 清华大学
姓名: 小红, 学号: 102, 学校: 清华大学
问题:
- 在这个例子中,
schoolName
(学校名称)是每个学生对象的实例变量,这意味着每个学生对象都存储了同样的学校信息。如果有 1000 个学生,那每个学生都将独立保存相同的“清华大学”字符串。这显然浪费了内存。 - 解决方法:我们可以使用
static
关键字,让学校名称只保存一份,所有学生对象共享这一数据。
2. 引入 static
:共享学校名称和统计学生人数
通过使用 static
关键字,我们可以做到以下几点:
- 学校名称是所有学生共享的,只存储一份。
- 学生人数是共享的,每创建一个学生对象,学生数量自动增加。
代码示例:
class Student {
// 学生的实例变量(每个学生都有自己的姓名和学号)
String name;
int id;
// 静态变量(所有学生共享),学校名称和学生总人数
static String schoolName = "清华大学";
static int studentCount = 0;
// 构造函数
public Student(String name, int id) {
this.name = name;
this.id = id;
studentCount++; // 每创建一个学生,人数增加
}
// 显示学生信息
public void displayInfo() {
System.out.println("姓名: " + name + ", 学号: " + id + ", 学校: " + schoolName);
}
// 静态方法:显示学生总数
public static void displayStudentCount() {
System.out.println("当前学生总数: " + studentCount);
}
}
public class Main {
public static void main(String[] args) {
// 创建两个学生对象
Student student1 = new Student("小明", 101);
Student student2 = new Student("小红", 102);
// 显示每个学生的信息
student1.displayInfo();
student2.displayInfo();
// 调用静态方法显示总人数
Student.displayStudentCount(); // 注意,静态方法通过类名调用
}
}
运行结果:
姓名: 小明, 学号: 101, 学校: 清华大学
姓名: 小红, 学号: 102, 学校: 清华大学
当前学生总数: 2
解释:
schoolName
是一个static
变量,因此它属于类本身,而不是某个具体的学生对象。这意味着无论创建多少个学生对象,schoolName
只会存储一份,所有对象共享这个学校名称。studentCount
是一个static
变量,用于记录学生的总数。每次创建一个学生对象,studentCount
都会增加。这也是静态变量的一个重要用途:统计类级别的信息。displayStudentCount()
是一个static
方法,表示它与具体的对象无关,可以直接通过类名调用。
3. 为什么需要 static
?
-
节省内存: 如果我们不使用
static
,每个对象都需要独立存储相同的信息,比如学校的名字。这会浪费大量内存。而使用static
后,学校名称只存储一份,所有对象共享。 -
全局统计: 如果你需要在整个程序中维护一些全局的状态,比如学生的总人数,使用
static
变量可以确保所有对象共享这一状态,并且可以在类级别访问它,而无需依赖于具体的某个对象。 -
无需依赖对象: 静态方法和静态变量与对象无关,因此可以直接通过类名调用,方便那些不依赖实例的操作。
静态方法(static method)
好的!接下来我们详细讲解一下**static
方法**在 Java 中的使用,包括它的特性、应用场景、以及注意事项。
1. 什么是static method?
static
方法是属于类本身的方法,而不是属于某个特定对象的方法。换句话说,static
方法不依赖于对象的状态,因此可以通过类名直接调用,而不需要实例化对象。
特性:
static
方法可以直接通过类名调用,而不需要创建类的对象。static
方法只能访问静态变量或调用静态方法,不能访问类的非静态(实例)变量或调用非静态方法,因为它不与特定对象关联。- 常用于工具类或不依赖对象状态的功能。
2. 为什么使用 static
方法?
1) 无需创建对象
有些功能不需要对象状态,例如数学计算、全局的逻辑操作等。为了避免每次使用这些功能时都创建对象,我们可以将这些功能设计为 static
方法,这样可以直接通过类名调用,简化代码。
2) 提高性能和效率
static
方法在类加载时就已经绑定到类上,因此调用时不需要通过对象查找,性能上有一定的优势。
3) 不依赖于对象状态
如果方法的行为不依赖于对象的实例变量或实例方法,那么它可以被设计为 static
方法。这样可以让方法更通用,减少对对象的依赖。
3. static
方法的示例
例子1:数学工具类
假设我们需要创建一个数学工具类,提供计算平方和平方根的功能。这些功能并不依赖于对象状态,因此可以将其定义为静态方法。
class MathUtil {
// 静态方法:计算平方
public static int square(int number) {
return number * number;
}
// 静态方法:计算平方根
public static double squareRoot(double number) {
return Math.sqrt(number);
}
}
public class Main {
public static void main(String[] args) {
// 通过类名直接调用静态方法,无需创建对象
int result = MathUtil.square(5);
System.out.println("5的平方是: " + result);
double sqrtResult = MathUtil.squareRoot(25.0);
System.out.println("25的平方根是: " + sqrtResult);
}
}
输出:
5的平方是: 25
25的平方根是: 5.0
解释:
MathUtil
类中的square
和squareRoot
是静态方法,因为它们不依赖于任何对象状态。我们可以直接通过MathUtil.square(5)
和MathUtil.squareRoot(25.0)
调用这些方法,而不需要创建MathUtil
类的对象.
4. static
方法的访问限制
只能访问静态变量和静态方法
由于 static
方法属于类本身,而不是某个具体对象,所以它无法访问对象的实例变量或实例方法。static
方法只能访问类的静态成员(静态变量和静态方法)。
示例:
class Student {
String name; // 实例变量
static String schoolName = "清华大学"; // 静态变量
// 静态方法
public static void printSchoolName() {
System.out.println("学校名称: " + schoolName); // 可以访问静态变量
// System.out.println("学生姓名: " + name); // 错误!静态方法不能访问实例变量
}
}
解释:
printSchoolName
是一个静态方法,它只能访问类的静态变量schoolName
。- 如果尝试在静态方法中访问实例变量
name
,会导致编译错误,因为静态方法不依赖于任何具体对象,因此无法访问对象的实例变量。
5. 静态方法的限制
1) 无法访问非静态成员
正如前面提到的,静态方法不能直接访问类的实例变量或调用实例方法,因为静态方法不依赖于具体的对象实例。
2) 不能使用 this
和 super
关键字
this
关键字引用的是当前对象,而 super
关键字引用的是父类对象的引用。由于静态方法属于类本身而不是对象,因此在静态方法中不能使用 this
或 super
。
示例:
class Test {
int x;
public static void testMethod() {
// this.x = 5; // 错误!静态方法中不能使用this
}
}
3) 静态方法不能被重写(overriding)
静态方法属于类,不参与对象的动态绑定(多态性)。如果一个静态方法在子类中定义了相同的方法,实际上这是对父类静态方法的隐藏(hiding),而不是重写。
示例:
class Parent {
public static void display() {
System.out.println("Parent类的静态方法");
}
}
class Child extends Parent {
public static void display() {
System.out.println("Child类的静态方法");
}
}
public class Main {
public static void main(String[] args) {
Parent parent = new Parent();
Parent child = new Child();
parent.display(); // 输出:Parent类的静态方法
child.display(); // 输出:Parent类的静态方法(静态方法没有多态性)
}
}
解释:
- 虽然在子类中定义了与父类相同的静态方法,但静态方法属于类本身,因此并不会像实例方法一样通过对象的多态性调用子类方法。
child.display()
实际上调用的是Parent
类的display()
方法。 - 如果方法需要动态绑定,应该使用实例方法而不是静态方法。