Try using it in your preferred language.

English

  • English
  • 汉语
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar
translation

这是AI翻译的帖子。

제이온

[Java] 反射概念及使用方法

  • 写作语言: 韓国語
  • 基准国家: 所有国家 country-flag

选择语言

  • 汉语
  • English
  • Español
  • Bahasa Indonesia
  • Português
  • Русский
  • 日本語
  • 한국어
  • Deutsch
  • Français
  • Italiano
  • Türkçe
  • Tiếng Việt
  • ไทย
  • Polski
  • Nederlands
  • हिन्दी
  • Magyar

durumis AI 总结的文章

  • 反射是在執行時存取類別資訊並操作類別的 API。
  • 透過反射,可以建立類別實例,並不受存取修飾詞限制地存取欄位和方法,尤其在大型開發階段中,框架需要動態管理依賴關係時,它非常有用。
  • 然而,它可能會破壞封裝性並降低效能,因此最好只在必要時使用。

反射是什么?

反射是通过加载到堆内存中的Class类型对象,支持创建所需类的实例,并支持访问实例的字段和方法,无论访问控制符如何。



这里提到的加载类是指,JVM的类加载器完成对类文件加载后,创建一个包含该类信息的Class类型对象并将其存储在内存的堆中。请注意,这与使用new关键字创建的对象不同。如果对Class类型对象不了解,建议参考java.lang.class对象的JDK文档。


使用方法

在使用反射之前,需要获取加载到堆内存中的Class类型对象。共有三种方法。


  • 通过类.class获取
  • 通过实例.getClass()获取
  • 通过Class.forName("类名")获取


public class Member {

    private String name;

    protected int age;

    public String hobby;

    public Member() {
    }

    public Member(String name, int age, String hobby) {
        this.name = name;
        this.age = age;
        this.hobby = hobby;
    }

    public void speak(String message) {
        System.out.println(message);
    }

    private void secret() {
        System.out.println("密码是1234。");
    }

    @Override
    public String toString() {
        return "Member{" +
            "name='" + name + '\'' +
            ", age=" + age +
            ", hobby='" + hobby + '\'' +
            '}';
    }
}

public class Main {

    public static void main(String[] args) throws ClassNotFoundException {
        Class memberClass = Member.class;
        System.out.println(System.identityHashCode(memberClass));

        Member member = new Member("Jayon", 23, "Da Rats 开发");
        Class memberClass2 = member.getClass();
        System.out.println(System.identityHashCode(memberClass2));

        Class memberClass3 = Class.forName("{包名}.Member");
        System.out.println(System.identityHashCode(memberClass3));
    }
}

// 执行结果
1740000325
1740000325

可以确认这三种方式获取的Class类型实例都是相同的。无论使用哪种方式,哈希值都相同,因此可以根据情况选择合适的方式使用。


现在,我们可以通过获取的Class类型来创建该类的实例,并访问实例的字段和方法,不受访问控制符的限制。首先,尝试创建该类的实例。


public class Main {

    public static void main(String[] args) throws Exception {
        // 打印Member的所有构造函数
        Member member = new Member();
        Class memberClass = member.getClass();
        Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);

        // 使用Member的默认构造函数创建实例
        Constructor constructor = memberClass.getConstructor();
        Member member2 = constructor.newInstance();
        System.out.println("member2 = " + member2);

        // 使用Member的其他构造函数创建实例
        Constructor fullConstructor =
            memberClass.getConstructor(String.class, int.class, String.class);
        Member member3 = fullConstructor.newInstance("Jayon", 23, "Da Rats 开发");
        System.out.println("member3 = " + member3);
    }
}

// 执行结果
public Member()
public Member(java.lang.String,int,java.lang.String)
member2 = Member{name='null', age=0, hobby='null'}

通过getConstructor()获取构造函数,并使用newInstance()动态创建Member实例。

最后,让我们尝试访问实例的字段和方法,无论访问控制符如何。

public class Main {

    public static void main(String[] args) throws Exception {
        Member member = new Member("Jayon", 23, "Da Rats 开发");
        Class memberClass = member.getClass();

        // 访问字段
        Field[] fields = memberClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            System.out.println(field.get(member));
        }
        fields[0].set(member, "Jayon2");
        System.out.println(member);

        // 访问方法
        Method speakMethod = memberClass.getDeclaredMethod("speak", String.class);
        speakMethod.invoke(member, "反射测试");

        Method secretMethod = memberClass.getDeclaredMethod("secret");
        secretMethod.setAccessible(true);
        secretMethod.invoke(member);
    }
}

// 执行结果
Jayon
23
Da Rats 开发
Member{name='Jayon2', age=23, hobby='Da Rats 开发'}
反射测试

通过getDeclaredFileds()获取类实例变量,通过get()获取字段值,通过set()修改字段值。需要注意的是,访问private访问控制符的字段时,需要将setAccessible()的参数设置为true。


同样地,可以使用getDeclaredMethod()获取方法。此时需要将方法名和参数类型作为参数传递。同样地,访问private访问控制符的方法时,需要将setAccessible()的参数设置为true。最后,使用invoke()方法调用通过反射API获取的方法。


