当前位置: 首页 > news >正文

《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() {@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 个标准注解:

  1. @Override:标记方法重写父类方法(编译器会检查是否正确重写)
  2. @Deprecated:标记方法 / 类已过时(使用时会有警告)
  3. @SuppressWarnings抑制编译器警告(如未使用变量警告)
  4. @SafeVarargs:Java 7+,标记可变参数方法是类型安全
  5. @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 个标准元注解:

  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 {@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 中的三个重要特性:

  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 = "北京市海淀区";// 成员内部类: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注解(包含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)中的使用。建议大家多动手练习,理解每种类型的适用场景,才能在实际开发中灵活运用。

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

http://www.lryc.cn/news/603850.html

相关文章:

  • 实在智能Agent智能体荣登全球“Go_Global_AI_100”百强榜,中国AI走向世界!
  • STM32——HAL库
  • 什么是EasyVR shield 3?如何设置EasyVR shield 3
  • 大模型应用开发模拟面试
  • 用动态的观点看加锁
  • TCMalloc 内存分配原理简析
  • 2-verilog-基础语法
  • Coze Studio概览(三)--智能体管理
  • sqli-labs通关笔记-第24关 SQL二次注入(单引号闭合)
  • 硬件学习笔记--73 电能表新旧精度等级对应关系
  • debug redis里面的lua脚本
  • Spring Boot 防重放攻击全面指南:原理、方案与最佳实践
  • ElasticSearch 的3种数据迁移方案
  • 在Word和WPS文字中把全角数字全部改为半角
  • Vue2学习-MVVM模型
  • Spring Boot 简单接口角色授权检查实现
  • C++入门知识学习(上)
  • 嵌入式学习日志(十一)
  • css3之三维变换详说
  • SQL Server中的分页查询
  • leetcode热题——螺旋矩阵
  • 第十一天:不定方程求解
  • 镁金属接骨螺钉注册检测:骨科植入安全的科学基石
  • Rust基础-part8-模式匹配、常见集合
  • 亚马逊 Vine 计划:评论生态重构与合规运营策略
  • 学习笔记-中华心法问答系统的性能提升
  • 小孙学变频学习笔记(十二)机械特性的调整 机械特性的改善
  • 想要批量提取视频背景音乐?FFmpeg 和转换器都安排上
  • Day 25:异常处理
  • VTK开发笔记(一):VTK介绍,Qt5.9.3+VS2017x64+VTK8.2编译