Java 静态关键字、代码块与内部类详解
详细讲解了 Java 中 static 关键字的使用,包括静态变量与方法的概念、访问方式及初始化场景。介绍了四种代码块(普通、构造、静态、同步)的定义与执行时机。深入剖析了内部类的四种形式(成员、静态、局部、匿名)及其访问规则。最后说明了对象打印原理及 toString() 重写方法,涵盖数组打印技巧。旨在帮助开发者理解面向对象的高级特性。

详细讲解了 Java 中 static 关键字的使用,包括静态变量与方法的概念、访问方式及初始化场景。介绍了四种代码块(普通、构造、静态、同步)的定义与执行时机。深入剖析了内部类的四种形式(成员、静态、局部、匿名)及其访问规则。最后说明了对象打印原理及 toString() 重写方法,涵盖数组打印技巧。旨在帮助开发者理解面向对象的高级特性。

static(静态)是 Java 中的一个关键字,主要用于修饰类中的变量或方法。被 static 修饰的成员属于 类本身 而非某个实例对象。它在编译阶段就会被加载到方法区中(Java 8 之后元空间/Metaspace),无需通过创建对象就能访问。其生命周期伴随类的一生 (即:随类的加载而创建,随类的卸载而销毁)。
public class Counter {
public static int count = 0; // 静态变量
public int id; // 实例变量
public Counter() {
count++;
this.id = count;
}
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
System.out.println(Counter.count); // 2
System.out.println(c1.id); // 1
System.out.println(c2.id); // 2
}
}
count 为静态变量,全类共享;id 为实例变量,每个对象拥有自己的 id 值。System.out.println(Counter.count); // 推荐
System.out.println(c1.count); // 不推荐
static 修饰的方法属于类本身。public class MathUtil {
public static int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
int sum = MathUtil.add(3, 5);
System.out.println(sum); // 8
}
}
注意:静态成员变量一般不会在构造方法中进行初始化,因为构造方法与对象实例关联,而静态成员变量则属于类本身。
静态成员变量的初始化有两种方式:
static { ... } 代码块中完成赋值(下文再讲)就地初始化示例代码:
public class Student {
private String name;
private String gender;
private int age;
// 静态成员变量就地初始化
private static String classRoom = "Bit36";
// 构造方法、普通方法、get/set 方法等
// ...
}
Math 类的 sqrt()、abs() 等,方便直接通过类名调用。public static final 定义常量,方便全局使用且避免重复创建。{} 包裹的一段或多段代码,即可称为'代码块'。static,在构造方法执行之前运行,每次创建对象都会执行。static {} 修饰,类加载时(只一次)执行,用来初始化静态成员或做只需执行一次的操作。synchronized(锁对象) { ... } 来实现多线程同步,控制临界区内的线程安全,(入门只需了解)。普通代码块通常指在方法或语句块内部,用花括号 {} 包裹的那部分代码。它的主要功能是局部作用域的划分。
示例:
public class Main {
public static void main(String[] args) {
// 普通(局部)代码块
{
int x = 10;
System.out.println("x = " + x);
}
// x 的作用域仅限于上面的 {} 之内
// System.out.println(x); // 编译报错
// 再次声明一个同名变量 x,不会冲突
int x = 100;
System.out.println("x2 = " + x);
}
}
输出:
x = 10
x2 = 100
要点:
构造代码块(又称'实例初始化块')是指在类中、方法外,但没有 static 修饰的一段 {} 代码。它在创建对象时会先于构造方法执行,用来对实例成员进行初始化或执行公共逻辑。从先于构造函数体执行的思想上看和 C++ 的初始化列表有相似之处。
new 构造对象时,都会先执行构造代码块,然后再执行构造方法。示例:
public class Student {
private String name;
private int age;
// 构造代码块(实例代码块)
{
this.name = "bit";
this.age = 12;
System.out.println("I am instance init()!");
}
public Student() {
System.out.println("I am Student init()!");
}
public void show() {
System.out.println("name: " + name + " age: " + age);
}
public static void main(String[] args) {
Student stu = new Student();
stu.show();
}
}
输出:
I am instance init()!
I am Student init()!
name: bit age: 12
可以看到,构造代码块先于构造方法执行。
new 对象的次数相同,每次创建对象都会执行一次。静态代码块使用 static {} 修饰,是属于类级别的初始化逻辑。它会在类加载的时候执行一次,不随着对象创建反复执行。
public class Student {
private String name;
private int age;
private static String classRoom;
// 静态代码块
static {
classRoom = "bit306";
System.out.println("I am static init()!");
}
// 构造代码块
{
this.name = "bit";
this.age = 12;
System.out.println("I am instance init()!");
}
public Student() {
System.out.println("I am Student init()!");
}
public static void main(String[] args) {
System.out.println("----开始 main 方法----");
Student s1 = new Student();
Student s2 = new Student();
}
}
输出顺序示例:
I am static init()!
----开始 main 方法----
I am instance init()!
I am Student init()!
I am instance init()!
I am Student init()!
static {},会按照代码顺序依次执行。(即合并)'同步代码块'并不是为了初始化而存在,而是为了解决多线程并发访问同一资源时的线程安全问题。写法一般是:
synchronized (锁对象) {
// 需要线程同步的代码
}
this(当前实例)、某个类对象、或专门的锁实例等。内部类(Inner Class)是将一个类的定义放在另一个类的内部,从而形成逻辑上的隶属关系。Java 提供了多种内部类形式,包括成员内部类、静态内部类、局部内部类以及匿名内部类。通过内部类,我们可以更好地封装和管理代码结构,也能直接访问外部类的私有成员,增强代码的灵活性和可读性。
成员内部类又叫非静态内部类,它是定义在外部类的成员位置(与外部类的成员变量、方法同级)但不带 static 关键字的内部类。
public class Outer {
private String name = "OuterName";
// 成员内部类
public class Inner {
public void show() {
// 直接访问外部类的私有成员
System.out.println("Outer name: " + name);
}
}
public void test() {
Inner inner = new Inner();
inner.show();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
执行流程:
Outer 对象:Outer outer = new Outer();outer.test() 方法中,实例化 Inner:Inner inner = new Inner();inner.show(),可以直接访问 Outer 类中的 name。private)。外部类名.this.成员 的方式区分,例如 Outer.this.name。外部类对象.new 内部类构造 () 来创建。静态内部类,也称静态嵌套类,使用 static 修饰。它与成员内部类的主要区别在于:
public class Outer {
private String name = "OuterName";
private static String staticName = "StaticOuterName";
// 静态内部类
public static class Inner {
public void show() {
// 只能直接访问外部类的静态成员
System.out.println("Outer staticName: " + staticName);
// System.out.println(name); // 非静态成员,无法直接访问
}
}
public static void main(String[] args) {
// 不需要外部类对象,直接创建静态内部类对象
Outer.Inner inner = new Outer.Inner();
inner.show();
}
}
外部类名。内部类名 对象名 = new 外部类名。内部类名 ();Outer.Inner.静态方法 或 Outer.Inner.静态变量 直接访问。局部内部类是定义在方法体或代码块内部的类,只在该方法或代码块中可见和使用。局部内部类可以看作'更局部化'的内部类,常用于一些只在某个方法中使用的场景。
public class Outer {
public void method() {
class Inner {
public void show() {
System.out.println("I am local inner class!");
}
}
// 在方法内部创建并使用
Inner inner = new Inner();
inner.show();
}
public static void main(String[] args) {
new Outer().method();
}
}
final 或'事实上的最终'变量所修饰的局部变量(Java 8+ 开始,只要不修改该变量即可,不必显式 final)。匿名内部类(Anonymous Inner Class)没有类名,通常用于简化创建某些接口或抽象类子类对象的过程,尤其在回调、事件处理等场景中使用广泛。
interface ITest {
void func();
}
public class Demo {
public static void main(String[] args) {
// 匿名内部类:直接 new 接口(或抽象类),然后立刻重写其中的方法
ITest test = new ITest() {
@Override
public void func() {
System.out.println("Anonymous Inner Class func");
}
};
test.func();
}
}
new 接口/抽象类 () { ... }:创建一个实现该接口或继承该抽象类的匿名子类对象。外部类对象.new 内部类 () 来实例化。static 修饰,只能直接访问外部类的静态成员。外部类名。内部类名 方式。掌握内部类的使用场景与写法,可以使代码的封装性更好,也能让某些实现方式更灵活、更简洁。 在实际开发中,根据需求选择合适的内部类形式:
在 Java 中,当我们使用 System.out.println(obj); 或字符串拼接(如 "" + obj)来打印一个对象时,实质上是自动调用该对象的 toString() 方法。如果类中没有重写toString(),则默认会调用 Object 类的 toString() 方法,打印出类似 类名@哈希值 的信息(如 com.bit.demo.Student@3f99bd52),往往并不是我们想要的'可读输出'。
System.out.println(对象引用); 或字符串拼接时,会自动调用 toString()。Object 的默认实现。默认实现:Object 的 toString() 返回的字符串格式一般是:
getClass().getName() + "@" + Integer.toHexString(hashCode())
也就是'类的完整名称@哈希码'形式,便于识别对象在内存中的'标识',但并不展示对象的具体属性信息。
为了打印出更有意义的信息,我们通常会在自定义的类中重写(Override)toString() 方法。这样当打印对象时,就能输出该对象的关键属性值或其他说明性内容。
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 toString() 方法
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
public static void main(String[] args) {
Student stu = new Student("Alice", 20);
System.out.println(stu); // 自动调用 stu.toString() => 输出:Student{name='Alice', age=20}
}
}
public String toString(),且带有 @Override 注解(可选但建议)。toString() 代码,方便使用。打印数组对象时,如果使用 System.out.println(arr); 也会得到类似 [Ljava.lang.String;@1540e19d 这样的结果(同样是 Object 的默认 toString())。如果想查看数组元素,可以使用以下方式:
Arrays.deepToString(数组):适用于多维数组。
String[][] arr2D = {{"A", "B"}, {"C", "D"}};
System.out.println(Arrays.deepToString(arr2D)); // [[A, B], [C, D]]
Arrays.toString(数组):适用于一维数组。
String[] arr = {"Hello", "World"};
System.out.println(Arrays.toString(arr)); // [Hello, World]
Object.toString(),返回'类名@哈希值',可读性差。toString(),让打印对象时输出更有意义的属性信息。Arrays.toString() 或 Arrays.deepToString() 更好地展示数组内容。在实际开发中,重写 toString() 不仅方便调试与日志记录,也能让我们更直观地了解对象的核心数据。合理使用 toString() 让输出信息更友好,对日常开发帮助很大。
本篇内容主要围绕以下三个方面展开:
未来的学习方向:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online