《Java 程序设计》第 9 章 - 内部类、枚举和注解

发布于:2025-07-30 ⋅ 阅读:(19) ⋅ 点赞:(0)

        大家好,今天我们来学习《Java 程序设计》第 9 章的内容 —— 内部类、枚举和注解。这三个知识点是 Java 中提升代码灵活性和可读性的重要工具,在实际开发中非常常用。接下来我们逐一展开讲解,每个知识点都会配上可直接运行的代码示例,方便大家动手实践。

思维导图

9.1 内部类

内部类是定义在另一个类(外部类)内部的类。它的主要作用是:

  • 封装性更好(内部类可以访问外部类的私有成员,而外部类外无法直接访问内部类
  • 逻辑上更紧密(当一个类只服务于另一个类时,适合定义为内部类)

内部类分为四种:成员内部类局部内部类匿名内部类静态内部类

9.1.1 成员内部类

成员内部类是定义在外部类的成员位置(与成员变量、成员方法同级)的内部类。

特点

  • 依赖外部类实例存在(必须先创建外部类对象,才能创建内部类对象)
  • 可以访问外部类的所有成员(包括私有)
  • 外部类可以通过内部类对象访问内部类成员

示例代码

// 外部类
public class OuterClass {
    private String outerName = "外部类私有成员";
    private int outerAge = 20;

    // 成员内部类
    public class InnerClass {
        private String innerName = "内部类私有成员";

        // 内部类方法
        public void showOuterInfo() {
            // 访问外部类成员(包括私有)
            System.out.println("外部类的name:" + outerName);
            System.out.println("外部类的age:" + outerAge);
        }

        public void showInnerInfo() {
            System.out.println("内部类的name:" + innerName);
        }
    }

    // 外部类方法:创建内部类对象并使用
    public void useInner() {
        InnerClass inner = new InnerClass();
        inner.showInnerInfo();
        System.out.println("通过外部类访问内部类私有成员:" + inner.innerName);
    }

    public static void main(String[] args) {
        // 1. 创建外部类对象
        OuterClass outer = new OuterClass();

        // 2. 创建内部类对象(必须通过外部类对象)
        OuterClass.InnerClass inner = outer.new InnerClass();

        // 3. 调用内部类方法
        inner.showOuterInfo();
        inner.showInnerInfo();

        // 4. 调用外部类方法(该方法内部使用了内部类)
        outer.useInner();
    }
}

运行结果

9.1.2 局部内部类

局部内部类是定义在外部类的方法代码块中的内部类(类似局部变量)。

特点

  • 只在定义它的方法 / 代码块内有效
  • 可以访问外部类的所有成员
  • 可以访问方法中的局部变量,但该变量必须是final(Java 8 后可省略,但仍需满足 "事实不可变")

示例代码

public class LocalInnerDemo {
    private String outerField = "外部类私有字段";

    public void outerMethod() {
        // 局部变量(事实不可变,等效于final)
        String localVar = "方法局部变量";

        // 局部内部类(定义在方法中)
        class LocalInnerClass {
            private String innerField = "局部内部类字段";

            public void innerMethod() {
                // 访问外部类成员
                System.out.println("访问外部类字段:" + outerField);
                // 访问方法局部变量(必须不可变)
                System.out.println("访问局部变量:" + localVar);
                // 访问内部类自身成员
                System.out.println("访问内部类字段:" + innerField);
            }
        }

        // 只能在当前方法中创建局部内部类对象并使用
        LocalInnerClass inner = new LocalInnerClass();
        inner.innerMethod();
    }

    public static void main(String[] args) {
        LocalInnerDemo outer = new LocalInnerDemo();
        outer.outerMethod();
    }
}

运行结果

9.1.3 匿名内部类

匿名内部类是没有类名的局部内部类,通常用于快速实现接口或继承类,简化代码。

特点

  • 没有类名,只能创建一次对象
  • 必须继承一个类或实现一个接口
  • 定义和创建对象同时进行

示例代码

// 定义一个接口
interface Greeting {
    void sayHello();
}

// 定义一个父类
class Animal {
    public void cry() {
        System.out.println("动物叫");
    }
}

public class AnonymousInnerDemo {
    public static void main(String[] args) {
        // 1. 匿名内部类实现接口
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("匿名内部类实现接口:Hello World!");
            }
        };
        greeting.sayHello();

        // 2. 匿名内部类继承父类
        Animal dog = new Animal() {
            @Override
            public void cry() {
                System.out.println("匿名内部类继承父类:汪汪汪~");
            }
        };
        dog.cry();

        // 3. 实际开发中常见场景:作为方法参数
        useGreeting(new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("作为参数的匿名内部类:你好!");
            }
        });
    }

    // 接收Greeting接口实现类的方法
    public static void useGreeting(Greeting g) {
        g.sayHello();
    }
}

