Java 中的静态成员与内部类深入解析
文章目录
一、Java 中的静态变量和静态方法
(一)静态变量
- 概念:静态变量也称为类变量,它属于类本身,而不属于类的任何实例对象。无论创建多少个类的实例,静态变量在内存中只有一份副本,被所有实例共享。
- 代码示例:
class Counter {
// 静态变量,用于记录创建的实例数量
public static int instanceCount = 0;
public Counter() {
// 每次创建实例时,静态变量加 1
instanceCount++;
}
}
可以这样使用:
Counter counter1 = new Counter();
Counter counter2 = new Counter();
System.out.println(Counter.instanceCount);
// 输出:2,因为创建了两个 Counter 实例,静态变量被共享并更新
- 特点:
- 使用
static
关键字修饰。 - 在类加载时初始化,其生命周期与类相同。
- 可以通过类名直接访问,也可以通过实例对象访问,但建议通过类名访问以体现其类属性。
- 使用
(二)静态方法
- 概念:静态方法是属于类的方法,它不依赖于类的实例对象。静态方法不能访问非静态成员(变量和方法),因为非静态成员是与实例相关联的,而静态方法在没有实例对象时也可以被调用。
- 代码示例:
class MathUtils {
// 静态方法,计算两个整数的最大值
public static int max(int num1, int num2) {
return (num1 > num2)? num1 : num2;
}
}
调用方式:
int result = MathUtils.max(5, 3);
System.out.println(result);
// 输出:5
- 特点:
- 用
static
关键字修饰。 - 可以在没有创建类实例的情况下直接通过类名调用。
- 只能访问类的静态成员,不能访问非静态成员,因为非静态成员需要实例对象才能确定其具体值。
- 用
以下是静态变量和静态方法的对比图表:
对比项目 | 静态变量 | 静态方法 |
---|---|---|
所属关系 | 属于类 | 属于类 |
内存分配 | 类加载时分配,一份副本 | 无特定单独内存分配描述,与类相关 |
访问方式 | 类名.静态变量 或 实例.静态变量(不推荐) | 类名.静态方法 |
与实例关系 | 被所有实例共享 | 不依赖实例,无实例时也可调用 |
访问限制 | 无特殊限制(除访问控制修饰符) | 只能访问静态成员 |
二、非静态内部类和静态内部类的区别
(一)非静态内部类
- 概念:非静态内部类也称为成员内部类,它是定义在另一个类内部的类,并且与外部类的实例相关联。非静态内部类可以访问外部类的所有成员(包括私有成员),因为它持有外部类的实例引用。
- 代码示例:
class OuterClass {
private int outerVariable = 10;
// 非静态内部类
class InnerClass {
public void accessOuterVariable() {
// 可以访问外部类的私有成员变量
System.out.println(outerVariable);
}
}
}
使用方式:
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.accessOuterVariable();
// 输出:10
- 特点:
- 定义在外部类内部,没有
static
修饰。 - 与外部类的实例相关联,创建非静态内部类对象时需要先有外部类对象。
- 可以访问外部类的所有成员,包括私有成员。
- 定义在外部类内部,没有
(二)静态内部类
- 概念:静态内部类是使用
static
关键字修饰的内部类,它属于外部类本身,而不依赖于外部类的实例。静态内部类只能访问外部类的静态成员。 - 代码示例:
class OuterClass2 {
private static int outerStaticVariable = 20;
// 静态内部类
static class InnerClass2 {
public void accessOuterStaticVariable() {
// 只能访问外部类的静态成员
System.out.println(outerStaticVariable);
}
}
}
使用方式:
OuterClass2.InnerClass2 inner2 = new OuterClass2.InnerClass2();
inner2.accessOuterStaticVariable();
// 输出:20
- 特点:
- 用
static
修饰,属于外部类。 - 不依赖外部类实例,可直接创建对象。
- 只能访问外部类的静态成员。
- 用
以下是非静态内部类和静态内部类的对比图表:
对比项目 | 非静态内部类 | 静态内部类 |
---|---|---|
与外部类实例关系 | 关联,需外部类实例创建对象 | 不依赖,可直接创建对象 |
访问外部类成员 | 所有成员 | 仅静态成员 |
定义修饰符 | 无 static 修饰 | 有 static 修饰 |
三、非静态内部类可以直接访问外部方法吗?
当非静态内部类访问外部类成员时,编译器会在非静态内部类的构造函数中自动添加一个外部类的引用参数。例如,对于前面的OuterClass
和InnerClass
示例,编译器实际上会将InnerClass
的构造函数修改为类似如下形式(伪代码):
class OuterClass {
private int outerVariable = 10;
// 非静态内部类
class InnerClass {
// 编译器添加的外部类引用
private OuterClass outer;
// 编译器修改后的构造函数
public InnerClass(OuterClass outer) {
this.outer = outer;
}
public void accessOuterVariable() {
// 通过外部类引用访问外部类成员
System.out.println(outer.outerVariable);
}
}
}
这样,非静态内部类就可以通过这个外部类引用访问外部类的成员,即使这些成员是私有的。这是编译器在背后自动处理的机制,使得非静态内部类能够方便地与外部类进行交互并访问其成员。
四、new 一个子类对象时的加载顺序
当创建一个子类对象时,类加载顺序如下:
- 首先加载父类:
- 加载父类的静态成员变量并初始化,按照它们在类中定义的顺序。
- 执行父类的静态初始化块(如果有)。
- 执行父类的静态构造方法(如果有)。
- 然后加载子类:
- 加载子类的静态成员变量并初始化,按照它们在类中定义的顺序。
- 执行子类的静态初始化块(如果有)。
- 执行子类的静态构造方法(如果有)。
- 最后创建子类对象:
- 初始化子类的非静态成员变量。
- 执行子类的非静态初始化块(如果有)。
- 调用子类的构造函数,如果子类构造函数中没有显式调用父类构造函数,则默认调用父类的无参构造函数,此时会先执行父类的非静态成员变量初始化、非静态初始化块和父类构造函数,然后再执行子类构造函数中的剩余代码。
以下是代码示例:
class Parent {
// 父类静态成员变量
public static int parentStaticVariable = 1;
// 父类静态构造方法
static {
System.out.println("父类静态初始化块,parentStaticVariable = " + parentStaticVariable);
}
public Parent() {
System.out.println("父类构造函数");
}
}
class Child extends Parent {
// 子类静态成员变量
public static int childStaticVariable = 2;
// 子类静态构造方法
static {
System.out.println("子类静态初始化块,childStaticVariable = " " + childStaticVariable);
}
public Child() {
System.out.println("子类构造函数");
}
}
当执行new Child();
时,输出结果为:
父类静态初始化块,parentStaticVariable = 1
子类静态初始化块,childStaticVariable = 2
父类构造函数
子类构造函数
这清晰地展示了在创建子类对象时,父类和子类的静态成员先加载,然后再进行子类对象的创建过程,包括父类和子类的非静态部分的初始化和构造函数调用。