优缺点

  • 优点
    • 在运行时,可以创建类的实例,访问字段和方法,不受访问控制符的限制,从而提供灵活性。
  • 缺点
    • 破坏封装性。
    • 在运行时创建实例,无法在编译时进行类型检查。
    • 在运行时创建实例,难以了解具体的运行流程。
    • 与直接访问字段和方法相比,使用反射访问的性能较低(并非所有情况下性能都较低)。


使用原因

通过反射API,可以在运行时访问类信息,并根据需要操作类。即使是使用private访问控制符声明的字段和方法,也可以进行操作。这似乎是一种违反面向对象设计中封装性原则的技术,不应该使用。


在小型控制台程序中,开发人员可以在编译时了解程序中使用的所有对象及其依赖关系。但是,在大型框架开发中,很难了解大量对象及其依赖关系。此时,可以使用反射动态创建类,并建立依赖关系。


例如,在Spring的Bean工厂中,只需添加@Controller、@Service、@Repository等注解,Bean工厂就能识别出带有这些注解的类,并创建和管理这些类。虽然开发人员没有显式地将这些类告知Bean工厂,但这一切都得益于反射。在运行时,会搜索并识别带有这些注解的类,然后使用反射创建这些类的实例,注入所需的字段,并将其存储在Bean工厂中。


当然,如上所述,反射会破坏封装性,因此建议仅在必要时使用。


来源

제이온
제이온
제이온
제이온
[Effective Java] 項目 1. 考慮使用靜態工廠方法而非建構函式 靜態工廠方法是一種比建構函式更靈活且有效率的建立實例的方法。它們可以具有名稱,並可返回符合特定條件的實例,並可透過快取來提高效能。與單例模式不同,靜態工廠方法可以建立和返回多種類型的實例,並且可以保持彈性,而無需使用反射。

2024年4月27日

[Effective Java] 項目 6. 避免不必要的物件建立 這是一份關於在 Java 中減少不必要物件建立的指南。對於 String、Boolean 等不變物件,最好使用字面值;對於正規表示式,最好快取 Pattern 物件。此外,自動裝箱會導致效能下降,因此最好使用基本類型。有關更多資訊,請參閱「Effective Java」。

2024年4月28日

[有效 Java] 項目 4. 要阻止實例化,請使用私有建構函數 僅包含靜態方法和欄位的工具類,最好將建構函數的存取修飾符設定為 private,以阻止實例化。 這可以防止使用者誤認為建構函數是自動生成的,並使繼承變得不可能,從而明確表示類別的意圖。

2024年4月28日

[Javascript] 物件的結構 (V8) JavaScript 的 Object 在 V8 引擎中根據狀態可以被優化為類似結構體的 Fast 模式或以雜湊表運作的 Dictionary 模式。Fast 模式是針對幾乎固定形式的鍵和值進行優化,速度很快,但當新增新鍵或刪除元素等操作時, 會轉換為 Dictionary 模式,速度會變慢。
곽경직
곽경직
곽경직
곽경직
곽경직

2024年3月18日

[非计算机专业,如何成为一名开发者] 14. 新手开发者常问的技术面试内容总结 本指南旨在为新手开发者提供技术面试准备指导。涵盖了面试中常见的概念,例如主内存区域、数据结构、关系型数据库 (RDBMS) 和 NoSQL、过程式编程和面向对象编程、重写和重载、页面替换算法、进程和线程、OSI 七层模型、TCP 和 UDP 等。
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

2024年4月3日

概念性數據模型 概念性數據模型是將實體分離並使用 ERD 表示實體間關係的過程。實體是獨立的資訊單位,屬性是 實體擁有的數據。識別碼用於唯一識別實體,關係表示實體間的交互作用。基數性表示實體間的數量關係,可選性表示數據的必需性。
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그

2024年4月8日

用開源打造 AI 全棧 AI 生態系統中,新的開源 LLM(大型語言模型)模型不斷湧現。Mistral、Llama、phi-2 等具有強大性能和開放許可證的模型已公開, 為使用這些模型開發的各種工具也正在開發中。LangChain、LlamaIndex、Haystack 等 LLM 框架,Ollama、vLLM、KServe 等推論和 服務工具,LiteLLM、One API 等 LLM 代理解決方案,都讓 AI 開發
RevFactory
RevFactory
RevFactory
RevFactory

2024年2月5日

邏輯數據模型 邏輯數據模型是將概念數據模型轉換為關係數據庫範式的過程,根據映射規則,將 1:1、1:N、N:M 關係 轉換為關係數據庫中的表格,並通過正規化來確保數據完整性。通過 1NF、2NF、3NF 的正規化過程,對表格進行 優化,消除部分依賴和傳遞依賴。
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그
제이의 블로그

2024年4月9日

用 Rust 構建精緻的 CLI 本文將介紹如何使用 Rust 構建 CLI 應用程式。我們將使用 clap 和 ratatui 庫來建立具有登入和登出功能的 CLI 程式。本文將介紹 Rust 驅動的 CLI 開發的方方面面,包括定義命令選項、新增子命令和實現互動式 TUI 介面。本文以不超過 160 個字的完整句子撰寫, 概述了程式的主要功能和實現方式。
곽경직
곽경직
곽경직
곽경직
곽경직

2024年3月13日