十五、static关键字
15.1 概述
static 关键字可以用来修饰成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属于某个对象的。也就是说,既然属于类,就可以不靠创建对象来调用了。
关于 static
关键字的作用可以用一句话来描述:**方便在没有创建对象的情况下进行调用,包括变量和方法。**也就是说,只要类被加载了,就可以通过类名进行访问。
小贴士:
static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况 下,去调用方法。Arrays 和 Math 两个工具类,就可以来体现 static 方法的便利。
15.2 静态变量
在声明变量的时候使用 static 关键字,那么这个变量就被称为静态变量。静态变量只在类加载的时候获取一次内存空间,这就使得静态变量很节省内存空间。
把一个变量声明为静态变量通常基于几个目的:
- 作为共享变量使用;
- 减少对象的创建;
- 保留唯一副本。
01、使用
有一个学生类 Student.java:
/**
* @author QHJ
* @date 2022/9/20 08:58
* @description: 学生类
*/
public class Student {
String name;
int age;
String school = "郑州大学";
}
引出一个问题:假设 “郑州大学” 录取了一万名新生,那么在创建一万个 Student 对象的时候,所有的字段(name、age、school)都会获取到一块内存,也就是说可能要获取一万块内存。虽然学生的姓名和年纪不尽相同,但都是属于 " 郑州大学" 的,如果每创建一个对象,school 这个字段都要占用一块内存的话,就会造成浪费。
因此,最好将 school 这个字段设置为 static
,这样就会只占用一块内存,而不是一万块:
/**
* @author QHJ
* @date 2022/9/20 08:58
* @description: 学生类
*/
public class Student {
String name;
int age;
static String school = "郑州大学";
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Student student1 = new Student("青花椒", 22);
Student student2 = new Student("青花椒", 25);
}
}
这样的话,内存分布是这样的:student1 和 student2 这两个引用变量存放在栈区(stack),青花椒22 这个对象和 青花椒25 这个对象存放在堆区(heap),school 这个静态变量存放在静态区。
02、栗子
-
非静态成员变量
/** * @author QHJ * @date 2022/9/20 09:54 * @description: static */ public class Counter { int count = 0; Counter() { count++; System.out.println(count); } public static void main(String[] args) { Counter counter1 = new Counter(); Counter counter2 = new Counter(); Counter counter3 = new Counter(); } }
输出结果:
1
1
1创建一个成员变量 count,并且在构造函数中让它自增。因为成员变量会在创建对象的时候获取内存,因此每一个对象都会有一个 count 副本,count 的值并不会随着对象的增多而递增。每创建一个 Counter 对象,count 的值就从 0 自增到 1。
-
静态成员变量
/** * @author QHJ * @date 2022/9/20 10:26 * @description: 静态成员变量 */ public class StaticCounter { static int count = 0; StaticCounter() { count++; System.out.println(count); } public static void main(String[] args) { StaticCounter staticCounter1 = new StaticCounter(); StaticCounter staticCounter2 = new StaticCounter(); StaticCounter staticCounter3 = new StaticCounter(); } }
输出结果:
1
2
3由于静态变量只会获取一次内存空间,所以任何对象对它的修改都会得到保留,所以每创建一个对象,count 的值就会加 1,所以最终的结果是 3。这也就是静态变量和成员变量之间的差别。
另外,由于静态变量属于一个类,所以不要通过对象引用来访问,而应该直接通过类名来访问,否则编译器会发出警告:
15.3 静态方法
在方法上加 static
关键字,它就是静态方法。将一个方法声明为静态方法,通常是为了方便在不创建对象的情况下使用。
静态方法有以下几个特征:
- 静态方法属于这个类而不是这个类的对象;
- 调用静态方法的时候不需要创建这个类的对象;
- 静态方法可以访问静态变量。
/**
* @author QHJ
* @date 2022/9/20 10:36
* @description:
*/
public class StaticMethodStudent {
String name;
int age;
static String school = "郑州大学";
public StaticMethodStudent(String name, int age) {
this.name = name;
this.age = age;
}
static void change() {
school = "河南科技大学";
}
void out() {
System.out.println(name + age + school);
}
public static void main(String[] args) {
StaticMethodStudent.change();
StaticMethodStudent student1 = new StaticMethodStudent("青花椒", 22);
StaticMethodStudent student2 = new StaticMethodStudent("青花椒", 25);
student1.out(); // 青花椒22河南科技大学
student2.out(); // 青花椒25河南科技大学
}
}
change() 方法是一个静态方法,所以它可以直接访问静态变量 school,把它的值更改为 “河南科技大学”;并且,可以通过类名直接调用该方法:StaticMethodStudent.change();
。
需要注意的是,静态方法不能访问非静态变量和调用非静态方法:
静态方法调用的注意事项:
- 静态方法可以直接访问类变量和静态方法。
- 静态方法不能直接访问普通成员变量或成员方法(非静态变量和非静态方法)。
- 反之,成员方法可以直接访问类变量或静态方法。
- 静态方法中,不能使用 this 关键字。
补充:main() 方法为什么是静态的?
如果 main() 方法不是静态的,就意味着 Java 虚拟机在执行的时候需要先创建一个对象才能调用 main() 方法,而 main() 方法作为程序的入口,创建一个额外的对象是非常多余的,
15.4 静态代码块
静态代码块通常来说是为了对静态变量进行一些初始化操作,比如单例模式、定义枚举类。
01、使用
定义在成员位置,使用一个 static 关键字,外加一个大括号括起来的代码被称为静态代码块。静态代码块通常用来初始化一些静态变量,它会优先于 main() 方法执行:
/**
* @author QHJ
* @date 2022/9/20 11:02
* @description: 静态代码块
*/
public class StaticBlock {
static {
System.out.println("静态代码块");
}
public static void main(String[] args) {
System.out.println("main方法");
}
}
输出结果:
静态代码块
main方法
既然静态代码块优先于 main() 方法执行,那没有 main() 方法的 Java 类能执行成功吗?
Java 1.6 是可以的,但是从 Java 7 开始就无法执行了:
02、作用
给类变量进行初始化赋值。
public class Game {
public static int number;
public static ArrayList<String> list;
static {
// 给类变量赋值
number = 2;
list = new ArrayList<String>();
// 添加元素到集合中
list.add("张三");
list.add("李四");
}
}
/**
* @author QHJ
* @date 2022/9/20 11:08
* @description: 静态代码块中初始化集合
*/
public class StaticBlockDemo {
public static List<String> writes = new ArrayList<>();
static {
writes.add("青花椒1");
writes.add("青花椒2");
writes.add("青花椒3");
System.out.println("part1");
}
static {
writes.add("青花椒4");
writes.add("青花椒5");
writes.add("青花椒6");
System.out.println("part2");
}
}
writes 是一个静态的 ArrayList,所以不太可能在声明的时候完成初始化,因此需要在静态代码块中完成初始化。
静态代码块在初始集合的时候非常有用。在实际的项目开发中,通常使用静态代码块来加载配置文件到内存当中。
15.5 静态内部类
01、使用
Java 允许我们在一个类中声明一个内部类,它提供了一种令人信服的方式,允许我们只在一个地方使用一些变量,使代码更具有条理性和可读性。
/**
* @author QHJ
* @date 2022/9/20 13:34
* @description: 静态内部类
*/
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
第一次加载 Singleton 类时并不会初始化 instance,只有第一次调用 getInstance() 方法时 Java 虚拟机才开始加载 SingletonHolder 并初始化 instance,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
需要注意的是,静态内部类不能访问外部类的所有成员变量;静态内部类可以访问外部类的所有静态变量,包括私有静态变量;外部类不能声明为 static。
02、静态内部类与非静态内部类的区别
非静态内部类对象持有外部类对象的引用(编译器会隐式地将外部类对象的引用作为内部类的构造器参数);而静态内部类对象不会持有外部类对象的引用;
由于非静态内部类的实例创建需要有外部类对象的引用,所以非静态内部类对象的创建必须依托于外部类的实例;而静态内部类的实例创建只需依托外部类;
并且由于非静态内部类对象持有了外部类对象的引用,因此非静态内部类可以访问外部类的非静态成员;而静态内部类只能访问外部类的静态成员;
两者的根本性区别其实也决定了用 static
去修饰内部类的真正意图:
- 内部类需要脱离外部类对象来创建实例;
- 避免内部类使用过程中出现内存溢出。
15.6 static变量和普通成员变量的区别
- 所属不同。static 变量属于类,不单属于任何对象;普通成员变量是属于某个对象的。
- 存储区域不同。static 变量位于方法区;普通成员变量位于堆区。
- 生命周期不同。static 变量生命周期与类的生命周期相同;普通成员变量和其所属的对象的生命周期相同。
- 在对象序列化时,static 变量会被排除在外(因为 static 变量是属于类的,不属于对象)。
15.6 静态原理图解
static 修饰的内容:
- 是随着类的加载而加载的,且只加载一次。
- 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
- 它优先于对象存在,所以,可以被所有对象共享。