Static关键字详解(静态成员变量,静态方法,代码块,静态代码块,单例模式)
1.Static
解释什么是static关键字,并举例至少三种用法
-
创建静态变量
static int count;
-
创建静态方法
static void printMessage() { System.out.println("Hello, World!"); }
-
创建静态块
static { System.out.println("静态块被执行,用于初始化静态成员"); count = 10; // 初始化静态变量 }
2.静态成员变量
思考静态成员变量和非静态成员变量的区别
-
存储位置:
- 静态成员变量存储在方法区的静态存储区域。
- 非静态成员变量存储在堆空间中,每个对象实例的副本都位于各自的内存空间。
-
生命周期:
- 静态成员变量的生命周期从类加载开始,到类卸载结束。
- 非静态成员变量的生命周期从对象创建开始,到对象垃圾回收结束。
-
出现顺序:
- 静态成员变量在类加载时就已经分配内存和初始化。
- 非静态成员变量在创建对象时分配内存和初始化。
-
调用方式:
- 静态成员变量可以直接通过类名调用,不依赖于对象实例。
- 非静态成员变量必须通过对象实例来调用。
-
初始化时机:
- 静态成员变量在类加载时进行初始化,通常是在静态初始化块中或者声明时进行赋值。
- 非静态成员变量在创建对象时进行初始化,通常是在构造方法中或者声明时进行赋值。
-
内存占用:
- 静态成员变量因为是被所有对象共享的,所以只有一个副本,占用固定的内存空间。
- 非静态成员变量每个对象都有自己的副本,因此创建的对象越多,占用的内存空间就越大。
-
共享:
- 静态成员变量是所有对象实例共享的,无论创建多少个对象,都只有一个静态成员变量的副本。
- 非静态成员变量每个对象实例都有自己的副本,各个对象之间的非静态成员变量互不影响。
3.静态方法
请简述static方法和非static方法有什么区别?什么情况下应该使用static方法
static方法和非static方法的主要区别如下:
-
所属对象:
- static方法是属于类的,不依赖于任何实例对象,可以通过类名直接调用。
- 非static方法是属于对象的,必须通过类的实例对象来调用。
-
调用方式:
- static方法可以通过类名直接调用,例如
ClassName.methodName()
。 - 非static方法必须通过类的实例对象调用,例如
objectName.methodName()
。
- static方法可以通过类名直接调用,例如
-
访问成员变量:
- static方法只能直接访问static成员变量和static成员方法。
- 非static方法可以访问static成员变量和成员变量,以及static成员方法和成员方法。
-
生命周期:
- static方法在类加载时就存在,不依赖于对象的创建和销毁。
- 非static方法的定义与对象的生命周期相关,随着对象的创建而存在,随着对象的销毁而消失。
-
创建对象:
- 调用static方法不需要创建类的实例对象。
- 调用非static方法通常需要先创建类的实例对象。
使用static方法的场景:
- 工具类方法: 当你需要一个不需要访问任何实例成员的方法时,比如数学计算、日期处理等工具类方法,使用static方法很合适。
- 静态工厂方法: 当你需要一个用于创建类实例的方法,而这个方法不需要访问任何特定的实例数据时,可以使用静态工厂方法。
- 代码组织: 当你想要组织代码,使得它不依赖于任何实例状态时,可以将相关方法定义为static。
- 性能考虑: 如果方法调用非常频繁,并且不依赖于对象状态,使用static方法可以避免创建不必要的对象实例,从而提高性能。
4.代码块
匿名代码块
匿名代码块是没有名字的代码块,通常出现在类体内部,不与任何方法或构造器关联。其作用如下:
-
初始化实例成员变量: 匿名代码块通常用于初始化类的实例成员变量,它会在每次创建类的实例对象时执行。
-
执行实例初始化逻辑: 如果有一些逻辑需要在创建类的实例时执行,而这些逻辑又不适合放在构造器中,可以使用匿名代码块来完成。
-
代码块内的变量作用域: 匿名代码块中定义的变量只在代码块内部有效,不会影响到类中的其他成员变量。
示例:
public class MyClass {
// 匿名代码块
{
// 初始化代码
}
}
静态代码块
静态代码块是使用static
关键字修饰的代码块,其作用如下:
-
初始化静态成员变量: 静态代码块通常用于初始化类的静态成员变量,它会在类加载时执行,且只执行一次。
-
执行静态初始化逻辑: 如果有一些逻辑需要在类加载时执行,而不是在创建实例时执行,可以使用静态代码块。
-
初始化顺序: 静态代码块在类加载时执行,优先于任何实例的初始化。如果有多个静态代码块,它们将按照它们在类定义中的顺序执行。
示例:
public class MyClass {
// 静态代码块
static {
// 静态初始化代码
}
}
5.继承static
在Java中,子类确实能够继承父类的static成员变量和方法。静态成员(包括静态变量和方法)属于类级别的成员,它们不是任何对象的实例的一部分,而是与类本身相关联。因此,当子类继承父类时,它会继承父类的所有静态成员。
6.程序分析
请阅读并分析以下三个案例中,程序启动运行的结果。
- 案例1
class Test {
public Test() {
System.out.println("构造器");
}
public void info() {
System.out.println("info");
}
static {
System.out.println("test static 1");
}
public static void main(String[] args) {
new Test().info();
}
{
System.out.println("代码块");
}
static {
System.out.println("test static 2");
}
}
//当Test类被加载时先执行静态初始化代码块,因此先按顺序输出test static 1、test static 2。然后执行main方法,这时执行实例初始化代码块,输出“代码块”,之后用构造器来完成对象的创建,输出“构造器”,在最后调用info方法输出“info”
- 案例2
class Parent {
static {
System.out.println("静态代码块Parent");
}
{
System.out.println("构造代码块Parent");
}
public Parent() {
System.out.println("构造方法Parent");
}
}
class Child extends Parent {
static {
System.out.println("静态代码块Child");
}
{
System.out.println("构造代码块Child");
}
public Child() {
System.out.println("构造方法Child");
}
}
//Child类继承Parent类,JVM先加载Parent类,加载过程按顺序执行静态代码块,输出“静态代码块Parent”,接着加载Child类,先执行静态代码块,输出“静态代码块Child”,当new Child()被调用时,先调用父类Parent的构造器,先构造代码块,输出“构造代码块Parent”,后“构造方法Parent”,最后使用子类Child的构造器,构造代码块,输出“构造代码块Child”,后“构造方法Child”
- 案例3
class B {
public static B b = new B();
public static B b2 = new B();
{
System.out.println("构造块");
}
static {
System.out.println("静态块");
}
}
public class Test08 {
public static void main(String[] args) {
B b = new B();
}
}
//调用new B()时,先初始化B类的静态变量b,执行初始化静态变量,输出“构造块”,接着初始化静态变量b2,输出“构造块”,然后执行静态初始化代码块,输出“静态块”,最后因为在main方法中执行了B b = new B(),所以会执行实例初始化代码块和构造方法,输出“构造块”。
7.单例模式
单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这种模式通常用于管理共享资源,如数据库连接或线程池,因为这些资源只需要一个实例来避免资源的重复创建和浪费。
public class Singleton {
// 静态变量,用于保存类的唯一实例
private static Singleton instance;
// 私有构造方法,防止外部直接创建实例
private Singleton() {
// 可以在这里添加一些初始化代码
}
// 静态方法,用于获取类的唯一实例
public static Singleton getInstance() {
if (instance == null) {
// 如果实例尚未创建,则创建一个新的实例
instance = new Singleton();
}
return instance;
}
// 其他方法,用于执行单例类的业务逻辑
public void doSomething() {
System.out.println("Doing something in the singleton instance.");
}
}
8.次数统计
编写一个Java类,实现如下功能:
-
该类能够自动记录被实例化的次数(即创建过该类多少对象)
-
能够随时通过调用某个方法,获取到这个数值
public class Counter {
private static int count = 0;
public Counter() {
//创建实例时数量+1
count++;
}
public static int getCount() {
return count;
}
public static void main(String[] args) {
System.out.println("未创建实例时的实例数量: " + getCount()); // 输出:0
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
System.out.println("三次创建实例后的实例数量: " + getCount()); // 输出:3
}
}