运行结果

说明:匿名内部类在 Java 8 后可被 Lambda 表达式替代(接口只有一个方法时),但匿名内部类仍有其不可替代的场景(如需要实现多个方法或继承类时)。

9.1.4 静态内部类

静态内部类是用static修饰的内部类,定义在外部类的成员位置

特点

  • 不依赖外部类实例(可直接通过外部类名创建对象)
  • 只能访问外部类的静态成员(不能访问非静态成员)
  • 外部类和内部类的静态成员可以互相访问

示例代码

public class OuterStatic {
    // 外部类静态成员
    private static String staticField = "外部静态字段";
    // 外部类非静态成员
    private String nonStaticField = "外部非静态字段";

    // 静态内部类
    public static class StaticInner {
        private String innerField = "静态内部类字段";

        public void innerMethod() {
            // 可以访问外部类静态成员
            System.out.println("访问外部静态字段:" + staticField);
            // 不能访问外部类非静态成员(编译报错)
            // System.out.println("访问外部非静态字段:" + nonStaticField);
            
            // 访问内部类自身成员
            System.out.println("访问内部类字段:" + innerField);
        }
    }

    public void outerMethod() {
        // 外部类访问静态内部类:直接通过类名创建
        StaticInner inner = new StaticInner();
        inner.innerMethod();
    }

    public static void main(String[] args) {
        // 1. 不创建外部类对象,直接创建静态内部类对象
        OuterStatic.StaticInner inner = new OuterStatic.StaticInner();
        inner.innerMethod();

        // 2. 通过外部类对象调用方法(方法内部使用静态内部类)
        OuterStatic outer = new OuterStatic();
        outer.outerMethod();
    }
}

运行结果

9.2 枚举类型

枚举(Enum)是 Java 5 引入的类型,用于定义固定数量的常量集合(如季节、星期、状态等)。

9.2.1 枚举类型的定义

枚举使用enum关键字定义每个常量之间用逗号分隔,末尾可省略分号

示例代码

// 定义枚举类型:季节
enum Season {
    SPRING,  // 春天
    SUMMER,  // 夏天
    AUTUMN,  // 秋天
    WINTER   // 冬天
}

public class EnumDefineDemo {
    public static void main(String[] args) {
        // 使用枚举常量
        Season currentSeason = Season.SUMMER;
        System.out.println("当前季节:" + currentSeason);

        // 枚举常量本质是枚举类的实例
        System.out.println("枚举常量的类型:" + currentSeason.getClass());
        System.out.println("枚举类名:" + Season.class.getName());
    }
}

运行结果

9.2.2 枚举类型的方法

枚举类默认继承java.lang.Enum,自带以下常用方法:

  • values()返回所有枚举常量的数组(顺序与定义一致)
  • valueOf(String name):根据名称获取枚举常量(名称必须完全匹配)
  • ordinal():返回枚举常量的索引(从 0 开始)
  • name():返回枚举常量的名称

此外,枚举还可以自定义方法。

示例代码

enum Week {
    MONDAY("周一", 1),
    TUESDAY("周二", 2),
    WEDNESDAY("周三", 3),
    THURSDAY("周四", 4),
    FRIDAY("周五", 5),
    SATURDAY("周六", 6),
    SUNDAY("周日", 7);

    // 枚举的成员变量
    private String chineseName;
    private int index;

    // 构造方法(见9.2.4)
    Week(String chineseName, int index) {
        this.chineseName = chineseName;
        this.index = index;
    }

    // 自定义方法:获取中文名称
    public String getChineseName() {
        return chineseName;
    }

