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),往往并不是我们想要的“可读输出”。
默认实现:Object 的 toString() 返回的字符串格式一般是:
getClass().getName() + "@" + Integer.toHexString(hashCode())也就是“类的完整名称@哈希码”形式,便于识别对象在内存中的“标识”,但并不展示对象的具体属性信息。
为什么常被打印?
System.out.println(对象引用); 或字符串拼接时,会自动调用 toString()。Object 的默认实现。为了打印出更有意义的信息,我们通常会在自定义的类中重写(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.toString(数组):适用于一维数组。
String[] arr = {"Hello", "World"};
System.out.println(Arrays.toString(arr));
// [Hello, World]Arrays.deepToString(数组):适用于多维数组。
String[][] arr2D = {{"A", "B"}, {"C", "D"}};
System.out.println(Arrays.deepToString(arr2D));
// [[A, B], [C, D]]Object.toString(),返回“类名@哈希值”,可读性差。toString(),让打印对象时输出更有意义的属性信息。Arrays.toString() 或 Arrays.deepToString() 更好地展示数组内容。在实际开发中,重写 toString() 不仅方便调试与日志记录,也能让我们更直观地了解对象的核心数据。合理使用 toString() 让输出信息更友好,对日常开发帮助很大。
本篇内容主要围绕以下三个方面展开:
未来的学习方向
如果你对本篇内容有任何疑问或想进一步探讨,欢迎在评论区留言。我们下篇文章见,继续一起探索 Java 的广阔天地!