javaSE泛型、反射與注解的核心原理與實際應(yīng)用
前言
泛型、反射與注解是 JavaSE 中支撐 “靈活編程” 與 “框架設(shè)計” 的核心技術(shù)。泛型解決 “類型安全” 問題,反射實現(xiàn) “運行時動態(tài)操作類”,注解提供 “代碼標(biāo)記與元數(shù)據(jù)” 能力 —— 三者結(jié)合構(gòu)成了 Java 框架(如 Spring、MyBatis)的底層基礎(chǔ)。本章節(jié)將系統(tǒng)講解這三項技術(shù)的核心原理與實際應(yīng)用。
一、泛型(Generic):編譯時的類型安全保障
在泛型出現(xiàn)之前,集合(如List)默認(rèn)存儲Object類型,取出元素時需強制轉(zhuǎn)換,容易出現(xiàn)ClassCastException(類型轉(zhuǎn)換異常)。泛型通過 “編譯時類型指定”,讓集合只能存儲特定類型元素,從源頭避免類型錯誤。
1.1 泛型的核心作用
- 編譯時類型檢查:限制集合(或泛型類)只能存儲指定類型元素,編譯階段就報錯,而非運行時崩潰。
- 避免強制轉(zhuǎn)換:取出元素時無需手動轉(zhuǎn)換(編譯器自動確認(rèn)類型)。
- 代碼復(fù)用:一套邏輯支持多種類型(如
List<String>、List<Integer>共用List的實現(xiàn))。
1.2 泛型的基本用法
1.2.1 泛型類與泛型接口
泛型類 / 接口在定義時聲明 “類型參數(shù)”(如<T>),使用時指定具體類型(如String)。
泛型類示例:
// 定義泛型類:聲明類型參數(shù)T(Type的縮寫,可自定義名稱)
class GenericBox<T> {
// 使用T作為類型(類似變量,代表一種類型)
private T value;
// T作為方法參數(shù)和返回值
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class GenericClassDemo {
public static void main(String[] args) {
// 使用時指定類型為String(只能存String)
GenericBox<String> stringBox = new GenericBox<>();
stringBox.setValue("Hello"); // 正確:存入String
// stringBox.setValue(123); // 編譯錯誤:不能存Integer
// 取出時無需轉(zhuǎn)換(自動為String類型)
String str = stringBox.getValue();
System.out.println(str); // 輸出:Hello
// 指定類型為Integer
GenericBox<Integer> intBox = new GenericBox<>();
intBox.setValue(100);
Integer num = intBox.getValue(); // 無需轉(zhuǎn)換
System.out.println(num); // 輸出:100
}
}
泛型接口示例:
// 定義泛型接口(支持多種類型的“生產(chǎn)者”)
interface Producer<T> {
T produce();
}
// 實現(xiàn)泛型接口時指定具體類型(如String)
class StringProducer implements Producer<String> {
@Override
public String produce() {
return "生產(chǎn)的字符串";
}
}
// 實現(xiàn)時保留泛型(讓子類也成為泛型類)
class NumberProducer<T extends Number> implements Producer<T> {
private T value;
public NumberProducer(T value) {
this.value = value;
}
@Override
public T produce() {
return value;
}
}
public class GenericInterfaceDemo {
public static void main(String[] args) {
Producer<String> strProducer = new StringProducer();
String str = strProducer.produce();
System.out.println(str); // 輸出:生產(chǎn)的字符串
Producer<Integer> intProducer = new NumberProducer<>(100);
Integer num = intProducer.produce();
System.out.println(num); // 輸出:100
}
}
1.2.2 泛型方法
泛型方法在方法聲明時獨立聲明類型參數(shù)(與類是否泛型無關(guān)),適用于 “單個方法需要支持多種類型” 的場景。
class GenericMethodDemo {
// 定義泛型方法:<E>是方法的類型參數(shù),聲明在返回值前
public <E> void printArray(E[] array) {
for (E element : array) {
System.out.print(element + " ");
}
System.out.println();
}
// 泛型方法帶返回值
public <E> E getFirstElement(E[] array) {
if (array != null && array.length > 0) {
return array[0];
}
return null;
}
}
public class TestGenericMethod {
public static void main(String[] args) {
GenericMethodDemo demo = new GenericMethodDemo();
// 調(diào)用時自動推斷類型(無需顯式指定)
String[] strArray = {"A", "B", "C"};
demo.printArray(strArray); // 輸出:A B C
Integer[] intArray = {1, 2, 3};
demo.printArray(intArray); // 輸出:1 2 3
// 獲取第一個元素(自動返回對應(yīng)類型)
String firstStr = demo.getFirstElement(strArray);
Integer firstInt = demo.getFirstElement(intArray);
System.out.println("字符串?dāng)?shù)組第一個元素:" + firstStr); // 輸出:A
System.out.println("整數(shù)數(shù)組第一個元素:" + firstInt); // 輸出:1
}
}
泛型方法特點:
- 類型參數(shù)聲明在方法返回值前(如
<E>),與類的泛型參數(shù)無關(guān)。- 調(diào)用時編譯器自動推斷類型(無需手動指定,如傳入
String[]則E自動為String)。
1.2.3 類型通配符(Wildcard)
當(dāng)需要處理 “未知類型的泛型” 時(如方法參數(shù)需要接收任意泛型List),使用通配符?及限定符(extends、super)控制類型范圍。
| 通配符形式 | 含義 | 適用場景 |
|---|---|---|
<?> | 任意類型(無限制) | 僅讀取元素,不修改(如打印任意 List) |
<? extends T> | 上限:只能是 T 或 T 的子類 | 讀取(可獲取 T 類型),不能添加(除 null) |
<? super T> | 下限:只能是 T 或 T 的父類 | 添加(可添加 T 或子類),讀取只能到 Object |
通配符示例:
import java.util.ArrayList;
import java.util.List;
public class WildcardDemo {
// 1. 無限制通配符<?>:接收任意List,只能讀,不能寫(除null)
public static void printList(List<?> list) {
for (Object obj : list) { // 只能用Object接收
System.out.print(obj + " ");
}
System.out.println();
// list.add("A"); // 編譯錯誤:無法確定類型,不能添加非null元素
}
// 2. 上限通配符<? extends Number>:只能接收Number或其子類(如Integer、Double)
public static double sum(List<? extends Number> list) {
double total = 0;
for (Number num : list) { // 可安全轉(zhuǎn)為Number
total += num.doubleValue(); // 調(diào)用Number的方法
}
return total;
}
// 3. 下限通配符<? super Integer>:只能接收Integer或其父類(如Number、Object)
public static void addIntegers(List<? super Integer> list) {
list.add(10); // 可添加Integer(或其子類,如Integer本身)
list.add(20);
// Integer num = list.get(0); // 編譯錯誤:只能用Object接收
}
public static void main(String[] args) {
// 測試<?>
List<String> strList = List.of("A", "B");
List<Integer> intList = List.of(1, 2);
printList(strList); // 輸出:A B
printList(intList); // 輸出:1 2
// 測試<? extends Number>
List<Integer> integerList = List.of(1, 2, 3);
List<Double> doubleList = List.of(1.5, 2.5);
System.out.println("整數(shù)和:" + sum(integerList)); // 輸出:6.0
System.out.println("小數(shù)和:" + sum(doubleList)); // 輸出:4.0
// 測試<? super Integer>
List<Number> numberList = new ArrayList<>();
addIntegers(numberList); // 向NumberList添加Integer
System.out.println("添加后:" + numberList); // 輸出:[10, 20]
}
}
1.3 泛型的局限
- 不能用基本類型:泛型參數(shù)只能是引用類型(如
List<int>錯誤,需用List<Integer>)。 - 運行時擦除:泛型信息在編譯后被擦除(如
List<String>和List<Integer>運行時都是List),無法通過instanceof判斷泛型類型。 - 靜態(tài)方法不能用類的泛型參數(shù):靜態(tài)方法屬于類,而泛型參數(shù)隨對象變化,若需泛型需定義為泛型方法。
二、反射(Reflection):運行時的類信息操作
反射允許程序在運行時獲取類的信息(如類名、屬性、方法、構(gòu)造器),并動態(tài)操作這些成分(如調(diào)用私有方法、修改私有屬性)。這打破了 “編譯時確定代碼邏輯” 的限制,讓程序更靈活(也是框架實現(xiàn) “自動裝配”“依賴注入” 的核心)。
2.1 反射的核心作用
- 運行時獲取類信息:無需提前知道類名,就能獲取類的屬性、方法等元數(shù)據(jù)。
- 動態(tài)創(chuàng)建對象:通過類信息動態(tài)實例化對象(如
Class.newInstance())。- 動態(tài)調(diào)用方法:包括私有方法(通過反射可繞過訪問權(quán)限)。
- 動態(tài)操作屬性:包括私有屬性(可修改值)。
2.2 反射的核心類
反射的所有操作都基于java.lang.Class類(類對象),它是反射的 “入口”。
| 核心類 / 接口 | 作用 |
|---|---|
Class | 類的元數(shù)據(jù)對象,代表一個類的信息 |
Constructor | 類的構(gòu)造器對象,用于創(chuàng)建實例 |
Method | 類的方法對象,用于調(diào)用方法 |
Field | 類的屬性對象,用于訪問 / 修改屬性值 |
2.3 反射的基本操作
2.3.1 獲取 Class 對象(反射入口)
獲取Class對象有 3 種方式,根據(jù)場景選擇:
class Student {
private String name;
private int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void study() {
System.out.println(name + "正在學(xué)習(xí)");
}
private String getInfo() {
return "姓名:" + name + ",年齡:" + age;
}
}
public class GetClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1:通過對象.getClass()(已知對象)
Student student = new Student();
Class<?> clazz1 = student.getClass();
System.out.println("方式1:" + clazz1.getName()); // 輸出:Student
// 方式2:通過類名.class(已知類名,編譯時確定)
Class<?> clazz2 = Student.class;
System.out.println("方式2:" + clazz2.getName()); // 輸出:Student
// 方式3:通過Class.forName("全類名")(僅知類名,運行時動態(tài)獲取,最常用)
Class<?> clazz3 = Class.forName("Student"); // 全類名:包名+類名(此處默認(rèn)無包)
System.out.println("方式3:" + clazz3.getName()); // 輸出:Student
// 驗證:同一個類的Class對象唯一
System.out.println(clazz1 == clazz2); // 輸出:true
System.out.println(clazz1 == clazz3); // 輸出:true
}
}
說明:一個類的Class對象在 JVM 中唯一,是類加載的產(chǎn)物(類加載時 JVM 自動創(chuàng)建Class對象)。
2.3.2 反射創(chuàng)建對象(通過構(gòu)造器)
通過Class對象獲取Constructor,再調(diào)用newInstance()創(chuàng)建實例(支持無參和有參構(gòu)造)。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectNewInstance {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
// 1. 獲取Class對象
Class<?> clazz = Class.forName("Student");
// 2. 方式1:調(diào)用無參構(gòu)造(若類無無參構(gòu)造,會拋異常)
Student student1 = (Student) clazz.newInstance(); // 已過時,推薦用Constructor
System.out.println("無參構(gòu)造創(chuàng)建:" + student1);
// 3. 方式2:調(diào)用有參構(gòu)造(更靈活,推薦)
// 獲取有參構(gòu)造器(參數(shù)為String和int)
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 傳入?yún)?shù)創(chuàng)建實例
Student student2 = (Student) constructor.newInstance("張三", 18);
System.out.println("有參構(gòu)造創(chuàng)建:" + student2);
}
}
2.3.3 反射調(diào)用方法(包括私有方法)
通過Method對象調(diào)用方法,支持公有和私有方法(私有方法需先設(shè)置setAccessible(true)取消訪問檢查)。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectInvokeMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class<?> clazz = Class.forName("Student");
Student student = (Student) clazz.getConstructor(String.class, int.class).newInstance("張三", 18);
// 1. 調(diào)用公有方法(study())
// 獲取方法:參數(shù)1為方法名,參數(shù)2為參數(shù)類型(無參則不寫)
Method studyMethod = clazz.getMethod("study");
// 調(diào)用方法:參數(shù)1為實例對象,參數(shù)2為方法參數(shù)(無參則不寫)
studyMethod.invoke(student); // 輸出:張三正在學(xué)習(xí)
// 2. 調(diào)用私有方法(getInfo())
// 獲取私有方法需用getDeclaredMethod(getMethod只能獲取公有)
Method getInfoMethod = clazz.getDeclaredMethod("getInfo");
// 取消訪問檢查(關(guān)鍵:私有方法必須設(shè)置,否則拋異常)
getInfoMethod.setAccessible(true);
// 調(diào)用私有方法
String info = (String) getInfoMethod.invoke(student);
System.out.println("私有方法返回:" + info); // 輸出:姓名:張三,年齡:18
}
}
2.3.4 反射操作屬性(包括私有屬性)
通過Field對象訪問或修改屬性,私有屬性同樣需要setAccessible(true)。
import java.lang.reflect.Field;
public class ReflectOperateField {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = Class.forName("Student");
Student student = (Student) clazz.getConstructor().newInstance(); // 無參構(gòu)造創(chuàng)建
// 1. 獲取并修改私有屬性name
Field nameField = clazz.getDeclaredField("name"); // 獲取私有屬性
nameField.setAccessible(true); // 取消訪問檢查
nameField.set(student, "李四"); // 設(shè)置屬性值(參數(shù)1為實例,參數(shù)2為值)
// 2. 獲取并修改私有屬性age
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(student, 20);
// 驗證修改結(jié)果(調(diào)用之前的私有方法getInfo())
Method getInfoMethod = clazz.getDeclaredMethod("getInfo");
getInfoMethod.setAccessible(true);
String info = (String) getInfoMethod.invoke(student);
System.out.println("修改后信息:" + info); // 輸出:姓名:李四,年齡:20
}
}
2.4 反射的應(yīng)用場景
- 框架底層:Spring 的 IOC 容器通過反射創(chuàng)建對象并注入依賴;MyBatis 通過反射將數(shù)據(jù)庫結(jié)果集映射為 Java 對象。
- 注解解析:自定義注解需配合反射獲取注解標(biāo)記的類 / 方法,執(zhí)行對應(yīng)邏輯(如權(quán)限校驗)。
- 動態(tài)代理:AOP 的動態(tài)代理(如 JDK 代理)基于反射實現(xiàn)方法增強。
- 工具類:如 JSON 序列化工具(Jackson、FastJSON)通過反射獲取對象屬性并轉(zhuǎn)為 JSON。
2.5 反射的優(yōu)缺點
- 優(yōu)點:靈活性高,支持運行時動態(tài)操作,是框架的核心技術(shù)。
- 缺點:
- 性能損耗:反射操作繞開編譯優(yōu)化,性能比直接調(diào)用低(但框架中影響可接受)。
- 破壞封裝:可直接訪問私有成員,可能導(dǎo)致代碼邏輯混亂。
- 可讀性差:反射代碼較繁瑣,不如直接調(diào)用直觀。
三、注解(Annotation):代碼的標(biāo)記與元數(shù)據(jù)
注解(Annotation)是 Java 5 引入的特性,本質(zhì)是 “代碼的標(biāo)記”,可在類、方法、屬性等元素上添加,用于攜帶 “元數(shù)據(jù)”(描述數(shù)據(jù)的數(shù)據(jù))。注解本身不直接影響代碼邏輯,但可通過反射解析注解,執(zhí)行對應(yīng)操作。
3.1 注解的核心作用
- 標(biāo)記代碼:如
@Override標(biāo)記方法重寫,編譯器會校驗是否符合重寫規(guī)則。- 攜帶元數(shù)據(jù):如
@Test標(biāo)記測試方法,測試框架會自動執(zhí)行標(biāo)記的方法。- 簡化配置:替代 XML 配置(如 Spring 的
@Controller標(biāo)記控制器類)。
3.2 常用內(nèi)置注解
Java 內(nèi)置了 3 個基本注解(定義在java.lang包),編譯器會識別并處理:
| 注解名稱 | 作用 | 使用位置 |
|---|---|---|
@Override | 標(biāo)記方法為重寫父類的方法,編譯器校驗 | 方法 |
@Deprecated | 標(biāo)記元素(類、方法等)已過時,編譯器警告 | 類、方法、屬性等 |
@SuppressWarnings | 抑制編譯器警告(如未使用變量警告) | 類、方法等 |
內(nèi)置注解示例:
public class BuiltInAnnotationDemo {
// @Deprecated:標(biāo)記方法已過時
@Deprecated
public void oldMethod() {
System.out.println("這是過時的方法");
}
// @Override:標(biāo)記方法重寫(若父類無此方法,編譯報錯)
@Override
public String toString() {
return "BuiltInAnnotationDemo對象";
}
// @SuppressWarnings:抑制“未使用變量”警告
@SuppressWarnings("unused")
public void test() {
int unusedVar = 10; // 若沒有@SuppressWarnings,編譯器會警告“變量未使用”
// 調(diào)用過時方法(編譯器會警告,但可執(zhí)行)
oldMethod();
}
public static void main(String[] args) {
new BuiltInAnnotationDemo().test();
}
}
3.3 元注解:定義注解的注解
自定義注解時,需要用 “元注解”(注解的注解)指定注解的 “作用范圍”“保留策略” 等。Java 提供 4 個元注解(定義在java.lang.annotation包):
| 元注解名稱 | 作用 | 常用值 |
|---|---|---|
@Target | 指定注解可使用的位置(如方法、類) | ElementType.METHOD(方法)、ElementType.TYPE(類)等 |
@Retention | 指定注解的保留策略(生命周期) | RetentionPolicy.RUNTIME(運行時保留,可反射獲?。?/td> |
@Documented | 標(biāo)記注解會被 javadoc 文檔記錄 | 無參數(shù) |
@Inherited | 標(biāo)記注解可被子類繼承 | 無參數(shù) |
核心元注解:@Target和@Retention是自定義注解必須的 ——@Target限制使用位置,@Retention(RetentionPolicy.RUNTIME)確保注解在運行時存在(才能被反射解析)。
3.4 自定義注解及解析
自定義注解需配合反射使用:先定義注解,再在代碼中標(biāo)記,最后通過反射解析注解并執(zhí)行邏輯。
示例:自定義權(quán)限校驗注解
import java.lang.annotation.*;
import java.lang.reflect.Method;
// 1. 定義自定義注解
@Target(ElementType.METHOD) // 注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) // 運行時保留,可反射獲取
@interface RequirePermission {
// 注解屬性(類似方法,可指定默認(rèn)值)
String value(); // 權(quán)限名稱(如"admin")
}
// 2. 使用注解標(biāo)記方法
class UserService {
// 標(biāo)記需要"admin"權(quán)限才能執(zhí)行
@RequirePermission("admin")
public void deleteUser() {
System.out.println("執(zhí)行刪除用戶操作");
}
// 標(biāo)記需要"user"權(quán)限才能執(zhí)行
@RequirePermission("user")
public void queryUser() {
System.out.println("執(zhí)行查詢用戶操作");
}
}
// 3. 通過反射解析注解,實現(xiàn)權(quán)限校驗
class PermissionChecker {
// 模擬當(dāng)前用戶擁有的權(quán)限
private String currentPermission = "admin";
// 執(zhí)行方法前校驗權(quán)限
public void executeWithCheck(Object obj, String methodName) throws Exception {
// 獲取方法對象
Method method = obj.getClass().getMethod(methodName);
// 判斷方法是否有@RequirePermission注解
if (method.isAnnotationPresent(RequirePermission.class)) {
// 獲取注解對象
RequirePermission annotation = method.getAnnotation(RequirePermission.class);
// 獲取注解的權(quán)限值
String requiredPerm = annotation.value();
// 校驗權(quán)限
if (currentPermission.equals(requiredPerm)) {
method.invoke(obj); // 權(quán)限通過,執(zhí)行方法
} else {
throw new RuntimeException("權(quán)限不足,需要:" + requiredPerm);
}
} else {
// 無注解,直接執(zhí)行
method.invoke(obj);
}
}
}
// 測試
public class CustomAnnotationDemo {
public static void main(String[] args) throws Exception {
UserService userService = new UserService();
PermissionChecker checker = new PermissionChecker();
checker.executeWithCheck(userService, "deleteUser"); // 輸出:執(zhí)行刪除用戶操作(權(quán)限足夠)
checker.executeWithCheck(userService, "queryUser"); // 輸出:執(zhí)行查詢用戶操作(權(quán)限足夠)
}
}
解析流程:
- 定義注解:用
@Target和@Retention指定使用位置和生命周期。 - 使用注解:在目標(biāo)方法上添加注解,設(shè)置屬性值。
- 反射解析:通過
method.isAnnotationPresent()判斷是否有注解,method.getAnnotation()獲取注解對象,進(jìn)而獲取屬性值并執(zhí)行邏輯。
四、三者關(guān)系與總結(jié)
- 泛型:編譯時保障類型安全,避免強制轉(zhuǎn)換,是編寫健壯代碼的基礎(chǔ)。
- 反射:運行時動態(tài)操作類信息,突破編譯時限制,是框架靈活性的核心。
- 注解:通過標(biāo)記攜帶元數(shù)據(jù),配合反射實現(xiàn) “標(biāo)記 - 解析 - 執(zhí)行” 的邏輯,是簡化配置的關(guān)鍵。
三者結(jié)合構(gòu)成了 Java 高級編程的基礎(chǔ) —— 泛型保證類型安全,反射提供動態(tài)能力,注解簡化元數(shù)據(jù)攜帶,共同支撐了 Spring、MyBatis 等主流框架的設(shè)計。
到此這篇關(guān)于javaSE泛型、反射與注解的核心原理與實際應(yīng)用的文章就介紹到這了,更多相關(guān)javaSE泛型、反射與注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springMvc異步的DeferredResult long?polling應(yīng)用示例解析
這篇文章主要為大家介紹了springMvc中DeferredResult的long?polling應(yīng)用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
Java中基于DeferredResult的異步服務(wù)詳解
這篇文章主要介紹了Java中基于DeferredResult的異步服務(wù)詳解,DeferredResult字面意思是"延遲結(jié)果",它允許Spring MVC收到請求后,立即釋放(歸還)容器線程,以便容器可以接收更多的外部請求,提升吞吐量,需要的朋友可以參考下2023-12-12
springboot自定義starter啟動器的具體使用實踐
本文主要介紹了springboot自定義starter啟動器的具體使用實踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09
如何獲取MyBatis Plus執(zhí)行的完整的SQL語句
這篇文章主要介紹了如何獲取MyBatis Plus執(zhí)行的完整的SQL語句問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07

