大家好,今天我们来学习《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() {@Overridepublic void sayHello() {System.out.println("匿名内部类实现接口:Hello World!");}};greeting.sayHello();// 2. 匿名内部类继承父类Animal dog = new Animal() {@Overridepublic void cry() {System.out.println("匿名内部类继承父类:汪汪汪~");}};dog.cry();// 3. 实际开发中常见场景:作为方法参数useGreeting(new Greeting() {@Overridepublic 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:确保重写父类方法@Overridepublic String toString() {return "使用@Override重写toString()";}// 2. @Deprecated:标记方法已过时@Deprecatedpublic void oldMethod() {System.out.println("这是一个已过时的方法");}// 3. @SuppressWarnings:抑制警告(这里抑制"未使用变量"警告)@SuppressWarnings("unused")public void testWarning() {int unusedVar = 10; // 若不加@SuppressWarnings,会有"未使用变量"警告}// 4. @SafeVarargs:标记可变参数类型安全@SafeVarargspublic 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.0public 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 个标准元注解:
@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
@Inherited
public @interface MyAnnotation {String value() default "默认值";
}// 使用自定义注解(类级别)
@MyAnnotation("父类注解")
class ParentClass {// 使用自定义注解(方法级别)@MyAnnotation("父类方法注解")public void parentMethod() {}
}// 子类继承父类(会继承@MyAnnotation)
class ChildClass extends ParentClass {@Overridepublic 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 = "北京市海淀区";// 成员内部类:Teacherpublic 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);}}// 静态内部类:Studentpublic 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)中的使用。建议大家多动手练习,理解每种类型的适用场景,才能在实际开发中灵活运用。
如果有任何疑问,欢迎在评论区留言讨论!