Java 反射 原

從學習Java開始,就入手面向對象、Java反射等等,也懵逼在面向對象的世界中,反問道我的對象是誰,不好意思,可能還沒有創建!

反射是Java的一個特點,也是使原本爲靜態語言的Java,多了那麼一些靈活性,在理解各個框架源碼以及組件內容的時候是一個不錯的知識點,比如註解,這是一個非常常見,又很好使的玩意,之前也有簡單的學習---Java 註解 基礎Java 註解 實踐 (最好先學習Java反射再去玩註解,會很有幫助)

 

從主要以下幾點開始學習

  • Class類的使用
  • 方法的反射
  • 成員變量的反射
  • 構造器的反射
  • Java類加載機制

 

Class類 和 面向對象

在面向對象的環境中,萬事萬物皆對象,但也總有例外,Java中有兩個不屬於對象,一個是普通數據類型,一個是靜態的成員

普通數據類型有封裝類的彌補,靜態的屬於類,那麼類是不是對象呢,類是對象,是java.lang.Class類的實例對象,看文字還比較容易理解,中文說出來就比較繞口, 英文: there is a class named Class

 

一個普通的類的實例對象表示

public class Coo {
    //Doo的實例對象 以doo表示
    Doo doo=new Doo();
}


class Doo{}

那麼一個Class類的實例對象,有三種表示方式,但不能是new Class,因爲下面源碼中也解釋爲什麼

