Java知識整理之 反射技術

當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載、連接、初始化三步來實現這個類,從而進行初始化。

目錄

 

一、類加載的過程

二、反射概述

三、獲取字節碼文件

四、Class對象的相關方法

4-1 Constructor類

4-1-2 Constructor類中的常用方法

4-2、Method類

4-2-2 Method類中的方法

五、利用反射來證實泛型的擦除

六、類加載器

總結:


一、類加載的過程

1、加載

       就是指將class文件讀入內存,並·爲之創建一個Class對象

       任何類被使用時都會建立一個且僅有一個Class對象

2、連接

       驗證是否有正確的內部結構,並和其他類協調一致

       準備負責爲類的靜態成員分配內存,並設置默認初始化值

       解析將類的二進制數據中的符號引用替換爲直接引用

3、初始化

       就是初始化,如果類中有語句:private static int a = 10,它的執行過程是這樣的,首先字節碼文件被加載到內存後,先進行鏈接的驗證這一步驟,驗證通過後準備階段,給a分配內存,因爲變量a是static的,所以此時a等於int類型的默認初始值0,即a=0,然後到解析,到初始化這一步驟時,才把a的真正的值10賦給a,此時a=10。

字節碼文件:每個類編譯後都是class文件,class文件 包含這個類的所有內容,我們將這種class文件稱爲字節碼文件。

關於java.lang.Class 由於java是面嚮對象語言,任何事物都可以用類來描述,字節碼文件也屬於事物,java中有一個類,專門描述了所有的字節碼文件。這個類就是java.lang.Class。這個類的對象,不需要我們手動創建,由JVM虛擬機,自動創建 當類一加載到內存中,JVM虛擬機就會創建對應得對象。

二、反射概述

反射是框架設計的靈魂,將JAVA代碼的各個組成部分封裝爲其他對象,可以在程序運行過程中操作這些對象,如圖(2-1)。

圖(2-1) 

從圖中可以看出,使用反射,實際上就是操作類對應的字節碼文件,怎麼獲取對應類的字節碼文件呢?

三、獲取字節碼文件

我們先定義一個User對象(3-1)

package com.qq.heronsbill.bean;

/**
 * @Author: ${user}
 */
public class User {
    private  int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id,String username, String password) {
        this.id = id ;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

獲取字節碼文件的常使用方法有三種

獲取Class對象的方式 作用 使用場景
對象.getClass() 通過對象的getClass()方法獲取 多用於對象的hu獲取解碼方式
類名.class 通過類名的屬性獲取 多用戶參數傳遞
Class.forName("全類名") 通過指定路徑的字符串獲取 多用於配置文件,將類名寫在配置文件中,讀取配置文件來加載類

注:因爲一個類只會被加載一次,那麼磁盤中就只有一個對應類的字節碼文件,所以,以上三種方式獲取的字節碼文件爲同一字節碼文件。

四、Class對象的相關方法

String getSimpleName(); 獲得簡單類名,只是類名,沒有包 .

String getName(); 獲取完整類名,包含包名+類名

public class App {
    public static void main(String[] args) throws NoSuchMethodException {
        //獲取User對象的字節碼文件對象
        Class<User> userClass = User.class;

        //方法演示
        String simpleName = userClass.getSimpleName();
        String name = userClass.getName();
        //打印輸出
        System.out.println("getSimpleName()方法獲取類名:"+ simpleName);
        System.out.println("getSimpleName()方法獲取全類名:"+ name);

    }
}

打印結果:

 getSimpleName()方法獲取類名:User
getSimpleName()方法獲取全類名:com.qq.heronsbilll.User

4-1 Constructor類

Constructor是構造方法類,類中的每一個構造方法都獲取一個Constructor對象,通過Constructor對象可以實例化對象

 1、Constructor[] getConstructors()  獲取所有的public修飾的構造方法 返回一個Constructor類型的數組,

2、Constructor getConstructor(Class... parameterTypes)  返回一個Constructor對象,參數類型對應的是原對象構造方法裏面對應參數的類型的字節碼對象,如果沒有參數,則指向空參構造。

 

 

public class AppConstructor {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //獲取User對象的字節碼文件對象
        Class<User> userClass = User.class;

        //方法演示

        Constructor<User>[] constructors = (Constructor<User>[]) userClass.getConstructors();
        Constructor<User> constructor = userClass.getConstructor(int.class, String.class, String.class);

        System.out.println("getConstructors()方法獲取所有的public修飾的構造方法"+Arrays.toString(constructors));
        System.out.println("getConstructor(Class... parameterTypes)方法獲取對應參數類型的構造方法"+constructor);

    }
}

