跳到主要内容Java 静态关键字、代码块与内部类详解 | 极客日志Javajava
Java 静态关键字、代码块与内部类详解
综述由AI生成详细讲解了 Java 中 static 关键字的使用,包括静态变量与方法的概念、访问方式及初始化场景。介绍了四种代码块(普通、构造、静态、同步)的定义与执行时机。深入剖析了内部类的四种形式(成员、静态、局部、匿名)及其访问规则。最后说明了对象打印原理及 toString() 重写方法,涵盖数组打印技巧。旨在帮助开发者理解面向对象的高级特性。
魔尊21 浏览 类和对象(下)
八、static 关键字
static(静态)是 Java 中的一个关键字,主要用于修饰类中的变量或方法。被 static 修饰的成员属于 类本身 而非某个实例对象。它在编译阶段就会被加载到方法区中(Java 8 之后元空间/Metaspace),无需通过创建对象就能访问。其生命周期伴随类的一生 (即:随类的加载而创建,随类的卸载而销毁)。
8.1 静态变量
8.1.1 概念
- 非静态变量:也称为实例变量,每创建一个对象就会产生一份新的变量副本。
- 静态变量:也称为类变量,只有一份共享的副本,所有该类对象都可以访问和修改。
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);
System.out.println(c1.id);
System.out.println(c2.id);
}
}
count 为静态变量,全类共享;
id 为实例变量,每个对象拥有自己的 id 值。
8.1.2 访问方式
- 类名。静态变量(推荐)
- 对象名。静态变量(不推荐,容易引起误解)
System.out.println(Counter.count);
System.out.println(c1.count);
8.2 静态方法
8.2.1 概念
- 被
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.2.2 访问限制
- 静态方法中只能访问 静态成员;
- 非静态方法中则可以同时访问静态成员和非静态成员。
8.3 static 成员变量初始化
注意:静态成员变量一般不会在构造方法中进行初始化,因为构造方法与对象实例关联,而静态成员变量则属于类本身。
- 就地初始化:在定义时直接给出初值
- 静态代码块初始化:在
static { ... } 代码块中完成赋值(下文再讲)
public class Student {
private String name;
private String gender;
private int age;
private static String classRoom = "Bit36";
}
8.4 静态成员的使用场景
- 工具类/工具方法:如
Math 类的 sqrt()、abs() 等,方便直接通过类名调用。
- 单例模式:通过静态字段持有唯一实例。
- 计数器:统计对象数量或方法调用次数。
- 常量池:
public static final 定义常量,方便全局使用且避免重复创建。
九、代码块
9.1 代码块概念以及分类
- 定义:在 Java 中使用
{} 包裹的一段或多段代码,即可称为'代码块'。
- 常见分类:
- 普通(局部)代码块:最常见,出现在方法体内部、流程控制语句中等,用来限制局部变量作用域或组织逻辑。
- 构造代码块(实例代码块):定义在类中、方法外,不带
static,在构造方法执行之前运行,每次创建对象都会执行。
- 静态代码块(静态初始化块):使用
static {} 修饰,类加载时(只一次)执行,用来初始化静态成员或做只需执行一次的操作。
- 同步代码块:用
synchronized(锁对象) { ... } 来实现多线程同步,控制临界区内的线程安全,(入门只需了解)。
9.2 普通代码块(局部代码块)
普通代码块通常指在方法或语句块内部,用花括号 {} 包裹的那部分代码。它的主要功能是局部作用域的划分。
public class Main {
public static void main(String[] args) {
{
int x = 10;
System.out.println("x = " + x);
}
int x = 100;
System.out.println("x2 = " + x);
}
}
- 普通代码块会限制局部变量的作用范围。
- 常用于封装临时逻辑或缩短变量生命周期,防止与其他同名变量冲突或占用资源太久。
9.3 构造代码块(实例代码块)
构造代码块(又称'实例初始化块')是指在类中、方法外,但没有 static 修饰的一段 {} 代码。它在创建对象时会先于构造方法执行,用来对实例成员进行初始化或执行公共逻辑。从先于构造函数体执行的思想上看和 C++ 的初始化列表有相似之处。
9.3.1 执行时机
- 每次调用
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
9.3.2 作用和特点
- 作用:可在对象创建前做一些实例变量的初始化或公共操作。
- 执行次数:与
new 对象的次数相同,每次创建对象都会执行一次。
- 与构造方法的关系:
- 若有多个构造方法,可将公共初始化操作放到构造代码块,避免重复代码。
- 执行顺序:构造代码块 → 构造方法。
9.4 静态代码块(静态初始化块)
静态代码块使用 static {} 修饰,是属于类级别的初始化逻辑。它会在类加载的时候执行一次,不随着对象创建反复执行。
9.4.1 执行时机
- 类第一次加载时,JVM 执行所有静态代码块。
- 只执行一次,与后续创建多少对象无关。
9.4.2 使用场景
- 初始化静态成员变量。
- 进行一次性的操作(如注册驱动、加载配置等)。
9.4.3 示例
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()!
9.4.4 注意事项
- 静态代码块只执行一次,无论创建多少对象都不会重复执行。
- 如果在同一个类里定义多个
static {},会按照代码顺序依次执行。(即合并)
- 静态环境下无法访问实例成员;要访问实例变量或实例方法,需要先创建对象。
9.5 同步代码块(了解)
'同步代码块'并不是为了初始化而存在,而是为了解决多线程并发访问同一资源时的线程安全问题。写法一般是:
- 当一个线程进入该代码块并持有'锁对象'时,其他线程只能等待,直到该线程执行完毕并释放锁。
- 通常'锁对象'可用
this(当前实例)、某个类对象、或专门的锁实例等。
十、内部类
内部类(Inner Class)是将一个类的定义放在另一个类的内部,从而形成逻辑上的隶属关系。Java 提供了多种内部类形式,包括成员内部类、静态内部类、局部内部类以及匿名内部类。通过内部类,我们可以更好地封装和管理代码结构,也能直接访问外部类的私有成员,增强代码的灵活性和可读性。
10.1 内部类概述
- 定义:在一个类的内部再定义一个类(或接口),该类称之为'内部类'或'嵌套类'。
- 分类:
- 成员内部类(非静态内部类)
- 静态内部类
- 局部内部类(定义在方法或代码块内)
- 匿名内部类(没有类名,直接定义并实例化)
- 好处:
- 内部类可以直接访问外部类的成员(包括私有成员),从而简化了访问操作。
- 逻辑上隶属关系更清晰,起到封装与隐藏的作用。
- 通过匿名内部类等方式,可以使代码更简洁,尤其在回调或事件监听等场景中。
10.2 成员内部类
成员内部类又叫非静态内部类,它是定义在外部类的成员位置(与外部类的成员变量、方法同级)但不带 static 关键字的内部类。
10.2.1 基本语法
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。
10.2.2 访问规则
- 内部类可以直接访问外部类的所有成员(包括
private)。
- 若内部类成员与外部类成员同名,可用
外部类名.this.成员 的方式区分,例如 Outer.this.name。
- 在外部类的非静态方法中,可以直接创建内部类实例;在外部类的静态方法或其他类中,则需通过
外部类对象.new 内部类构造 () 来创建。
10.3 静态内部类
静态内部类,也称静态嵌套类,使用 static 修饰。它与成员内部类的主要区别在于:
- 静态内部类只能访问外部类的静态成员,无法直接访问外部类的非静态成员。
- 创建静态内部类的对象时,不需要外部类对象的实例。
10.3.1 基本语法
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);
}
}
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.show();
}
}
10.3.2 访问方式
- 静态内部类对象的创建方式:
外部类名。内部类名 对象名 = new 外部类名。内部类名 ();
- 静态内部类中的实例方法,依然需要创建内部类实例来调用;但如果有静态方法或静态变量,可以通过
Outer.Inner.静态方法 或 Outer.Inner.静态变量 直接访问。
10.4 局部内部类
局部内部类是定义在方法体或代码块内部的类,只在该方法或代码块中可见和使用。局部内部类可以看作'更局部化'的内部类,常用于一些只在某个方法中使用的场景。
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();
}
}
10.4.1 特点
- 作用域限制:只能在定义它的方法或代码块中创建并使用。
- 访问外部变量:可以访问外部类的成员,也可以访问该方法中被
final 或'事实上的最终'变量所修饰的局部变量(Java 8+ 开始,只要不修改该变量即可,不必显式 final)。
10.5 匿名内部类(抽象类和接口时详细介绍)
匿名内部类(Anonymous Inner Class)没有类名,通常用于简化创建某些接口或抽象类子类对象的过程,尤其在回调、事件处理等场景中使用广泛。
10.5.1 基本写法
interface ITest {
void func();
}
public class Demo {
public static void main(String[] args) {
ITest test = new ITest() {
@Override
public void func() {
System.out.println("Anonymous Inner Class func");
}
};
test.func();
}
}
new 接口/抽象类 () { ... }:创建一个实现该接口或继承该抽象类的匿名子类对象。
- 匿名:没有类名,直接在此处定义并实例化。
10.5.2 特点
- 只能使用一次:若需要多次创建同样功能的对象,通常还是单独定义一个类或使用 Lambda(对于函数式接口)更好。
- 简化代码:不用显式定义一个实现类/子类。
10.6 小结
- 成员内部类:
- 需要先创建外部类对象,再通过
外部类对象.new 内部类 () 来实例化。
- 可以访问外部类的所有成员。
- 静态内部类:
- 使用
static 修饰,只能直接访问外部类的静态成员。
- 无需外部类实例即可创建,使用
外部类名。内部类名 方式。
- 局部内部类:
- 定义在方法或代码块内部,仅在该方法/代码块中可见。
- 可以访问外部类的成员,也可以访问方法内'事实上的最终'变量。
- 匿名内部类:
- 没有类名,常用于简化接口或抽象类的实现。
- 使用场景多在回调、事件监听等。
掌握内部类的使用场景与写法,可以使代码的封装性更好,也能让某些实现方式更灵活、更简洁。
在实际开发中,根据需求选择合适的内部类形式:
- 若需要频繁调用且功能独立,建议成员内部类或静态内部类;
- 若仅在某个方法中临时使用,且逻辑不复杂,可选择局部内部类或匿名内部类来简化代码。
十一、对象的打印
在 Java 中,当我们使用 System.out.println(obj); 或字符串拼接(如 "" + obj)来打印一个对象时,实质上是自动调用该对象的 toString() 方法。如果类中没有重写toString(),则默认会调用 Object 类的 toString() 方法,打印出类似 类名@哈希值 的信息(如 com.bit.demo.Student@3f99bd52),往往并不是我们想要的'可读输出'。
11.1 Object 的 toString()
- 为什么常被打印?
- 在使用
System.out.println(对象引用); 或字符串拼接时,会自动调用 toString()。
- 如果没有重写该方法,就会使用
Object 的默认实现。
默认实现:Object 的 toString() 返回的字符串格式一般是:
getClass().getName() + "@" + Integer.toHexString(hashCode())
也就是'类的完整名称@哈希码'形式,便于识别对象在内存中的'标识',但并不展示对象的具体属性信息。
11.2 重写 toString()
为了打印出更有意义的信息,我们通常会在自定义的类中重写(Override)toString() 方法。这样当打印对象时,就能输出该对象的关键属性值或其他说明性内容。
11.2.1 基本示例
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@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);
}
}
11.2.2 重写要点
- 方法签名:必须是
public String toString(),且带有 @Override 注解(可选但建议)。
- 返回值:返回一个可读性强、能够体现对象核心信息的字符串。
- 风格多样:可以根据需要自定义输出格式,或使用 JSON、XML 等形式。
- IDE 快捷生成:大多数 IDE(如 Eclipse、IntelliJ IDEA)可以自动生成
toString() 代码,方便使用。
11.3 打印数组
打印数组对象时,如果使用 System.out.println(arr); 也会得到类似 [Ljava.lang.String;@1540e19d 这样的结果(同样是 Object 的默认 toString())。如果想查看数组元素,可以使用以下方式:
Arrays.deepToString(数组):适用于多维数组。
String[][] arr2D = {{"A", "B"}, {"C", "D"}};
System.out.println(Arrays.deepToString(arr2D));
Arrays.toString(数组):适用于一维数组。
String[] arr = {"Hello", "World"};
System.out.println(Arrays.toString(arr));
11.4 小结
- 默认打印对象:调用
Object.toString(),返回'类名@哈希值',可读性差。
- 重写 toString():通过在自定义类中重写
toString(),让打印对象时输出更有意义的属性信息。
- 打印数组:使用
Arrays.toString() 或 Arrays.deepToString() 更好地展示数组内容。
在实际开发中,重写 toString() 不仅方便调试与日志记录,也能让我们更直观地了解对象的核心数据。合理使用 toString() 让输出信息更友好,对日常开发帮助很大。
十二、总结与展望
- static 关键字
- 静态变量和静态方法在类级别上提供共享资源和操作,无需创建对象即可使用。
- 静态代码块为类提供一次性初始化的机会。
- 代码块
- 包括静态代码块、实例代码块等,用于在不同阶段进行初始化操作。
- 掌握代码块的执行顺序有助于理解对象的生命周期。
- 内部类
- 提供了更灵活的访问方式和封装机制。
- 主要分为成员内部类、静态内部类、匿名内部类和局部内部类。
- Java 继承与多态:继续探索面向对象的另外两个特性,理解类与类之间的继承关系以及动态绑定机制。
- 接口与抽象类:深入理解接口与抽象类的应用场景,以及如何运用多态思想进行程序扩展。
- 常用设计模式:结合内部类与静态特性,在单例、工厂、观察者等常见模式中,静态和内部类都有独特的用武之地。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online