反射 API 有什麼用?

Java 的反射機制允許開發人員在運行時進行更改並獲取有關類、接口、字段和方法的信息,而無需知道它們的名稱。

反射 API 還允許您創建新對象、調用方法以及獲取或設置字段值。

讓我們列出使用反射可以做的所有事情:

  • 識別/確定對象的類別
  • 獲取有關類修飾符、字段、方法、常量、構造函數和超類的信息
  • 找出哪些方法屬於已實現的接口
  • 創建一個類的實例,其類名在程序執行之前是未知的
  • 按名稱獲取和設置實例字段的值
  • 按名稱調用實例方法

幾乎所有現代 Java 技術都使用反射。它是當今大多數 Java / Java EE 框架和庫的基礎,例如:

  • 用於構建 Web 應用程序的Spring框架
  • JUnit測試框架

如果您以前從未遇到過這些機制,您可能會問為什麼所有這些都是必要的。答案很簡單但也很模糊:反射極大地增加了靈活性以及定制我們的應用程序和代碼的能力。

但總是有利有弊。那麼讓我們提幾個缺點:

  • 違反應用程序安全性。反射讓我們訪問我們不應該訪問的代碼(違反封裝)。
  • 安全限制。反射需要運行安全管理器的系統不可用的運行時權限。
  • 低性能。Java 中的反射通過掃描類路徑以找到要加載的類來動態確定類型。這會降低程序的性能。
  • 維護困難。使用反射的代碼難以閱讀和調試。它不太靈活,也更難維護。

使用反射 API 處理類

所有反射操作都以java.lang.Class對像開始。對於每種類型的對象,都會創建一個不可變的java.lang.Class實例。它提供了獲取對象屬性、創建新對象和調用方法的方法。

讓我們看一下使用java.lang.Class的基本方法列表:

方法 行動
字符串獲取名稱(); 返回類的名稱
int getModifiers(); 返回訪問修飾符
包 getPackage(); 返回有關包的信息
類 getSuperclass(); 返回有關父類的信息
類[] getInterfaces(); 返回一個接口數組
構造函數[] getConstructors(); 返回有關類構造函數的信息
字段[] getFields(); 返回類的字段
字段 getField(String fieldName); 按名稱返回類的特定字段
方法[] getMethods(); 返回方法數組

這些是獲取有關類、接口、字段和方法的數據的最重要方法。還有一些方法可以讓您獲取或設置字段值,以及訪問私有字段。我們稍後再看。

現在我們將討論獲取java.lang.Class本身。我們有三種方法可以做到這一點。

1.使用Class.forName

在正在運行的應用程序中,您必須使用forName(String className)方法來獲取類。

此代碼演示了我們如何使用反射創建類。讓我們創建一個我們可以使用的Person類:

package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

我們示例的第二部分是使用反射的代碼:

public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

如果已知類的全名,則可以使用此方法。然後你可以使用靜態Class.forName()方法獲取相應的類。此方法不能用於基本類型。

2. 使用.class

如果一個類型可用但沒有它的實例,那麼您可以通過將.class添加到類型名稱來獲取該類。這是獲取原始類型類的最簡單方法。

Class aClass = Person.class;

3. 使用.getClass()

如果對象可用,那麼獲取類的最簡單方法是調用object.getClass()

Person person = new Person();
Class aClass = person.getClass();

最後兩種方法有什麼區別?

如果您在編碼時知道您對哪個類對象感興趣,請使用A.class 。如果沒有實例可用,那麼您應該使用.class

獲取類的方法

讓我們看看返回類方法的方法:getDeclaredMethods()getMethods()

getDeclaredMethods()返回一個數組,其中包含Class 對象表示的類或接口的所有已聲明方法的Method對象,包括公共、私有、默認和受保護的方法,但不包括繼承的方法。

getMethods()返回一個數組,其中包含由 Class 對象表示的類或接口的所有公共方法的Method對象——那些由類或接口聲明的,以及那些從超類和超接口繼承的。

讓我們來看看它們各自是如何工作的。

讓我們從getDeclaredMethods()開始。為了再次幫助我們理解這兩種方法之間的區別,下面我們將使用抽象的Numbers類。讓我們編寫一個靜態方法,將我們的Method數組轉換為List<String>

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

這是運行此代碼的結果:

byteValue
shortValue
intValue
longValue
float floatValue;
雙值