    // 自定义方法:判断是否为周末
    public boolean isWeekend() {
        return this == SATURDAY || this == SUNDAY;
    }
}

public class EnumMethodDemo {
    public static void main(String[] args) {
        // 1. 自带方法:values()
        Week[] weeks = Week.values();
        System.out.println("所有星期:");
        for (Week week : weeks) {
            System.out.println(week + " -> 索引:" + week.ordinal() + ",中文:" + week.getChineseName());
        }

        // 2. 自带方法:valueOf()
        Week friday = Week.valueOf("FRIDAY");
        System.out.println("\n通过valueOf获取:" + friday.getChineseName());

        // 3. 自定义方法
        System.out.println(Week.SATURDAY.getChineseName() + "是周末吗?" + Week.SATURDAY.isWeekend());
        System.out.println(Week.MONDAY.getChineseName() + "是周末吗?" + Week.MONDAY.isWeekend());
    }
}

运行结果

9.2.3 枚举在 switch 中的应用

枚举非常适合在switch语句中使用,代码更清晰易读(避免使用魔法数字)。

示例代码

// 定义状态枚举
enum OrderStatus {
    UNPAID,    // 未支付
    PAID,      // 已支付
    SHIPPED,   // 已发货
    RECEIVED   // 已收货
}

public class EnumSwitchDemo {
    public static void main(String[] args) {
        OrderStatus status = OrderStatus.PAID;
        handleOrder(status);
    }

    // 根据订单状态处理订单
    public static void handleOrder(OrderStatus status) {
        switch (status) {
            case UNPAID:
                System.out.println("订单未支付,请尽快付款");
                break;
            case PAID:
                System.out.println("订单已支付,准备发货");
                break;
            case SHIPPED:
                System.out.println("订单已发货,请耐心等待");
                break;
            case RECEIVED:
                System.out.println("订单已收货,交易完成");
                break;
            default:
                System.out.println("未知状态");
        }
    }
}

运行结果

9.2.4 枚举类型的构造方法

枚举的构造方法必须是私有的(默认 private,不可显式声明为 public),用于初始化枚举常量的成员变量。

示例代码(延续 9.2.2 的 Week 枚举,补充说明):

enum Color {
    // 枚举常量(调用构造方法)
    RED("红色", "#FF0000"),
    GREEN("绿色", "#00FF00"),
    BLUE("蓝色", "#0000FF");

    // 成员变量
    private String desc;  // 颜色描述
    private String code;  // 十六进制代码

    // 私有构造方法(只能在枚举内部调用)
    Color(String desc, String code) {
        this.desc = desc;
        this.code = code;
    }

    //  getter方法
    public String getDesc() {
        return desc;
    }

    public String getCode() {
        return code;
    }
}

public class EnumConstructorDemo {
    public static void main(String[] args) {
        // 遍历所有颜色枚举
        for (Color color : Color.values()) {
            System.out.println(color + ":" + color.getDesc() + ",代码:" + color.getCode());
        }
    }
}

运行结果

说明:枚举常量的定义顺序就是调用构造方法的顺序,每个常量对应一次构造方法调用。

9.3 注解类型

        注解(Annotation)是 Java 5 引入的元数据(描述数据的数据),用于修饰代码(类、方法、变量等),不直接影响代码运行但可被编译器或框架解析使用

9.3.1 注解概述

注解的作用

  • 编译检查(如@Override确保方法正确重写)
  • 代码分析(如框架通过注解识别配置)
  • 生成文档(如@param用于生成 API 文档)

注解的使用格式@注解名(属性=值),若属性为 value 且只有一个属性,可省略value=

9.3.2 标准注解

Java 内置了 5 个标准注解:

  1. @Override:标记方法重写父类方法(编译器会检查是否正确重写)
  2. @Deprecated:标记方法 / 类已过时(使用时会有警告)
  3. @SuppressWarnings抑制编译器警告(如未使用变量警告)
  4. @SafeVarargs:Java 7+,标记可变参数方法是类型安全
  5. @FunctionalInterface:Java 8+,标记接口是函数式接口(只有一个抽象方法)

示例代码

public class StandardAnnotationDemo {

