下面是詳細地講解 Java 中的註解與反射,並提供了很多的示例來幫助理解。
Java 註解(Annotations)
1. 註解的基本概念
註解(Annotation)是 Java 5 引入的一種用於為程式碼元素(類、方法、欄位、引數等)新增後設資料的機制。這些後設資料可以在編譯時、類載入時或執行時被讀取並使用。註解不會直接影響程式碼的執行,但可以透過工具或框架來處理這些後設資料,以實現一些功能。
2. 內建註解
Java 提供了一些常用的內建註解:
@Override
:用於標註方法,表明該方法重寫了父類的方法。@Deprecated
:用於標註過時的元素,編譯器看到使用了這個元素會發出警告。@SuppressWarnings
:用於抑制編譯器警告。
public class AnnotationExample {
@Override
public String toString() {
return "AnnotationExample";
}
@Deprecated
public void deprecatedMethod() {
// 這個方法已經過時
}
@SuppressWarnings("unchecked")
public void suppressWarningsExample() {
List rawList = new ArrayList();
rawList.add("example");
}
}
3. 自定義註解
你可以定義自己的註解,並透過元註解(meta-annotation)來指定註解的行為。
元註解:
@Retention
:指定註解的保留策略,取值有RetentionPolicy.SOURCE
、RetentionPolicy.CLASS
、RetentionPolicy.RUNTIME
。@Target
:指定註解可以應用的程式元素,取值有ElementType.TYPE
、ElementType.FIELD
、ElementType.METHOD
、ElementType.PARAMETER
等。@Documented
:指定註解是否包含在 Javadoc 中。@Inherited
:指定註解是否可以被子類繼承。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // 執行時保留
@Target(ElementType.METHOD) // 作用於方法
public @interface MyAnnotation {
String value();
}
public class MyClass {
// 當 MyClass 的 myMethod 方法被註釋了這個註解後,可以透過反射在執行時獲取這個註解及其值。下面的"結合註解與反射"有例子
@MyAnnotation(value = "example")
public void myMethod() {
System.out.println("Method with MyAnnotation");
}
}
Java 反射(Reflection)
1. 反射的基本概念
反射是一種執行時機制,允許程式在執行時檢查和操作類、方法、欄位等。透過反射,你可以:
- 獲取類的詳細資訊(類名、修飾符、父類、介面等)。
- 獲取類的方法、建構函式、欄位等。
- 動態呼叫方法或建構函式。
- 動態訪問和修改欄位的值。
2. 獲取類的資訊
- 獲取 Class 物件
有多種方法可以獲取一個類的 Class 物件:
Class.forName(String className):
透過類的完全限定名獲取 Class 物件。
ClassName.class:
透過類的字面常量獲取 Class 物件。
object.getClass():
透過物件例項獲取 Class 物件。
// 獲取 Class 物件的三種方式
Class<?> clazz1 = Class.forName("java.util.ArrayList");
Class<?> clazz2 = ArrayList.class;
ArrayList<String> list = new ArrayList<>();
Class<?> clazz3 = list.getClass();
3. 獲取類的成員
getDeclaredFields():
獲取類的所有欄位(包括私有欄位)。
getDeclaredMethods():
獲取類的所有方法(包括私有方法)。
getDeclaredConstructors():
獲取類的所有建構函式。
getField(String name):
獲取類的指定欄位(不包括私有欄位)。
getMethod(String name, Class<?>... parameterTypes):
獲取類的指定方法(不包括私有方法)。
getConstructor(Class<?>... parameterTypes):
獲取類的指定建構函式。
Class<?> clazz = Class.forName("java.util.ArrayList");
// 獲取所有宣告的欄位
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("欄位: " + field.getName());
}
// 獲取所有宣告的方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法: " + method.getName());
}
// 獲取所有宣告的建構函式
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("建構函式: " + constructor.getName());
}
4. 動態建立物件
newInstance():
使用無參建構函式建立物件。
Constructor.newInstance(Object... initargs):
使用指定建構函式建立物件。
Class<?> clazz = Class.forName("java.util.ArrayList");
// 使用無參建構函式建立物件
Object obj1 = clazz.newInstance();
// 使用帶引數的建構函式建立物件
Constructor<?> constructor = clazz.getConstructor(Collection.class);
Collection<String> collection = Arrays.asList("A", "B", "C");
Object obj2 = constructor.newInstance(collection);
System.out.println(obj1);
System.out.println(obj2);
5. 動態呼叫方法
Method.invoke(Object obj, Object... args):
呼叫指定物件的該方法。
Class<?> clazz = Class.forName("java.util.ArrayList");
Method method = clazz.getMethod("add", Object.class);
ArrayList<String> list = new ArrayList<>();
method.invoke(list, "Hello");
System.out.println(list); // 輸出: [Hello]
6. 動態訪問和修改欄位
Field.get(Object obj):
獲取指定物件中此欄位的值。
Field.set(Object obj, Object value):
設定指定物件中此欄位的值。
class MyClass {
private String field = "Initial Value";
}
Class<?> clazz = Class.forName("MyClass");
Field field = clazz.getDeclaredField("field");
field.setAccessible(true); // 如果欄位是私有的,需要設定可訪問
MyClass obj = new MyClass();
System.out.println("原始欄位值: " + field.get(obj)); // 獲取欄位值
field.set(obj, "New Value"); // 設定欄位值
System.out.println("修改後的欄位值: " + field.get(obj)); // 獲取欄位值
結合註解與反射
註解與反射的結合非常常見,尤其在框架中,例如 Spring 和 Hibernate。透過反射機制,你可以在執行時讀取註解資訊,並根據這些資訊執行特定的操作。
示例:簡單的依賴注入
以下示例展示瞭如何透過註解和反射實現簡單的依賴注入:
import java.lang.annotation.*;
import java.lang.reflect.*;
// 定義註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Inject {
}
// 服務類
class Service {
public void serve() {
System.out.println("Service is serving");
}
}
// 客戶端類
class Client {
@Inject
private Service service;
public void doSomething() {
service.serve();
}
}
// 注入依賴的工具類
public class DependencyInjector {
public static void main(String[] args) throws Exception {
Client client = new Client();
injectDependencies(client);
client.doSomething();
}
public static void injectDependencies(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
for (Field field : clazz.getDeclaredFields()) { // 遍歷client的所有欄位(變數).
if (field.isAnnotationPresent(Inject.class)) { // 獲取帶有Inject註解的變數, 把它注入到 Client 中
field.setAccessible(true);
Object dependency = field.getType().getConstructor().newInstance();
field.set(obj, dependency); // 透過field的set方法將service例項注入到client中
}
}
}
}
在這個示例中:
@Inject
註解用於標註需要注入的欄位。DependencyInjector
類透過反射獲取Client
類中帶有@Inject
註解的欄位,並動態例項化Service
類的物件,注入到Client
類的例項中。Client
類呼叫doSomething
方法時,Service
類的例項已經被注入並可以使用。
總結
註解和反射是 Java 中非常強大和靈活的機制,透過它們可以實現許多高階功能,例如依賴注入、AOP、動態代理等。在實際開發中,理解和熟練運用這些技術,可以幫助你編寫出更加靈活、可擴充套件的程式碼。
文章到這裡就這束了!~
其他文章地址:
快速入門,springboot知識點彙總
springboot常用註解大全(超詳細, 30個)
springboot websocket知識點彙總
spring cloud知識點彙總, 待更