????????大家好,今天我們來學習《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)中的使用。建議大家多動手練習,理解每種類型的適用場景,才能在實際開發中靈活運用。
如果有任何疑問,歡迎在評論區留言討論!