《Java 程序设计》第 9 章 - 内部类、枚举和注解
大家好,今天我们来学习《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 个标准注解:
@Override:标记方法重写父类方法(编译器会检查是否正确重写)@Deprecated:标记方法 / 类已过时(使用时会有警告)@SuppressWarnings:抑制编译器警告(如未使用变量警告)@SafeVarargs:Java 7+,标记可变参数方法是类型安全的@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 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:标记函数式接口@FunctionalInterfaceinterface 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.2add方法标签:工具,计算
9.3.4 标准元注解
元注解是修饰注解的注解,用于指定注解的适用范围、保留策略等。Java 提供了 5 个标准元注解:
@Target:指定注解可修饰的元素类型(如类、方法、变量等),取值为ElementType枚举(如TYPE、METHOD、FIELD)@Retention:指定注解的保留策略,取值为RetentionPolicy枚举:SOURCE:只在源码中保留(编译后丢弃)CLASS:编译后保留在 class 文件中(运行时丢弃,默认)RUNTIME:运行时保留(可通过反射获取)
@Documented:标记注解会被javadoc工具提取到文档中@Inherited:标记注解可被子类继承(仅对类注解有效)@Repeatable:Java 8+,标记注解可重复修饰同一元素
示例代码(综合使用元注解):
import java.lang.annotation.*;// 元注解:可修饰类和方法,运行时保留,生成文档,可被继承@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @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 中的三个重要特性:
-
内部类:
- 成员内部类:依赖外部类实例,可访问外部类所有成员
- 局部内部类:定义在方法中,仅在方法内有效
- 匿名内部类:无类名,快速实现接口 / 继承类
- 静态内部类:不依赖外部类实例,仅访问外部类静态成员
-
枚举类型:
- 用
enum定义,包含固定常量集合 - 自带
values()、valueOf()等方法,可自定义方法 - 适合在
switch中使用,构造方法必须私有
- 用
-
注解类型:
- 元数据,用于修饰代码
- 标准注解:
@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注解(包含name、age、gender属性),用该注解修饰一个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)中的使用。建议大家多动手练习,理解每种类型的适用场景,才能在实际开发中灵活运用。
如果有任何疑问,欢迎在评论区留言讨论!












