從學習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...等等很多很多
好好學習,天天向上
------------------------------------------------------------