大家好,今天我们来学习《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 <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 个标准元注解:
@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 {
@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)中的使用。建议大家多动手练习,理解每种类型的适用场景,才能在实际开发中灵活运用。
如果有任何疑问,欢迎在评论区留言讨论!