寫在最前
很高興前一段的文章能給大家?guī)?lái)幫助浙于,相信大家都清楚基礎(chǔ)知識(shí)對(duì)于程序員來(lái)說(shuō)是解決問(wèn)題的根基稻轨,筆者的系列文章將從英文定義政钟、思路分析缚去、實(shí)例應(yīng)用一步一步對(duì)重要的基礎(chǔ)知識(shí)進(jìn)行講解潮秘,沒(méi)關(guān)注的童鞋趕緊點(diǎn)一波關(guān)注,跟筆者一起鞏固你們的基礎(chǔ)吧R捉帷枕荞!
附前文地址:十個(gè)不可忽視的Java基礎(chǔ)知識(shí)
上次的文章中提到了Java的反射機(jī)制,但有讀者私下反映講解并不透徹搞动,仍然屬于懵圈狀態(tài)躏精,因此本文將對(duì)其進(jìn)行詳解。
RTTI和反射鹦肿?
這是一個(gè)經(jīng)常被混淆的問(wèn)題矗烛。RTTI or Reflection? 相信看過(guò)《Thinking in Java》的同學(xué)都有這么個(gè)疑惑,在Type Information這一章中有這么一段話箩溃。
Java allows you to discover information about objects and classes at run time. This takes two forms: "traditional" RTTI, which assumes that you have all the types available at compile time, and the reflection mechanism, which allows you to discover and use class information solely at run time.
這其中瞭吃,RTTI(Runtime Type Information)是C++ 中的一個(gè)概念,B大明顯先學(xué)的C++,在這本書之前也是先出版過(guò)《Thinking in C++》涣旨,因此可能對(duì)這個(gè)名詞有深厚的感情虱而?總之,千萬(wàn)不要嘗試去區(qū)分RTTI和反射(Reflection)的區(qū)別开泽,這兩者就像老人和男人(來(lái)自某知乎網(wǎng)友貼切的舉例),分開(kāi)理解是很蠢的魁瞪。
這里多說(shuō)一句穆律,諸如TIJ這種神書,也并不是適合每一個(gè)人去理解知識(shí)的导俘,在有些篇幅中峦耘,如果你發(fā)現(xiàn)自己卡住了,一定要多看例子旅薄,多查資料辅髓,用自己從中悟出的道理去解釋泣崩,不要去和死定義糾纏。
反射的目的
很多人在熟練掌握本文所述內(nèi)容后仍不解RTTI的目的洛口,一般有兩個(gè)原因:一是代碼編寫量過(guò)小矫付,自己設(shè)計(jì)的經(jīng)驗(yàn)不足,二則是不具備動(dòng)態(tài)的設(shè)計(jì)思路第焰。我們先來(lái)看下面和個(gè)例子(出自TIJ):
import java.util.*;
abstract class Shape{
void draw() {
System.out.println(this + ".draw() ");
}
abstract public String toString();
}
class Circle extends Shape {
public String toString() {
return "Circle ";
}
}
class Square extends Shape {
public String toString() {
return "Square ";
}
}
class Triangle extends Shape {
public String toString() {
return "Triangle ";
}
}
public class Shapes {
public static void main(String[] args) {
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle()
);
for (Shape shape : shapeList) {
shape.draw();
}
}
}
在上例中买优,我們創(chuàng)建了Shape類,其中Circle, Triangle, 以及Square是他的三個(gè)子類挺举。在主方法中我們創(chuàng)建了Shape的list杀赢,然后用匿名的方式創(chuàng)建三種子類的引用,不難看出湘纵,在這里他們都被強(qiáng)制向上轉(zhuǎn)換為了Shape脂崔。所以輸出的值為:
/*
Output:
Circle.draw()
Square.draw()
Triangle.draw()
*/
由此可見(jiàn),我們通過(guò)熟悉多態(tài)(Polymorphism)梧喷,也就是在子類中重寫toString()方法砌左,實(shí)現(xiàn)了最基礎(chǔ)的在運(yùn)行中獲取類型信息。利用此種方法伤柄,可使你的代碼更易讀寫及維護(hù)绊困,設(shè)計(jì)也更容易被理解,執(zhí)行或修改适刀。但如果我們不只是需要draw()方法被執(zhí)行秤朗,而需要執(zhí)行一些并不適用于全部子類的方法,比如rotate()笔喉,旋轉(zhuǎn)取视,對(duì)于Circle來(lái)說(shuō)執(zhí)行沒(méi)有任何意義,那么我們就需要反射機(jī)制常挚,我們便可以使Shape調(diào)用屬于他子類獨(dú)有的方法作谭。
這里光靠定義說(shuō)明確實(shí)是很難理解,舉個(gè)通俗的例子吧奄毡。比如屏幕上有一大堆各種各樣的水果折欠,蘋果,梨吼过,香蕉锐秦。蘋果被點(diǎn)擊時(shí)候執(zhí)行爆炸方法,香蕉被點(diǎn)擊的時(shí)候執(zhí)行剝皮方法盗忱,梨被點(diǎn)擊的時(shí)候砍成兩半酱床。那么我不知道用戶點(diǎn)擊的是哪個(gè)水果,但我希望直接執(zhí)行這個(gè)水果的方法趟佃,這時(shí)候利用反射機(jī)制將會(huì)使得我們的代碼簡(jiǎn)單已讀且利于修改再開(kāi)發(fā)扇谣。
Class類型及對(duì)象的獲取方法
在運(yùn)行時(shí)(runtime)承載類型信息的Class對(duì)象昧捷,你的程序中任何一個(gè)類,在你編譯的時(shí)候罐寨,這個(gè)Class對(duì)象都會(huì)被創(chuàng)建靡挥。簡(jiǎn)單點(diǎn)說(shuō)就是編譯后出現(xiàn)的Name.class那個(gè)文件,Name是和你的類名相同的名字(創(chuàng)建這個(gè)對(duì)象的是JVM的一個(gè)子系統(tǒng)叫做"class loader")衩茸。Java中獲取Class類型的方法有如下三種:
- .class
所有的引用數(shù)據(jù)類型我們都可以在類名后加上".class"來(lái)獲取Class類型芹血,這種方法是靜態(tài)的。這里要多說(shuō)一句的是楞慈,若是基本類型幔烛,我們同樣可以使用“封裝類.TYPE”的方法,比如"int.class"對(duì)應(yīng)的是"Integer.TYPE"囊蓝。
- Class.forName()
通過(guò)一個(gè)類的String名來(lái)獲取Class類型饿悬,這種方法同上一樣為靜態(tài),且必須拋出ClassNotFoundException聚霜。
- getClass()
這種方法是通過(guò)實(shí)例名來(lái)獲取Class類型狡恬,是動(dòng)態(tài)的,這點(diǎn)與前兩種不同蝎宇,注意區(qū)分弟劲。
綜合來(lái)看上述三種方法,我們用一個(gè)表格來(lái)簡(jiǎn)單來(lái)區(qū)分他們:
方法名 | 獲取形式 |
---|---|
.class | 獲取聲明時(shí)的類 |
getClass() | 獲取運(yùn)行時(shí)的類 |
Class.forName() | 通過(guò)類名來(lái)獲得類 |
在獲得類型之后姥芥,在后面加一個(gè)newInstance()方法兔乞,便能創(chuàng)建對(duì)象,例如:
Class cc = Class.forName("Daniel");
Object oo = cc.newInstance();
//這里調(diào)用的是關(guān)于cc的無(wú)參數(shù)構(gòu)造方法
類中屬性的獲取方法
有了Class對(duì)象后凉唐,我們當(dāng)然希望對(duì)其的方法和參數(shù)進(jìn)行執(zhí)行或修改庸追,此處我們最常見(jiàn)的便是getDeclaredFields()方法了。例如:
Class cc = Class.forName("Daniel");
Field[] field = cc.getDeclaredFields();
需要注意的是台囱,類中又很多方法淡溯,所以我們應(yīng)該定義一個(gè)Field數(shù)組。在這里常用的方法我將列在下面的這個(gè)表格當(dāng)中簿训,需要用時(shí)我們只需套用特定的方法即可咱娶,理解起來(lái)非常容易。
方法 | 作用 |
---|---|
getDeclaredMethods() | 獲取所有的方法 |
getReturnType() | 獲得方法的返回類型 |
getParameterTypes() | 獲得方法的傳入?yún)?shù)類型 |
getDeclaredMethod("",.class,……) | 獲得特定的方法 |
getDeclaredConstructors() | 獲取所有的構(gòu)造方法 |
getDeclaredConstructor(.class,……) | 獲取特定的構(gòu)造方法 |
getSuperclass() | 獲取某類的父類 |
getInterfaces() | 獲取某類實(shí)現(xiàn)的接口 |
用以上方法進(jìn)行反編譯强品,使我們的代碼變得更加靈活豺总,這也是OO類型編程的一個(gè)終極目標(biāo)。
說(shuō)到這里择懂,我們?cè)僖黄饋?lái)看一下上次文章中提到的來(lái)自CSDN上的例子:
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* <a class='replace_word' title="Java 知識(shí)庫(kù)" target='_blank' style='color:#df3434; font-weight:bold;'>Java </a>Reflection Cookbook
*
* @author Michael Lee
* @since 2006-8-23
* @version 0.1a
*/
public class Reflection {
/**
* 得到某個(gè)對(duì)象的公共屬性
*
* @param owner, fieldName
* @return 該屬性對(duì)象
* @throws Exception
*
*/
public Object getProperty(Object owner, String fieldName) throws Exception {
Class ownerClass = owner.getClass();
Field field = ownerClass.getField(fieldName);
Object property = field.get(owner);
return property;
}
/**
* 得到某類的靜態(tài)公共屬性
*
* @param className 類名
* @param fieldName 屬性名
* @return 該屬性對(duì)象
* @throws Exception
*/
public Object getStaticProperty(String className, String fieldName)
throws Exception {
Class ownerClass = Class.forName(className);
Field field = ownerClass.getField(fieldName);
Object property = field.get(ownerClass);
return property;
}
/**
* 執(zhí)行某對(duì)象方法
*
* @param owner
* 對(duì)象
* @param methodName
* 方法名
* @param args
* 參數(shù)
* @return 方法返回值
* @throws Exception
*/
public Object invokeMethod(Object owner, String methodName, Object[] args)
throws Exception {
Class ownerClass = owner.getClass();
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(owner, args);
}
/**
* 執(zhí)行某類的靜態(tài)方法
*
* @param className
* 類名
* @param methodName
* 方法名
* @param args
* 參數(shù)數(shù)組
* @return 執(zhí)行方法返回的結(jié)果
* @throws Exception
*/
public Object invokeStaticMethod(String className, String methodName,
Object[] args) throws Exception {
Class ownerClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(null, args);
}
/**
* 新建實(shí)例
*
* @param className
* 類名
* @param args
* 構(gòu)造函數(shù)的參數(shù)
* @return 新建的實(shí)例
* @throws Exception
*/
public Object newInstance(String className, Object[] args) throws Exception {
Class newoneClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Constructor cons = newoneClass.getConstructor(argsClass);
return cons.newInstance(args);
}
/**
* 是不是某個(gè)類的實(shí)例
* @param obj 實(shí)例
* @param cls 類
* @return 如果 obj 是此類的實(shí)例,則返回 true
*/
public boolean isInstance(Object obj, Class cls) {
return cls.isInstance(obj);
}
/**
* 得到數(shù)組中的某個(gè)元素
* @param array 數(shù)組
* @param index 索引
* @return 返回指定數(shù)組對(duì)象中索引組件的值
*/
public Object getByArray(Object array, int index) {
return Array.get(array,index);
}
}
再一次回看我們會(huì)發(fā)現(xiàn)另玖,以上不過(guò)只是將各種方法的應(yīng)用簡(jiǎn)單寫出困曙,并沒(méi)有過(guò)多的邏輯表伦。簡(jiǎn)單來(lái)說(shuō),這種類型的代碼對(duì)于我們來(lái)說(shuō)只是字典型的材料慷丽,在設(shè)計(jì)的過(guò)程中即查即用蹦哼,沒(méi)必要逐一去背誦。
結(jié)語(yǔ)
關(guān)于Java反射部分的知識(shí)要糊,本文中也涉及得差不多了纲熏。對(duì)于反射機(jī)制,比起了解語(yǔ)法锄俄,更重要的是要去理解它在設(shè)計(jì)模式中發(fā)揮的作用局劲,而設(shè)計(jì)模式是Java編程思想中相當(dāng)重要的一環(huán),有關(guān)內(nèi)容在后續(xù)的文章中可能還會(huì)提及奶赠,希望大家持續(xù)關(guān)注鱼填。
文章中若有錯(cuò)誤或不盡人意之處還請(qǐng)大家指出,歡迎大家私信作者的微博:LightningDC或直接在文章下方回復(fù)進(jìn)行交流毅戈。筆者還會(huì)繼續(xù)為大家更新苹丸,歡迎關(guān)注,如果文章對(duì)你有用苇经,就隨手點(diǎn)個(gè)贊吧哈哈赘理,碼字不易。