/*
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader) {
    // Initialize final field for classLoader.  The initialization value of non-null
    // prevents future JIT optimizations from assuming this final field is null.
    classLoader = loader;
}

上面是Class類中的一個構造器,是私有的,而且註釋說只有JVM創建Class對象,在以前的Java版本中你可能會看到一個無參的構造器,沒關係,那你的構造器也絕對是私有,不能直接創建,在上面中出現了一個JIT編譯的關鍵詞,有興趣的小夥伴可以研究擴展

 

任何類都是Class的實例對象,下面三種方式:

public class Coo {

    //Doo的實例對象 以doo表示
    Doo doo=new Doo();

    //①可以看出Doo類有一個隱含的靜態成員變量class
    Class first=Doo.class;

    //②已知類的對象,通過getClass獲取
    Class second=doo.getClass();

    //重理解一次:doo代表Doo類的實例對象,first、second代表的是Class的實例對象
    //這個Class的實例對象又證明說Doo這個類本身是一個實例對象的存在
    //一本正經的胡說八道,那麼給一個官方給出的說法是這樣的 :first、second表示了Doo類的類 類型(class type)
    //所有東西都是對象,類也是對象,是Class的實例對象,這個對象稱爲該類的類類型
    //就可以分析到,Doo的對象是doo,Doo的類類型是Class的對象first、second
    //不管哪種表達方式表示Doo的類類型,一個類只可能是Class類的一個實例對象,所以first == second

    //③需要異常處理,參數爲類的全稱"com.cloud.eureka.Doo"
    Class third=null;

    {
        try {
            third = Class.forName("com.cloud.eureka.Doo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //依然存在 first == second == third
    //由此可見,我們可以通過類的類類型創建該類的對象,通過first、second、third創建
    //需要異常處理,是誰的類的類類型對象,創建的對象就是誰,需要強轉
    //newInstance前提需要無參構造方法
    {
        try {
            Doo dooFirst= (Doo) first.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}


class Doo{}

 

Java 動態加載類信息

三種表示Class的實例對象中,第三種具有很好的動態加載類③

  • 可以表示類的類類型,還可以動態加載類
  • 區分編譯、運行
  • 編譯時加載類屬於靜態加載類
  • 運行時加載類屬於動態加載類

很多時候,大家都是通過工具(IDEA、eclipse等)進行辦公或者學習,編譯和運行都是由工具來輔助完成的,那麼我們需要知道編譯、運行的區別

1.2..3...好,我們得到了編譯、運行知識的技能

 

只要是在類裏面用到的,都隱含class,對應的類的類類型,如下: 

public class Coo {

    Class c0=int.class;
    Class c1=String.class;
    Class c2=Double.class;
    Class c3=void.class;
    
    // package不是在類裏面的,error
    // Class c4=package.class;
}

 

在Doo類中,寫個方法

class Doo{
    public static void staticVoidMethod(Object o){
        //傳遞的是什麼類型,就是什麼類型
        Class co=o.getClass();
    }
}

o傳遞的是什麼對象,co就是該類的類類型,那麼底層怎麼實現的,可能會比較複雜,貼一份源碼

public final native Class<?> getClass();

這是一個native聲明的一個方法,稱爲本地方法,Java中有一項技術JNR,使用Java聲明,C語言實現,Java 中調用...一堆,有興趣的可以瞭解瞭解,效果就是上面說的,返回類的類類型

 

下面是簡單的通過Class獲取類的信息:

class Doo {
    public static void staticVoidMethod(Object o) {
        //傳遞的是什麼類型,就是什麼類型
        Class co = o.getClass();

        System.out.println("類的全名稱:" + co.getName());
        System.out.println("類的名字:" + co.getSimpleName());

        //Method類,方法對象
        //一個成員方法 就是 一個Method對象
        //getMethods 獲取所有public的方法,其中包括父類繼承的函數
        Method[] allMethods = co.getMethods();

        //getDeclaredMethods獲取該類自己聲明的方法
        Method[] thisMethods = co.getDeclaredMethods();

        for (Method method : allMethods) {
            //method.getReturnType()得到的是類的類類型
            //比如返回值是String,那麼得到的是String.class的類類型,通過getName獲取名稱
            System.out.println("返回類型:" + method.getReturnType().getName());

            System.out.println("方法名稱:" + method.getName());

            //獲取參數類型
            Class[] parameterTypes = method.getParameterTypes();
            for (Class c : parameterTypes) {
                System.out.println("參數類型:" + c.getName());
            }
            System.out.println("====================================");
        }

        //成員變量 =》對象
        //屬於java.lang.reflect.Field
        //Field封裝了關於成員變量的操作
        //getFields獲取所有public的成員變量
        Field[] field=co.getFields();
        //得到自己聲明的成員變量
        Field[] declaredFields=co.getDeclaredFields();
        for (Field fields:field) {
            System.out.println("成員變量類型"+fields.getType());
            System.out.println("成員變量名稱"+fields.getName());
        }
    }
}

簡單來一個main方法,加入一個String類

public class Coo {
    public static void main(String[] args) {
        String hello=new String();
        Doo.staticVoidMethod(hello);
    }
}

控制檯打印 , 所有String內的方法信息: 

類的全名稱:java.lang.String
類的名字:String
返回類型:boolean
方法名稱:equals
參數類型:java.lang.Object
====================================
返回類型:java.lang.String
方法名稱:toString
====================================
返回類型:int
方法名稱:hashCode
====================================
返回類型:int
方法名稱:compareTo
參數類型:java.lang.Object
====================================
//......

可以總結出來,getDeclaredXXX()方法都是獲取自己聲明的內容,包括成員變量,構造器,方法等等,直接的getXXX()方法部分會獲取所有內容包括父類的內容,另外數組是一個特殊的存在,打印的是“0]”差不多的樣子,在JVM對數組的存儲方式也比較VIP,有興趣的可以理解擴展

 

方法的反射

上面有獲取所有的方法的示例,下面來學習如何獲取某一個方法以及方法的反射操作

①方法的名稱和方法的參數列表可以唯一定位某一個方法

②method.invoke(對象,參數列表)

public class MethodReflect {
    //獲取getMethod方法,獲取①號
    public static void main(String[] args) {
        MethodDemo demo = new MethodDemo();
        //1.獲取類信息
        Class c0 = demo.getClass();

        //2.獲取方法
        try {
            //第一種寫法
            Method method1 = c0.getDeclaredMethod("getMethod", new Class[]{String.class, String.class});
            //第二種寫法
            Method method2 = c0.getDeclaredMethod("getMethod", String.class, String.class);

            //平時正常的調用方法: demo.getMethod(str0,str1)
            //現在使用method1來調用--public Object invoke(Object obj, Object... args)
            //第一個參數是調用的類,第二個參數是可用可無,按定義的方法來錄入(str0,str1)
            //invoke的方法如果有返回值,則返回Object的值,void的返回值爲null
            try {
                Object object1 = method1.invoke(demo, new Object[]{"hello", " world"});
                Object object2 = method2.invoke(demo, "hello", " world");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }

}

class MethodDemo {
    //①
    public void getMethod(String a, String b) {
        System.out.println("concat: " + a + b);
    }

    //②
    public void getMethod(int a, int b) {
        System.out.println("sum: " + a + b);
    }
}

 

反射和泛型

泛型不說了,非常的常用,比如list,map等等,約定類型,不多做解釋,直接先來一個操作,比對List和List<String>是否相等

public class Coo {
    public static void main(String[] args) {
        //無泛型
        List list0=new ArrayList();
        //String泛型
        List<String> list1=new ArrayList<>();
        list1.add("hello");

        Class c0=list0.getClass();
        Class c1=list1.getClass();
        //輸出
        System.out.println(c0==c1);
    }
}

輸出的結果是true,  編譯後的class文件也可以當成字節碼,說明反射的操作都是編譯之後的操作,而且返回true說明編譯之後list的泛型被抹去了,去泛型化的,得到Java的泛型是一種規範,只在編譯時有效,跳過編譯編譯就無效了,爲了驗證這一點,剛好可以使用反射來做一個驗證

//獲取list1<String> 中的 add方法 ,向裏面加一個int類型的值
Method method=c1.getMethod("add",Object.class);
method.invoke(list1,100);

System.out.println(list1.size());

list1的大小改變了,說明添加成功了,也驗證了Java泛型只在編譯期有效,運行時則去泛型化,如果去遍歷這個list1是會報類型轉化異常的

 

反射的用處有很多,比如工具類,源碼理解,註解解析等等,再例如excel導出導入這樣的操作,網上也有非常多的poi操作案例,也可以用反射+註解的方式非常簡潔的實現; 例如spring源碼中很多的註解@Autowired、@SpringCloudApplication、@Service...等等很多很多

 

好好學習,天天向上

------------------------------------------------------------

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