java註解與反射(非常詳細, 帶有很多樣例)

小程xy發表於2024-07-31

下面是詳細地講解 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.SOURCERetentionPolicy.CLASSRetentionPolicy.RUNTIME
  • @Target:指定註解可以應用的程式元素,取值有 ElementType.TYPEElementType.FIELDElementType.METHODElementType.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. 獲取類的資訊

  1. 獲取 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知識點彙總, 待更

相關文章