這些是在Number類中聲明的方法。getMethods()返回什麼?讓我們更改示例中的兩行:

final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

這樣做,我們將看到以下一組方法:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

因為所有類都繼承Object ,所以我們的方法也返回Object類的公共方法

獲取類的字段

getFields和getDeclaredFields方法用於獲取類的字段。例如,讓我們看一下LocalDateTime類。我們將重寫我們的代碼:

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

作為執行此代碼的結果,我們獲得了包含在 LocalDateTime 類中的字段集。

MIN
MAX
serialVersionUID
日期
時間

類比我們之前對方法的檢查,讓我們看看如果我們稍微更改代碼會發生什麼:

final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

輸出:

最小
最大值

現在讓我們弄清楚這些方法之間的區別。

getDeclaredFields方法返回由此表示的類或接口聲明的所有字段的 Field 對數組班級目的。

getFields方法為類或接口所代表的所有公共字段返回一個Field對像數組班級目的。

現在讓我們看看LocalDateTime 的內部。

班級的最小值最大限度字段是公共的,這意味著它們將通過getFields方法可見。相比之下,日期,時間,serialVersionUID方法具有private修飾符,這意味著它們不會通過getFields方法可見,但我們可以使用getDeclaredFields獲取它們。這就是我們如何訪問私有字段的 Field 對象。

其他方法說明

現在該說說Class類的一些方法了,即:

方法 行動
獲取修改器 為我們的班級獲取修飾符
獲取包裹 獲取包含我們類的包
獲取超類 獲取父類
獲取接口 獲取類實現的接口數組
獲取名稱 獲取完全限定的類名
獲取簡單名稱 獲取類名

getModifiers()

修飾符可以使用班級目的。

修飾符是關鍵字,如publicstaticinterface等。我們使用getModifiers()方法獲取修飾符:

Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

此代碼設置一個值整數位域變量。每個訪問修飾符都可以通過設置或清除相應的位來打開或關閉。我們可以使用java.lang.reflect.Modifier類中的方法檢查修飾符:

import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

回想一下我們的Person的聲明是什麼樣的:

public class Person {}

我們得到以下輸出:

類修飾符:1
是公共的:true
是靜態的:false
是 final 的:false
是抽象的:false
是接口的:false

如果我們使我們的類抽象,那麼我們有:

public abstract class Person {}

這個輸出:

類修飾符:1025
是公共的:true
是靜態的:false
是最終的:false
是抽象的:true
是接口:false

我們更改了訪問修飾符,這意味著我們還更改了通過Modifier類的靜態方法返回的數據

獲取包()

只知道一個類,我們可以得到關於它的包的信息:

Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

獲取超類()

如果我們有一個 Class 對象,那麼我們可以訪問它的父類:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

我們得到眾所周知的Object類:

class java.lang.Object

但是如果我們的類有另一個父類,那麼我們會看到它:

package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

在這裡我們得到我們的父類:

class com.company.Human

獲取接口()

以下是我們如何獲取類實現的接口列表的方法:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

我們不要忘記更改我們的Person類:

public class Person implements Serializable {}

輸出:

[接口 java.io.Serializable]

一個類可以實現很多接口。這就是為什麼我們得到一個數組班級對象。在 Java Reflection API 中,接口也被表示為班級對象。

請注意:該方法只返回指定類實現的接口,不返回其父類。要獲得該類實現的接口的完整列表,您需要引用當前類及其在繼承鏈上游的所有祖先。

getName() & getSimpleName() & getCanonicalName()

讓我們編寫一個涉及原始類、嵌套類、匿名類和String類的示例:

public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

我們程序的結果:

int class (primitive):
getName(): int
getCanonicalName(): int
getSimpleName(): int
getTypeName(): int

String.class (普通類):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (嵌套類):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (匿名內部類):
getName() ): TestReflection$1
getCanonicalName()): 空
getSimpleName()):
getTypeName(): 測試反射 $1

現在讓我們分析程序的輸出:

  • getName()返回實體的名稱。

  • getCanonicalName()返回基類的規範名稱,如 Java 語言規範所定義。如果基類沒有規範名稱(即,如果它是本地類或匿名類或元素類型沒有規範名稱的數組),則返回 null。

  • getSimpleName()返回源代碼中指定的基類的簡單名稱。如果基類是匿名的,則返回一個空字符串。

  • getTypeName()返回此類型名稱的信息字符串。