Java基礎 -------反射機制

經常聽到java 反射,自己也看過一些文章和視頻,但總是感覺沒有很好地理解,所以結合了自己覺得比較好的文章(主要是我能理解的),自己又總結了一下,作爲記錄。

前言

在這裏插入圖片描述

    我們都知道,JVM加載的是.class文件。其實,類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。
    同時,JVM規範允許類加載器在預料某個類將要被使用時就預先加載它,如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器會在程序首次主動使用該類時會生成錯誤報告(LinkageError錯誤),如果這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤。

舉例Object o=new Object();

例如要運行一段代碼 Object o=new Object();
其運行過程:

首先JVM會啓動,你的代碼會編譯成一個.class文件,然後被類加載器加載進jvm的內存中,你的類Object加載到方法區中,創建了Object類的class對象到堆中,注意這個不是new出來的對象,而是類的類型對象,每個類只有一個class對象,作爲方法區類的數據結構的接口。jvm創建對象前,會先檢查類是否加載,尋找類對應的class對象,若加載好,則爲你的對象分配內存,初始化也就是代碼:new Object()。
上面的流程就是你自己寫好的代碼扔給jvm去跑,跑完就over了,jvm關閉,你的程序也停止了。

理解了上面的過程,再理解反射就比較容易了。

一、反射的概述

    Java的反射(reflection)機制是指在程序的運行狀態中,可以構造任意一個類的對象,可以瞭解任意一個對象所屬的類,可以瞭解任意一個類的成員變量和方法,可以調用任意一個對象的屬性和方法。這種動態獲取程序信息以及動態調用對象的功能稱爲Java語言的反射機制。反射被視爲動態語言的關鍵。

反射提供的主要功能:

  1. 在運行時判斷任意一個對象所屬的類;
  2. 在運行時構造任意一個類的對象;
  3. 在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法);
  4. 在運行時調用任意一個對象的方法;
  5. 生成動態代理.(下篇再講)
    重點:是運行時而不是編譯時

二、獲取class文件對象的方式

反射是對一個類進行解剖,要想解剖一個類,必須先要獲取到該類的字節碼文件對象。
而解剖使用的就是Class類中的方法,所以先要獲取到每一個字節碼文件對應的Class類型的對象。
再強調一下, 咱們寫的代碼是存儲在後綴名.java文件中的,它會被編譯成.class文件,jvm真正執行的是.class文件。Java是面向對象的語言,一切皆爲對象,所以java認爲編譯後的.class文件也是對象,給抽象成了一個類,這個類就是Class。在這裏插入圖片描述
通過文檔瞭解到,使用forName(String className)方法就可以得到想要反射的類。
在這裏插入圖片描述
獲取class文件對象的有三種方式,具體如下:
(1)使用Class類的forName()靜態方法

 Class perClass = Class.forName("com.diligentkong.bean.Person");
System.out.println(perClass);
輸出:class com.diligentkong.bean.Person

(2)直接獲取某一個對象的class

Person person = new Person();      // new一個Person對象
Class perClass = person.getClass();// 獲取Class對象
System.out.println(perClass);
//結果輸出:class com.diligentkong.bean.Person

(3)調用某個對象的getClass()方法


Class perClass = Person.class;
System.out.println(perClass);
輸出:class com.diligentkong.bean.Person

三、反射的基本運用

1.獲取構造器信息

通過Class類的getConstructor方法得到Constructor類的一個實例,而Constructor類有一個newInstance方法可以創建一個對象實例:

Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數

可以根據傳入的參數來調用對應的構造函數。

2.通過反射來生成對象

1 .使用Class對象的newInstance()方法來創建Class對象對應類的實例。

Class perClass = Class.forName("com.diligentkong.bean.Person");
//調用 Person 的無參數構造方法,創建此Class對象所表示的類的一個新實例
Person person = (Person) perClass.newInstance(); 
System.out.println(person);
輸出:Person{name='null', age=0}

2.先通過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建實例。這種方法可以用指定的構造器構造類的實例。

Class perClass = Class.forName("com.diligentkong.bean.Person");
Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數
Person p = (Person) constructor.newInstance("kong",22);
System.out.println(p); //Person{name='kong', age=22}

3.獲取方法

