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 的广阔天地!