打印結果

getConstructors()方法獲取所有的public修飾的構造方法[public com.qq.heronsbilll.User(), public com.qq.heronsbilll.User(int,java.lang.String,java.lang.String)]
getConstructor(Class... parameterTypes)方法獲取對應參數類型的構造方法public com.qq.heronsbilll.User(int,java.lang.String,java.lang.String)
 

4-1-2 Constructor類中的常用方法

在 獲取對應的Constructor對象後,可以再使用newInstanc(Object... initargs)方法來實例化相關對象

1. T newInstance(Object... initargs)  根據指定參數創建對象。這裏的參數是實例化對象時傳入的參數
2. T newInstance()  空參構造方法創建對象。

注:當一個對象含有空參的public類型的構造方法時,可以直接通過 Class.newInstance()的方法快捷實例化對象,但是這個方法在JDK9以後就過時了。

4-2、Method類

Method是方法類,類中的每一個方法都是Method的對象,通過Method對象可以調用對應方法。和Constructor類相同,Method類有兩個方法,一個是獲取全部方法的getMethods()方法,另一個是獲取指定方法的getMethod("方法名", 方法的參數類型... 類型)方法

1. Method[] getMethods()  獲取所有的public修飾的成員方法,包括父類中的方法。

2. Method getMethod("方法名", 方法的參數類型... 類型)   根據方法名和參數類型獲得一個方法對象,只能是獲取public修飾的方法

public class AppMethod {
    public static void main(String[] args) throws NoSuchMethodException {
        //獲取User對象的字節碼文件對象
        Class<User> userClass = User.class;

        //方法演示
        Method[] methods = userClass.getMethods();
        Method setId = userClass.getMethod("setId", int.class);


        System.out.println("getMethods()方法獲取User類中的所有方法,包括父類");
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("getMethod()方法獲取參數爲int類型的setId方法:"+setId);
    }
}

打印結果

getMethods()方法獲取User類中的所有方法,包括父類
public java.lang.String com.qq.heronsbilll.User.toString()
public int com.qq.heronsbilll.User.getId()
public java.lang.String com.qq.heronsbilll.User.getPassword()
public void com.qq.heronsbilll.User.setId(int)
public void com.qq.heronsbilll.User.setPassword(java.lang.String)
public java.lang.String com.qq.heronsbilll.User.getUsername()
public void com.qq.heronsbilll.User.setUsername(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-----------------------------------------------------
getMethod()方法獲取參數爲int類型的setId方法:public void com.qq.heronsbilll.User.setId(int)

4-2-2 Method類中的方法

獲取對應大方法對象之後,可以使用Method類中的invoke(Object obj, Object... args)方法,調用相關方法

Object invoke(Object obj, Object... args)  根據參數args調用對象obj的該成員方法,如果obj=null,則表示該方法是靜態方法

        Method getId = userClass.getMethod("getId");
        User obj = userClass.newInstance();
        setId.invoke(obj,1);
        System.out.println("打印obj對象:"+obj);
        Object invoke = getId.invoke(obj);
        System.out.println("獲取obj對象的id值:"+invoke);

 打印結果:

打印obj對象:User{id=1, username='null', password='null'}
獲取obj對象的id值:1

 

 

五、利用反射來證實泛型的擦除

java中的泛型是僞泛型,java爲了提高效率,設計泛型僅在編譯時有效,而編譯後的class文件中是不存在泛型的,如果通過反射技術獲取到對應類的字節碼文件,將不會受到泛型的限制,下面進行代碼演示。

public class GenericityApp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //創建Integer類型的ArrayList集合
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        //不能添加int以外的數據
        //list.add("aaa");


        //獲取ArrayList的字節碼文件對象
        Class listClass = list.getClass();
        //獲取所有方法,並找到add
        Method[] methods = listClass.getMethods();
        for (Method method : methods) {

            System.out.println(method);
        }
        System.out.println("-----------------------------------------------------");

        //兩個add方法參數都是Object類型的(其中一個方法需要加int類型的參數)
        // public void java.util.ArrayList.add(int,java.lang.Object)
        //public boolean java.util.ArrayList.add(java.lang.Object)

        //反射出ArrayList的add方法
        Method add = listClass.getMethod("add", Object.class);

        add.invoke(list,"字符串類型");
        add.invoke(list,true);
        add.invoke(list,'a');
        add.invoke(list,new User(1,"xiaohua","1111"));

        System.out.println("list集合:"+list);


    }
}