獲取某個Class對象的方法集合,主要有以下幾個方法:
在這裏插入圖片描述
在這裏插入圖片描述

1.獲取所有的:

  •  public Method[] getMethods():獲取所有"公有(public)方法";(包含了父類的方法也包含Object類)
    
  •  public Method[] getDeclaredMethods():方法返回類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
    

2.獲取單個的: 方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應Class的對象。

  •  public Method getMethod(String name,Class<?>... parameterTypes):
    
  • public Method getDeclaredMethod(String name,Class<?>… parameterTypes)
          參數:
               name : 方法名;
                Class … : 形參的Class類型對象

調用方法:
public Object invoke(Object obj,Object… args):
      參數說明:
           obj : 要調用方法的對象;
           args:調用方式時所傳遞的實參;
實例代碼:

public class Person {

    private String name;
    private int age;
    public void  run(){
        System.out.println("I can  run ....");
    }
    protected void eat(int num){
        System.out.println("我今天吃了"+num+"頓大餐,哈哈哈!");
    }

     public Person() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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



}

  public static void main(String[] args) throws Exception {

            //獲取Class對象
           Class perClass = Class.forName("com.diligentkong.bean.Person");

            Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數
            Person p = (Person) constructor.newInstance("kong",22);
            System.out.println(p); //Person{name='kong', age=22}

            // 獲取所有公有方法
            System.out.println("------------------獲取所有“公有”方法-------------");
            Method[] methodArray = perClass.getMethods();
            for (Method m: methodArray){
                System.out.println(m);

            }


            System.out.println("----------------獲取所有的方法,包括私有的---------");
            Method[] declaredMethods = perClass.getDeclaredMethods();
            for (Method m : declaredMethods){
                m.setAccessible(true); //解除私有限定
                System.out.println(m);
            }

            System.out.println("----------------獲取公有的run()方法-------------------");
            Method m = perClass.getMethod("run");
            m.invoke(p);
            System.out.println(m);


            System.out.println("-------------------獲取私有的eat()方法----------------------------");
            Method promethod = perClass.getDeclaredMethod("eat",int.class);
            promethod.setAccessible(true);
            promethod.invoke(p,3);

        }

結果:
在這裏插入圖片描述

4.獲取類的成員變量信息

在這裏插入圖片描述
getFiled:訪問公有的成員變量
getDeclaredField:所有已聲明的成員變量,但不能得到其父類的成員變量
示例代碼:

在Person中加入如下字段:

public int i;
public int j;

 Class perClass = Class.forName("com.diligentkong.bean.Person");

            Constructor constructor = perClass.getConstructor(String.class,int.class);//獲取有參構造函數

            Field field =  perClass.getDeclaredField("age");


            System.out.println("-------------訪問所有的屬性,包括public屬性-------");
            Field[] fields = perClass.getDeclaredFields();
            for (Field f: fields){
                System.out.println(f.getName());
            }

            System.out.println("--------------獲取public屬性--------------");
            Field[] privateFields = perClass.getFields();
            for (Field f: privateFields){
                System.out.println(f.getName());
            }

            System.out.println("--------------獲取獲取特定的屬性:age--------------");
            Field ff = perClass.getDeclaredField("age");
            System.out.println(ff);

            System.out.println("----------------獲取特定的屬性: i------------");
            Field ff2= perClass.getField("i");
            System.out.println(ff2);
            System.out.println("---------------------");

//實例化這個類,賦值給p1
            Person p1 = (Person) perClass.newInstance();
            //解除私有限定
            field.setAccessible(true);
            // 給p1對象的age屬性賦值20
            field.set(p1,20);
            //獲取屬性age
            System.out.println(""+field.get(p1));
            
        }

結果:
在這裏插入圖片描述

5.通過反射越過泛型檢查

例如:ArrayList的一個對象,在這個集合中添加一個字符串數據,

// 泛型只在編譯期有效,在運行期會被擦出掉
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
Class clazz = Class.forName("java.util.ArrayList");
Method m = clazz.getMethod("add",Object.class);
m.invoke(list,"aaa");
System.out.println(list); //[100, 200, aaa]

好了,暫時總結這些,有新的認識再去補充。

參考的博文:
https://www.zhihu.com/question/24304289
https://www.sczyh30.com/posts/Java/java-reflection-1/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章