Java 面向对象高级:代码块与内部类详解
本文基于学习文档,系统梳理代码块(类初始化机制)和内部类(四种类型及用法)的核心知识点、执行规则及实战案例,包含作业题完整解答,助力夯实 Java 面向对象进阶技能。
一、代码块
1. 核心概念
代码块是类的五大成分之一(字段、构造器、方法、代码块、内部类),用于完成类或对象的初始化,分为静态代码块和实例代码块两类。
2. 静态代码块
(1)基本语法与特点
- 格式:
static { 代码逻辑 } - 执行时机:类加载时自动执行,类仅加载一次,故静态代码块仅执行一次。
- 作用:初始化静态变量(如全局常量赋值、资源预加载)。
- 访问规则:只能访问类的静态成员(静态变量、静态方法),不能访问实例成员。
(2)类加载触发时机(主动引用才触发初始化)
- 实例化对象:
new MyClass() - 访问静态成员:
MyClass.staticField或MyClass.staticMethod()(final 修饰的静态常量除外) - 初始化子类:子类初始化时,父类未初始化则先初始化父类
(3)被动引用(不触发类初始化,可能触发加载)
- 子类引用父类静态字段:
Child.value(仅触发父类加载,不触发子类初始化) - 数组定义引用类:
MyClass[] arr = new MyClass[10] - 访问静态常量:
MyClass.CONSTANT(常量编译期已确定,不触发类初始化)
3. 实例代码块
(1)基本语法与特点
- 格式:
{ 代码逻辑 }(无 static 修饰) - 执行时机:每次创建对象时执行,执行次数与对象创建次数一致,且在构造器执行前执行。
- 作用:初始化实例变量(与构造器功能类似,可提取多个构造器的公共初始化逻辑)。
- 访问规则:可访问类的静态成员和实例成员。
4. 执行顺序总结
(1)单个类内部
静态代码块 → 实例代码块 → 构造器(静态代码块仅执行一次,实例代码块和构造器每次创建对象都执行)。
(2)继承关系(子类 B 继承父类 A)
父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器。
5. 常见判断题(核心结论)
- 实例代码块可以访问静态变量(√)
- 实例代码块的执行优先于构造方法(√)
- 实例代码块在每次创建对象时执行,执行次数与对象创建次数相同(√)
- 静态代码块可以访问非静态变量(×)
- 静态代码块和实例代码块的执行顺序与它们在代码中的位置无关(×,同类型代码块按定义顺序执行)
- 静态代码块的执行优先于实例代码块(√)
- 静态代码块只执行一次,实例代码块每次创建对象都执行(√)
6. 代码示例(执行顺序验证)
class Parent {
// 父类静态代码块
static {
System.out.println("父类静态代码块执行");
}
// 父类实例代码块
{
System.out.println("父类实例代码块执行");
}
// 父类构造器
public Parent() {
System.out.println("父类构造器执行");
}
}
class Child extends Parent {
// 子类静态代码块
static {
System.out.println("子类静态代码块执行");
}
// 子类实例代码块
{
System.out.println("子类实例代码块执行");
}
// 子类构造器
public Child() {
System.out.println("子类构造器执行");
}
}
// 测试类
public class TestBlockOrder {
public static void main(String[] args) {
System.out.println("第一次创建子类对象:");
new Child();
System.out.println("\n第二次创建子类对象:");
new Child();
}
}运行结果
第一次创建子类对象:
父类静态代码块执行
子类静态代码块执行
父类实例代码块执行
父类构造器执行
子类实例代码块执行
子类构造器执行
第二次创建子类对象:
父类实例代码块执行
父类构造器执行
子类实例代码块执行
子类构造器执行二、内部类
1. 核心概念
内部类是定义在类内部的类,核心价值是提高代码封装性,根据定义位置和修饰符可分为四类:成员内部类、静态内部类、局部内部类、匿名内部类。
2. 成员内部类(非静态内部类)
(1)基本语法与特点
- 定义:直接定义在外部类中,与外部类成员(字段、方法)同级,无 static 修饰。
- 依赖外部类:必须先创建外部类实例,才能创建成员内部类实例。
- 访问权限:可访问外部类的所有成员(包括 private 成员),外部类需通过内部类实例访问内部类成员。
- 编译文件:生成
外部类名$内部类名.class(如Outer$Inner.class)。
(2)创建对象语法
// 格式:外部类实例.new 内部类构造器()
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
// 简写:new 外部类().new 内部类()
Outer.Inner inner = new Outer().new Inner();(3)访问外部类成员(避免变量重名)
当内部类与外部类有同名成员时,用 外部类名.this.成员名 访问外部类成员:
public class Outer {
private int number = 10; // 外部类成员变量
public class Inner {
private int number = 20; // 内部类成员变量
public void show() {
int number = 30; // 局部变量
System.out.println(number); // 30(局部变量)
System.out.println(this.number); // 20(内部类成员变量)
System.out.println(Outer.this.number); // 10(外部类成员变量)
}
}
}
// 测试
public class TestMemberInner {
public static void main(String[] args) {
Outer.Inner inner = new Outer().new Inner();
inner.show(); // 输出:30、20、10
}
}3. 静态内部类
(1)基本语法与特点
- 定义:用 static 修饰的内部类,直接定义在外部类中。
- 不依赖外部类:无需创建外部类实例,可直接通过外部类名访问。
- 访问权限:仅能访问外部类的静态成员(静态变量、静态方法),不能访问实例成员。
- 编译文件:生成
外部类名$内部类名.class(如Outer$StaticInner.class)。
(2)创建对象语法
// 格式:外部类名.内部类名 变量名 = new 外部类名.内部类构造器()
Outer.StaticInner inner = new Outer.StaticInner();(3)成员内部类与静态内部类区别
| 对比维度 | 成员内部类 | 静态内部类 |
|---|---|---|
| 修饰符 | 无 static 修饰 | 有 static 修饰 |
| 依赖外部类 | 依赖外部类实例 | 不依赖外部类实例 |
| 创建对象语法 | 外部类实例.new 内部类() | 外部类名.内部类() |
| 访问外部类成员 | 可访问所有成员(静态+实例) | 仅能访问静态成员 |
4. 局部内部类(了解)
- 定义:定义在方法体、代码块或构造器中的内部类。
- 特点:作用域仅限于所在局部区域,仅能在该区域内创建对象和使用;可访问外部类的静态成员和所在方法的局部变量(需是 final 修饰,JDK8+ 可省略 final)。
语法示例:
public class Outer { public void method() { // 局部内部类(定义在方法中) class LocalInner { public void show() { System.out.println("局部内部类方法执行"); } } // 仅能在当前方法中使用 LocalInner inner = new LocalInner(); inner.show(); } }
5. 匿名内部类(重点)
(1)核心概念与特点
- 定义:无显式类名的内部类,本质是“继承了某个类或实现了某个接口的子类匿名对象”。
语法格式:
new 类名/接口名() { // 类体(重写父类方法或实现接口抽象方法) };特点:
- 必须继承一个类或实现一个接口(仅能继承一个类或实现一个接口)。
- 没有构造器(因无类名),但可定义成员变量和成员方法。
- 编译文件:生成
外部类名$1.class(多个匿名内部类按顺序编号 $1、$2...)。 - 作用:简化代码,用于一次性创建仅使用一次的对象。
(2)常见使用场景
作为方法实参传递(当方法形参是接口或抽象类类型时):
// 1. 定义接口
interface Socket {
void supply(); // 供电
void turnOn(); // 开机
}
// 2. 定义方法(形参为接口类型)
public class TestAnonInner {
public static void testSocket(Socket socket) {
socket.turnOn();
socket.supply();
}
public static void main(String[] args) {
// 3. 匿名内部类作为实参传递
testSocket(new Socket() {
@Override
public void supply() {
System.out.println("开始供电");
}
@Override
public void turnOn() {
System.out.println("打开插座");
}
});
}
}(3)继承抽象类的匿名内部类示例
// 抽象类
abstract class DataDao {
public void connect() {
System.out.println("打开数据库连接");
}
abstract void select(); // 抽象查询方法
public void disConnect() {
System.out.println("关闭数据库连接");
}
// 模板方法
public final void execute() {
connect();
select();
disConnect();
}
}
// 测试
public class TestAnonAbstract {
public static void main(String[] args) {
// 匿名内部类继承抽象类
DataDao dao = new DataDao() {
@Override
void select() {
System.out.println("查询用户数据:select * from user");
}
};
dao.execute();
}
}运行结果
打开数据库连接
查询用户数据:select * from user
关闭数据库连接6. 内部类作业题:学生数组排序
题目需求
- 定义
Student类,包含name(姓名)和age(年龄)属性,提供构造器和toString()方法。 - 创建
Student[]数组,添加 3 个学生对象。 - 使用
Arrays.sort()对数组按年龄倒序排序(两种方式实现)。 - 打印排序前和排序后的数组。
解答代码(两种排序方式)
import java.util.Arrays;
import java.util.Comparator;
// 1. 学生实体类
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// getter 方法(用于排序)
public int getAge() {
return age;
}
// 重写 toString() 便于打印
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
// 2. 测试类(两种排序方式)
public class TestStudentSort {
public static void main(String[] args) {
// 初始化学生数组
Student[] students = {
new Student("张三", 20),
new Student("李四", 22),
new Student("王五", 19)
};
// 排序前打印
System.out.println("排序前:" + Arrays.toString(students));
// 方式一:匿名内部类实现 Comparator 接口(推荐,灵活)
Arrays.sort(students, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 倒序排序:s2.age - s1.age(正序为 s1.age - s2.age)
return s2.getAge() - s1.getAge();
}
});
// 方式二:让 Student 类实现 Comparable 接口(略,需修改 Student 类)
/*
class Student implements Comparable<Student> {
// ... 其他代码不变
@Override
public int compareTo(Student o) {
return o.getAge() - this.getAge(); // 倒序
}
}
Arrays.sort(students); // 直接排序
*/
// 排序后打印
System.out.println("排序后(年龄倒序):" + Arrays.toString(students));
}
}运行结果
排序前:[Student{name='张三', age=20}, Student{name='李四', age=22}, Student{name='王五', age=19}]
排序后(年龄倒序):[Student{name='李四', age=22}, Student{name='张三', age=20}, Student{name='王五', age=19}]三、总结
- 代码块:静态代码块负责类初始化(类加载时执行一次),实例代码块负责对象初始化(创建对象时执行,在构造器前),核心是掌握执行顺序(父静→子静→父实→父构→子实→子构)。
内部类:
- 成员内部类:依赖外部类实例,可访问外部类所有成员。
- 静态内部类:不依赖外部类,仅访问外部类静态成员。
- 匿名内部类:简化代码,常用于作为方法实参传递(接口/抽象类类型)。
- 核心价值:代码块优化初始化逻辑,内部类提高封装性,两者结合可编写更简洁、高效的 Java 程序,是实际项目中的常用技术。