    // 1. @Override:确保重写父类方法
    @Override
    public String toString() {
        return "使用@Override重写toString()";
    }

    // 2. @Deprecated:标记方法已过时
    @Deprecated
    public void oldMethod() {
        System.out.println("这是一个已过时的方法");
    }

    // 3. @SuppressWarnings:抑制警告(这里抑制"未使用变量"警告)
    @SuppressWarnings("unused")
    public void testWarning() {
        int unusedVar = 10;  // 若不加@SuppressWarnings,会有"未使用变量"警告
    }

    // 4. @SafeVarargs:标记可变参数类型安全
    @SafeVarargs
    public final <T> void safeMethod(T... args) {
        for (T t : args) {
            System.out.println(t);
        }
    }

    public static void main(String[] args) {
        StandardAnnotationDemo demo = new StandardAnnotationDemo();
        System.out.println(demo.toString());

        // 调用过时方法(会有警告)
        demo.oldMethod();

        // 调用安全可变参数方法
        demo.safeMethod("a", "b", "c");
    }
}

// 5. @FunctionalInterface:标记函数式接口
@FunctionalInterface
interface MyFunction {
    void doSomething();
    // 函数式接口只能有一个抽象方法,若再添加会报错
    // void doAnother();
}

运行结果

9.3.3 定义注解类型

自定义注解使用@interface关键字格式与接口类似,但可包含属性(类似接口的方法,但有默认值)。

示例代码

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 自定义注解:用于标记方法的作者和版本(结合元注解,见9.3.4)
@Target(ElementType.METHOD)  // 只能用于方法
@Retention(RetentionPolicy.RUNTIME)  // 运行时保留(可通过反射获取)
public @interface MethodInfo {
    // 注解属性(格式:类型 属性名() [default 默认值];)
    String author();  // 作者(无默认值,使用时必须指定)
    String version() default "1.0";  // 版本(有默认值,可省略)
    String[] tags() default {};  // 标签(数组类型)
}

// 使用自定义注解
public class CustomAnnotationDemo {

    @MethodInfo(author = "张三", version = "1.2", tags = {"工具", "计算"})
    public int add(int a, int b) {
        return a + b;
    }

    @MethodInfo(author = "李四")  // 版本使用默认值1.0
    public void printInfo() {
        System.out.println("使用自定义注解的方法");
    }

    public static void main(String[] args) throws Exception {
        // 通过反射获取注解信息(后续章节会学习反射,此处了解即可)
        MethodInfo info = CustomAnnotationDemo.class.getMethod("add", int.class, int.class).getAnnotation(MethodInfo.class);
        System.out.println("add方法作者:" + info.author());
        System.out.println("add方法版本:" + info.version());
        System.out.println("add方法标签:" + String.join(",", info.tags()));
    }
}

运行结果

add方法作者:张三
add方法版本:1.2
add方法标签:工具,计算

9.3.4 标准元注解

元注解是修饰注解的注解,用于指定注解的适用范围、保留策略等。Java 提供了 5 个标准元注解:

  1. @Target:指定注解可修饰的元素类型(如类、方法、变量等),取值为ElementType枚举(如TYPEMETHODFIELD
  2. @Retention:指定注解的保留策略,取值为RetentionPolicy枚举:
    • SOURCE:只在源码中保留(编译后丢弃)
    • CLASS:编译后保留在 class 文件中(运行时丢弃,默认)
    • RUNTIME:运行时保留(可通过反射获取)
  3. @Documented:标记注解会被javadoc工具提取到文档中
  4. @Inherited:标记注解可被子类继承(仅对类注解有效)
  5. @Repeatable:Java 8+,标记注解可重复修饰同一元素

示例代码(综合使用元注解):

import java.lang.annotation.*;

// 元注解:可修饰类和方法,运行时保留,生成文档,可被继承
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
    String value() default "默认值";
}

// 使用自定义注解(类级别)
@MyAnnotation("父类注解")
class ParentClass {
    // 使用自定义注解(方法级别)
    @MyAnnotation("父类方法注解")
    public void parentMethod() {}
}

// 子类继承父类(会继承@MyAnnotation)
class ChildClass extends ParentClass {
    @Override
    public void parentMethod() {}
}

