黑马学习笔记Day11

2025-11-14 黑马程序员学习笔记 97 0

Java 面向对象高级:代码块与内部类详解

本文基于学习文档,系统梳理代码块(类初始化机制)和内部类(四种类型及用法)的核心知识点、执行规则及实战案例,包含作业题完整解答,助力夯实 Java 面向对象进阶技能。

一、代码块

1. 核心概念

代码块是类的五大成分之一(字段、构造器、方法、代码块、内部类),用于完成类或对象的初始化,分为静态代码块实例代码块两类。

2. 静态代码块

(1)基本语法与特点

  • 格式:static { 代码逻辑 }
  • 执行时机:类加载时自动执行,类仅加载一次,故静态代码块仅执行一次。
  • 作用:初始化静态变量(如全局常量赋值、资源预加载)。
  • 访问规则:只能访问类的静态成员(静态变量、静态方法),不能访问实例成员。

(2)类加载触发时机(主动引用才触发初始化)

  • 实例化对象:new MyClass()
  • 访问静态成员:MyClass.staticFieldMyClass.staticMethod()(final 修饰的静态常量除外)
  • 初始化子类:子类初始化时,父类未初始化则先初始化父类

(3)被动引用(不触发类初始化,可能触发加载)

  • 子类引用父类静态字段:Child.value(仅触发父类加载,不触发子类初始化)
  • 数组定义引用类:MyClass[] arr = new MyClass[10]
  • 访问静态常量:MyClass.CONSTANT(常量编译期已确定,不触发类初始化)

3. 实例代码块

(1)基本语法与特点

  • 格式:{ 代码逻辑 }(无 static 修饰)
  • 执行时机:每次创建对象时执行,执行次数与对象创建次数一致,且在构造器执行前执行。
  • 作用:初始化实例变量(与构造器功能类似,可提取多个构造器的公共初始化逻辑)。
  • 访问规则:可访问类的静态成员和实例成员。

4. 执行顺序总结

(1)单个类内部

静态代码块 → 实例代码块 → 构造器(静态代码块仅执行一次,实例代码块和构造器每次创建对象都执行)。

(2)继承关系(子类 B 继承父类 A)

父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器。

5. 常见判断题(核心结论)

  1. 实例代码块可以访问静态变量(√)
  2. 实例代码块的执行优先于构造方法(√)
  3. 实例代码块在每次创建对象时执行,执行次数与对象创建次数相同(√)
  4. 静态代码块可以访问非静态变量(×)
  5. 静态代码块和实例代码块的执行顺序与它们在代码中的位置无关(×,同类型代码块按定义顺序执行)
  6. 静态代码块的执行优先于实例代码块(√)
  7. 静态代码块只执行一次,实例代码块每次创建对象都执行(√)

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. 内部类作业题:学生数组排序

题目需求

  1. 定义 Student 类,包含 name(姓名)和 age(年龄)属性,提供构造器和 toString() 方法。
  2. 创建 Student[] 数组,添加 3 个学生对象。
  3. 使用 Arrays.sort() 对数组按年龄倒序排序(两种方式实现)。
  4. 打印排序前和排序后的数组。

解答代码(两种排序方式)

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}]

三、总结

  1. 代码块:静态代码块负责类初始化(类加载时执行一次),实例代码块负责对象初始化(创建对象时执行,在构造器前),核心是掌握执行顺序(父静→子静→父实→父构→子实→子构)。
  2. 内部类

    • 成员内部类:依赖外部类实例,可访问外部类所有成员。
    • 静态内部类:不依赖外部类,仅访问外部类静态成员。
    • 匿名内部类:简化代码,常用于作为方法实参传递(接口/抽象类类型)。
  3. 核心价值:代码块优化初始化逻辑,内部类提高封装性,两者结合可编写更简洁、高效的 Java 程序,是实际项目中的常用技术。
最后更新于 2025-11-15 09:41:14