打印結果:

public boolean java.util.ArrayList.add(java.lang.Object)
public void java.util.ArrayList.add(int,java.lang.Object)
public boolean java.util.ArrayList.remove(java.lang.Object)
public java.lang.Object java.util.ArrayList.remove(int)
public java.lang.Object java.util.ArrayList.get(int)
public java.lang.Object java.util.ArrayList.clone()
public int java.util.ArrayList.indexOf(java.lang.Object)
public void java.util.ArrayList.clear()
public boolean java.util.ArrayList.isEmpty()
public int java.util.ArrayList.lastIndexOf(java.lang.Object)
public boolean java.util.ArrayList.contains(java.lang.Object)
public void java.util.ArrayList.replaceAll(java.util.function.UnaryOperator)
public int java.util.ArrayList.size()
public java.util.List java.util.ArrayList.subList(int,int)
public java.lang.Object[] java.util.ArrayList.toArray()
public java.lang.Object[] java.util.ArrayList.toArray(java.lang.Object[])
public java.util.Iterator java.util.ArrayList.iterator()
public java.util.Spliterator java.util.ArrayList.spliterator()
public boolean java.util.ArrayList.addAll(int,java.util.Collection)
public boolean java.util.ArrayList.addAll(java.util.Collection)
public java.lang.Object java.util.ArrayList.set(int,java.lang.Object)
public void java.util.ArrayList.forEach(java.util.function.Consumer)
public void java.util.ArrayList.ensureCapacity(int)
public void java.util.ArrayList.trimToSize()
public boolean java.util.ArrayList.retainAll(java.util.Collection)
public boolean java.util.ArrayList.removeAll(java.util.Collection)
public boolean java.util.ArrayList.removeIf(java.util.function.Predicate)
public void java.util.ArrayList.sort(java.util.Comparator)
public java.util.ListIterator java.util.ArrayList.listIterator(int)
public java.util.ListIterator java.util.ArrayList.listIterator()
public boolean java.util.AbstractList.equals(java.lang.Object)
public int java.util.AbstractList.hashCode()
public java.lang.String java.util.AbstractCollection.toString()
public boolean java.util.AbstractCollection.containsAll(java.util.Collection)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public default java.util.stream.Stream java.util.Collection.stream()
public default java.util.stream.Stream java.util.Collection.parallelStream()
-----------------------------------------------------
list集合:[1, 字符串類型, true, a, User{id=1, username='xiaohua', password='1111'}]

結果中可以看到,加Integer泛型約束的list集合再編譯後對數據的添加不再限制,無論是字符串類型,Boolean類型,還是對象,都可以添加。

六、類加載器

類加載器是負責加載類的對象。將class文件從硬盤加載到內存中生成Class對象

類加載器的組成

BootstrapClassLoader  根類加載器 也被稱作引導類加載器,負責Java核心類的加載 比如System,String等

ExtClassLoalde  擴展類加載器 JDK中JRE的lib目錄下ext目錄

AppClassLoader 系統類加載器 負責在JVM啓動時加載來自java命令的class文件,以及classpath環境變量所指定的jar包和類路徑

 

注意:

  1. 所有的類加載器都是java.lang.ClassLoader的子類
  2. 使用 類.class.getClassLoader()可以獲得加載自己的類加載器
  3. 類加載器加載機制:全盤負責委託機制
    1. 全盤負責:A類如果要使用B類(不存在),A類加載器必須負責加載B類
    2. 委託機制:A類加載器如果要加載資源B,必須詢問父類加載器是否加載、如果加載,將直接使用,如果沒有加載機制,再自己加載
    3. 採用全盤負責委託機制保證一個class文件只會被加載一次,形成一個Class對象

 

總結:

反射是框架設計的靈魂,理解了反射技術,對學習框架有很大的幫助。同時反射技術又可以很好的解耦合,對於優化代碼有很大的幫助。

能力尚淺,有待進步,如有不足,不吝賜教!

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