public class MetaAnnotationDemo {
    public static void main(String[] args) {
        // 查看子类是否继承了父类的类注解
        MyAnnotation classAnno = ChildClass.class.getAnnotation(MyAnnotation.class);
        System.out.println("子类继承的类注解:" + classAnno.value());

        // 查看子类方法是否有注解(未重写时继承,重写后需显式添加)
        try {
            MyAnnotation methodAnno = ChildClass.class.getMethod("parentMethod").getAnnotation(MyAnnotation.class);
            System.out.println("子类方法的注解:" + (methodAnno == null ? "无(因重写未显式添加)" : methodAnno.value()));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

运行结果

子类继承的类注解:父类注解
子类方法的注解:无(因重写未显式添加)

9.4 小结

本章我们学习了 Java 中的三个重要特性:

  1. 内部类

    • 成员内部类:依赖外部类实例,可访问外部类所有成员
    • 局部内部类:定义在方法中,仅在方法内有效
    • 匿名内部类:无类名,快速实现接口 / 继承类
    • 静态内部类:不依赖外部类实例,仅访问外部类静态成员
  2. 枚举类型

    • enum定义,包含固定常量集合
    • 自带values()valueOf()等方法,可自定义方法
    • 适合在switch中使用,构造方法必须私有
  3. 注解类型

    • 元数据,用于修饰代码
    • 标准注解:@Override@Deprecated
    • 自定义注解用@interface,需配合元注解使用
    • 元注解:@Target@Retention等,控制注解行为

编程练习

练习 1:内部类综合应用

需求:定义一个外部类School,包含成员内部类Teacher和静态内部类Student,分别记录教师和学生信息,最后在main方法中创建对象并打印信息。

参考答案

public class School {
    private String schoolName = "阳光中学";
    private static String address = "北京市海淀区";

    // 成员内部类:Teacher
    public class Teacher {
        private String name;
        private String subject;

        public Teacher(String name, String subject) {
            this.name = name;
            this.subject = subject;
        }

        public void showInfo() {
            System.out.println("教师:" + name + ",教授" + subject + ",学校:" + schoolName);
        }
    }

    // 静态内部类:Student
    public static class Student {
        private String name;
        private int grade;

        public Student(String name, int grade) {
            this.name = name;
            this.grade = grade;
        }

        public void showInfo() {
            System.out.println("学生:" + name + ",年级:" + grade + ",学校地址:" + address);
        }
    }

    public static void main(String[] args) {
        // 创建外部类对象
        School school = new School();

        // 创建成员内部类对象
        School.Teacher teacher = school.new Teacher("王老师", "数学");
        teacher.showInfo();

        // 创建静态内部类对象
        School.Student student = new School.Student("小明", 3);
        student.showInfo();
    }
}

练习 2:枚举与注解综合应用

需求:定义一个Gender枚举(男 / 女),自定义一个UserInfo注解(包含nameagegender属性),用该注解修饰一个User,最后通过反射获取注解信息并打印。

参考答案

import java.lang.annotation.*;

// 定义性别枚举
enum Gender {
    MALE("男"), FEMALE("女");

    private String desc;
    Gender(String desc) {
        this.desc = desc;
    }
    public String getDesc() {
        return desc;
    }
}

// 自定义用户信息注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserInfo {
    String name();
    int age();
    Gender gender();
}

// 使用注解修饰User类
@UserInfo(name = "张三", age = 25, gender = Gender.MALE)
class User {}

public class EnumAnnotationPractice {
    public static void main(String[] args) {
        // 通过反射获取User类的注解
        UserInfo info = User.class.getAnnotation(UserInfo.class);
        if (info != null) {
            System.out.println("用户信息:");
            System.out.println("姓名:" + info.name());
            System.out.println("年龄:" + info.age());
            System.out.println("性别:" + info.gender().getDesc());
        }
    }
}

总结

        本章内容在 Java 开发中应用广泛,尤其是内部类在 GUI 编程(如事件监听)、枚举在状态管理、注解在框架(如 Spring)中的使用。建议大家多动手练习,理解每种类型的适用场景,才能在实际开发中灵活运用。

如果有任何疑问,欢迎在评论区留言讨论!


网站公告

今日签到

点亮在社区的每一天
去签到