反射
今天我來分享下, 我關(guān)于Java中反射的理解标沪。如果做過iOS開發(fā)的同學(xué)應(yīng)該很清楚iOS里Runtime的黑魔法, 而Java中的反射其實(shí)就是iOS中的Runtime.
什么是反射
反射是一種專門為靜態(tài)語言提供的技術(shù),用于在程序運(yùn)行時(shí)(Runtime)動(dòng)態(tài)的修改程序的結(jié)構(gòu),改變程序的行為.
Java為什么要引入反射
Java也是靜態(tài)語言.為了讓Java語言也能夠在運(yùn)行時(shí)修改類或者對(duì)象的狀態(tài)和改變類或?qū)ο?/strong>的行為因此引入了反射機(jī)制.
在靜態(tài)語言中,使用一個(gè)變量時(shí)绎谦,必須知道它的類型.在Java中兼都,變量的類型信息在編譯時(shí)都保存到了class文件中,這樣在運(yùn)行時(shí)才能保證準(zhǔn)確無誤.換句話說,程序在運(yùn)行時(shí)的行為都是固定的.如果想在運(yùn)行時(shí)改變,就需要反射這東西了.舉個(gè)例子:
在Spring中,有這樣的java bean配置:
<bean id="someID" class="com.sweetcs.AppleBean">
<property name="someField" value="someValue" />
</bean>
Spring
在處理這個(gè)bean標(biāo)簽
時(shí),發(fā)現(xiàn)class屬性
指定的是com.sweetcs.AppleBean
這個(gè)類,就會(huì)調(diào)用Class.forName(String)
來實(shí)例化這個(gè)類,再通過反射,可以取到someField
屬性的值了.
如果我們想改變這個(gè)程序運(yùn)行時(shí)的信息,我們這里直接修改bean
,property的屬性
即可,無需重新編譯.
在動(dòng)態(tài)語言中,使用變量不需要聲明類型
后控,因而不需要這反射這種機(jī)制
。
比如在javascript
中空镜,我們知道有個(gè)變量applebean
浩淘,不管applebean
有沒有sell()
屬性,我們都可以這么寫:applebean.sell()
因?yàn)闆]有類型檢查吴攒,這里這么寫是允許的馋袜。至于在運(yùn)行時(shí)報(bào)不報(bào)錯(cuò),就要看運(yùn)行時(shí)applebean的真正值了舶斧。
一點(diǎn)思考
一欣鳖、反射是可在運(yùn)行期間確定對(duì)象的類型, 多態(tài)也是在運(yùn)行期間才確定類型, 那么多態(tài)的實(shí)現(xiàn)是否和反射有關(guān)?
- 多態(tài)的技術(shù)上的實(shí)現(xiàn)是方法后期的動(dòng)態(tài)綁定, 即在運(yùn)行時(shí)才決定方法應(yīng)該綁定到那塊內(nèi)存中(該內(nèi)存即對(duì)應(yīng)相應(yīng)的對(duì)象)。
- 反射技術(shù)上的實(shí)現(xiàn)是基礎(chǔ)是Java中的方法區(qū)的class,有的語言把它們稱為類對(duì)象.Java中的類都是有類對(duì)象創(chuàng)建的茴厉,我們可以通過類對(duì)象來管理我們創(chuàng)建的所有對(duì)象.
- 說一說多態(tài)的實(shí)現(xiàn)本質(zhì)上和反射并沒有關(guān)系.
二泽台、為什么Java也能夠?qū)崿F(xiàn)反射,加入我們在編譯期間就確定了真實(shí)對(duì)象類型還能使用反射嗎?
- Java在自建立以來就有反射技術(shù), 其反射技術(shù)和多態(tài)主要基于JVM在運(yùn)行時(shí)才動(dòng)態(tài)加載類或調(diào)用方法/訪問屬性的機(jī)制.該機(jī)制不需要事先(寫代碼的時(shí)候或編譯期)知道真實(shí)的運(yùn)行對(duì)象是誰.
- 而反射是基于Class對(duì)象,我們每編寫的一個(gè)類,都會(huì)被編譯成字節(jié)碼文件,將類信息存儲(chǔ)在這個(gè)字節(jié)碼文件中,該文件即Class對(duì)象,在我們使用到這個(gè)Class的靜態(tài)成員變量或者成員時(shí)它就會(huì)加載進(jìn)JVM.
- 多態(tài)主要是基于方法的動(dòng)態(tài)綁定,前提是JVM在運(yùn)行時(shí)才確定對(duì)象的真實(shí)類型,這時(shí)候才去綁定方法到真實(shí)的對(duì)象上.
反射機(jī)制的作用
我們先了解下反射究竟能做哪些事情, 可以分為以下四類.
- 在運(yùn)行期間可以動(dòng)態(tài)的創(chuàng)建任意類型的對(duì)象
- 在運(yùn)行期間可以獲取任意類的類型的任意信息
- 在運(yùn)行期間可以獲取某個(gè)對(duì)象某個(gè)屬性的值或設(shè)置某個(gè)屬性的值
- 在運(yùn)行期間可以調(diào)用某個(gè)對(duì)象的任意方法
Class對(duì)象
要學(xué)習(xí)反射, 首先要學(xué)習(xí)Class對(duì)象,Class對(duì)象表示的是 我們類的類信息(我們編寫的類,).
比如創(chuàng)建一個(gè)Shapes類,那么,JVM就會(huì)創(chuàng)建一個(gè)Shapes對(duì)應(yīng)Class類的Class對(duì)象,該Class對(duì)象保存了Shapes類相關(guān)的類型信息.
實(shí)際上在Java中每個(gè)類都有一個(gè)Class對(duì)象,每當(dāng)我們編寫并且編譯一個(gè)新創(chuàng)建的類就會(huì)產(chǎn)生一個(gè)對(duì)應(yīng)Class對(duì)象并且這個(gè)Class對(duì)象會(huì)被保存在同名.class文件里(編譯后的字節(jié)碼文件保存的就是Class對(duì)象),那為什么需要這樣一個(gè)Class對(duì)象呢?是這樣的,當(dāng)我們new一個(gè)新對(duì)象或者引用靜態(tài)成員變量時(shí),Java虛擬機(jī)(JVM)中的類加載器子系統(tǒng)會(huì)將對(duì)應(yīng)Class對(duì)象加載到JVM中,然后JVM再根據(jù)這個(gè)類型信息相關(guān)的Class對(duì)象創(chuàng)建我們需要實(shí)例對(duì)象或者提供靜態(tài)變量的引用值.
獲取Class對(duì)象
類名.class
這種類型表明編譯期間就能確定類型。僅限于編譯期間這個(gè)類型是已知的矾缓。
@Test
public void test1(){
Class c1 = int[].class;
Class c2 = int[].class;
System.out.println(c1 == c2);
Class c3 = int[][].class;
System.out.println(c1 == c3);
Class c4 = byte[].class;
System.out.println(c1 == c4);
}
對(duì)象.getClass()
獲取某個(gè)對(duì)象的運(yùn)行時(shí)類型
@Test
public void test2(){
Object obj = new TestClass();
Class class1 = obj.getClass();
System.out.println(class1);
}
Class.forName(全限定類名)
使用properties配置文件, 動(dòng)態(tài)配置運(yùn)行時(shí)要?jiǎng)?chuàng)建的類.
@Test
public void test3() throws Exception{
Properties pro = new Properties();
//文件在src下怀酷,最終在bin等類路徑(.class文件)下,可以用ClassLoader加載這個(gè)文件
//文件在src外面嗜闻,只能使用FileInputStream來加載
pro.load(new FileInputStream("type.properties"));
String name = pro.getProperty("typename");
//這句代碼可能會(huì)發(fā)生:ClassNotFoundException蜕依,類型找不到異常
Class clazz = Class.forName(name);
System.out.println(clazz);
}
類加載器.loadClass(全限定類名)
@Test
public void test4()throws Exception{
Properties pro = new Properties();
//文件在src下,最終在bin等類路徑(.class文件)下琉雳,可以用ClassLoader加載這個(gè)文件
//文件在src外面样眠,只能使用FileInputStream來加載
pro.load(new FileInputStream("type.properties"));
String name = pro.getProperty("typename");
//獲取系統(tǒng)類加載器對(duì)象
ClassLoader c = ClassLoader.getSystemClassLoader();
Class loadClass = c.loadClass(name);
System.out.println(loadClass);
}
類加載器應(yīng)用場景
- 主要應(yīng)用于.class文件加密, 需要自身的類加載器來解密
- .class文件是特殊路徑,系統(tǒng)不知道,只能用類加載器
注意事項(xiàng)
- 提供的類名是全限定類名(Class.forName或者類加載器獲取類對(duì)象)
- Class.forName可能拋出ClassNotFoundException
使用場景
(一) 創(chuàng)建任意類型對(duì)象
方式一 使用Class對(duì)象.newInstance(parameters)
步驟
- 獲取這個(gè)類型的類對(duì)象
- 通過這個(gè)類對(duì)象.newInstance()創(chuàng)建出對(duì)應(yīng)對(duì)象.
- 這個(gè)類型必須有一個(gè)公共的無參構(gòu)造器.
示例代碼
@Test
public void testNewInstanceMethod1_2() {
FileInputStream fis = null;
try {
fis = new FileInputStream("properties.properties");
Properties properties = new Properties();
properties.load(fis);
String className = properties.getProperty("student");
Class classObject = Class.forName(className);
Object stu = classObject.newInstance();
System.out.println(stu);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testNewInstanceMethod1_1() {
FileInputStream fis = null;
try {
fis = new FileInputStream("properties.properties");
Properties properties = new Properties();
properties.load(fis);
String className = properties.getProperty("typename");
Class classObject = Class.forName(className);
String str = (String) classObject.newInstance();
System.out.println(classObject);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
特點(diǎn)
- 這個(gè)類型必須有一個(gè)公共的無參構(gòu)造
- 非公共的:IllegalAccessException
沒有無參構(gòu)造:NoSuchMethodException 類型<init>()
- 非公共的:IllegalAccessException
- 這個(gè)類型在運(yùn)行期間必須存在
- 不存在 ClassNotFoundException
方式二 使用構(gòu)造器對(duì)象.newInstance(parameters)
步驟
得到這個(gè)類型的class對(duì)象
-
通過class對(duì)象, 獲取構(gòu)造器對(duì)象。
-
可能有翠肘,可能沒有
- 如果構(gòu)造器的訪問權(quán)限不允許檐束,那么可以使用如下方法,使得它可以被訪問
構(gòu)造器對(duì)象.setAccessible(true)
-
-
使用構(gòu)造器對(duì)象創(chuàng)建對(duì)應(yīng)的類型束倍。
- 構(gòu)造器對(duì)象.newInstance(...)
示例代碼
@Test
public void testNewInstanceMethod2_1() {
FileInputStream fis = null;
try {
fis = new FileInputStream("properties.properties");
Properties properties = new Properties();
properties.load(fis);
String className = properties.getProperty("student");
Class classObject = Class.forName(className);
Constructor constructor = classObject.getDeclaredConstructor(String.class, int.class);
if (!constructor.isAccessible())
constructor.setAccessible(true);
Object o = constructor.newInstance("SweetCS", 26);
System.out.println(o);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} finally {
if (null != fis) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
特點(diǎn)
- 如果夠照的訪問權(quán)限是不允許的被丧, 可以通過調(diào)用構(gòu)造器對(duì)象的setAccessible解決
- 如果沒有會(huì)拋出 IllegalAccessException
(二)獲取類的任意信息
通過這個(gè)Class對(duì)象可以獲取這個(gè)類型的所有的信息
包盟戏、類名、修飾符甥桂、父類柿究、接口們、成員們:屬性黄选、構(gòu)造器蝇摸、方法、注解信息(必須是RetentionPolicy.RUNTIME
- 關(guān)于屬性:
修飾符糕簿、數(shù)據(jù)類型探入、屬性名、注解信息 - 關(guān)于構(gòu)造器
屬性能獲取+ 2(形參列表懂诗、拋出的異常列表) - 關(guān)于方法
屬性能獲取 + 3(返回值類型蜂嗽、形參列表、拋出的異常列表)
public class TestClassInfo {
@Test
public void testClassInfo() throws ClassNotFoundException {
Class managerClass = Class.forName("bean.Manager");
// 獲取屬性信息 (訪問控制符(Modifilers) 類型 屬性名稱)
System.out.println("fileds: ");
Field[] fields = managerClass.getDeclaredFields();
System.out.println("modifiler\ttype\tpropertyName");
for (int i= 0; i <fields.length; i++) {
Field field = fields[i];
System.out.print(
"|-"+ Modifier.toString( + field.getModifiers()) + "-|" + "\t" +
"|-"+ field.getType() + "-|" + "\t" +
"|-"+ field.getName() + "-|"+ "\t");
System.out.println();
// 獲取注解
String annotations = StringUtils.join(field.getDeclaredAnnotations(), ",");
if (StringUtils.trimToNull(annotations) != null)
System.out.println("方法上的注解們:" + annotations);
}
// 構(gòu)造器比屬性少了一個(gè)類型
System.out.println("construtors: ");
Constructor[] constructors = managerClass.getConstructors();
System.out.println("訪問控制符\t構(gòu)造器名稱");
for (Constructor c :
constructors) {
System.out.println("|-" + Modifier.toString(c.getModifiers()) + "\t" + "-|" + "|-" + c.getName() + "-|" );
String exceptions = StringUtils.join(c.getExceptionTypes(), ",");
if (StringUtils.trimToNull(exceptions) != null)
System.out.println("構(gòu)造器上的異常:" + exceptions);
}
// Method 就比屬性多了一個(gè)返回類型
System.out.println("methods: ");
Method[] methods = managerClass.getDeclaredMethods();
for (Method m :
methods) {
System.out.println("|-" + Modifier.toString(m.getModifiers()) + "-|"+ "\t" +
"|-" + m.getReturnType() + "-|"+"\t" +
"|-" + m.getName() +"-|"+
"("+ StringUtils.join(m.getParameterTypes(), ",") + ")");
// 獲取注解
String annotations = StringUtils.join(m.getDeclaredAnnotations(), ",");
if (StringUtils.trimToNull(annotations) != null)
System.out.println("方法上的注解們:" + annotations);
String exceptions = StringUtils.join(m.getExceptionTypes(), ",");
if (StringUtils.trimToNull(exceptions) != null)
System.out.println("構(gòu)造器上的異常:" + exceptions);
}
// 其他
// 獲取包名
Package p = managerClass.getPackage();
System.out.println("包名: " + p.getName());
// 類訪問修飾符
String modifierName = Modifier.toString(managerClass.getModifiers());
System.out.println("類訪問修飾符:" + modifierName);
// 父類
Class superClass = managerClass.getSuperclass();
System.out.println("父類:" + superClass);
// 獲取實(shí)現(xiàn)的接口
Class[] interfaces = managerClass.getInterfaces();
for (Class i:
interfaces) {
System.out.println("接口:" + i);
}
}
}
輸出
fileds:
modifiler type propertyName
|-private static final-| |-long-| |-serialVersionUID-|
|-private-| |-double-| |-bonus-|
construtors:
訪問控制符 構(gòu)造器名稱
|-public -||-day23.bean.Manager-|
|-public -||-day23.bean.Manager-|
methods:
|-public-| |-class java.lang.String-| |-toString-|()
|-public-| |-int-| |-compareTo-|(class day23.bean.Manager)
|-public volatile-| |-int-| |-compareTo-|(class java.lang.Object)
|-public-| |-double-| |-getBonus-|()
|-public-| |-void-| |-setBonus-|(double)
方法上的注解們:@day23.bean.MyAnnotation(value=SweetCS)
包名: day23.bean
類訪問修飾符:public final
父類:class day23.bean.Employee
接口:interface java.io.Serializable
接口:interface java.lang.Comparabl
(三) 動(dòng)態(tài)設(shè)置和獲取屬性值
@Test
public void testFiled() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//假設(shè)Manager對(duì)象是在運(yùn)行期間創(chuàng)建的
Class mangerClass = Class.forName("day23.bean.Manager");
//obj代表一個(gè)經(jīng)理對(duì)象
Object obj = mangerClass.newInstance();
//2殃恒、設(shè)置obj這個(gè)經(jīng)理的獎(jiǎng)金值
//(1)先得到獎(jiǎng)金屬性對(duì)象
Field bounsField = mangerClass.getDeclaredField("bonus"); //"bonus"配置文件中配置
//(2)設(shè)置屬性可操作,private屬性既不能使用反射直接setter也不能直接getter
if (!bounsField.isAccessible()) bounsField.setAccessible(true);
System.out.println("bonus :" + bounsField.get(obj));
//(3)設(shè)置獎(jiǎng)金值
bounsField.set(obj, 1000.0);
//3植旧、獲取obj這個(gè)經(jīng)理的獎(jiǎng)金值
System.out.println("bonus :" + bounsField.get(obj));
}
反射進(jìn)行屬性操作終結(jié)
- 通過類對(duì)象獲取屬性對(duì)象(Field)
- 最好用getDeclaredField來獲取屬性對(duì)象, 該方法私有屬性也能獲取
- 如果是讀屬性, 調(diào)用
屬性對(duì)象.get(實(shí)例對(duì)象)
- 如果是寫屬性, 調(diào)用
屬性對(duì)象.set(實(shí)例對(duì)象, 參數(shù))
注意: 如果屬性不可訪問, 設(shè)置屬性為accessible, 即可訪問
(四) 方法調(diào)用
反射技術(shù)還可以通過類對(duì)象來獲取這個(gè)對(duì)象所有的方法對(duì)象。java中方法對(duì)象使用Method表示离唐。所以我們可以通過類對(duì)象拿到Method對(duì)象, 來進(jìn)行方法的調(diào)用病附。
@Test
public void testMethodInvoke() throws Exception {
// 假設(shè)Manager對(duì)象是在運(yùn)行期間創(chuàng)建的
Class clazz = Class.forName("day23.bean.Manager");// 這個(gè)字符串可以從配置文件中獲取
// obj代表一個(gè)經(jīng)理對(duì)象
Object obj = clazz.newInstance();
//調(diào)用obj經(jīng)理對(duì)象的setEId,setEName,setSalary等
//(1)得到setName方法對(duì)象
//參數(shù)一:方法名
//參數(shù)二:方法的形參類型列表
//因?yàn)榉椒赡苤剌d,所以需要用“方法名 + 形參列表”
//"setEname", String.class通常也在xml文件中配置
//getMethod可以得到這個(gè)類型的公共的方法包括從父類繼承的
Method method = clazz.getMethod("setEname", String.class);//
//(2)調(diào)用這個(gè)方法
//參數(shù)一:哪個(gè)對(duì)象的method方法
//參數(shù)二:該method方法需要的實(shí)參列表
//invoke方法的返回值亥鬓,就是method方法調(diào)用后的返回值完沪,如果method方法沒有返回值,那么為null
Object returnValue = method.invoke(obj, "張三");
System.out.println(returnValue);
// (1)得到getName方法對(duì)象
Method method2 = clazz.getMethod("getEname");
//(2)調(diào)用
Object value = method2.invoke(obj);
System.out.println(value);
}
反射調(diào)用方法總結(jié)
- 通過類對(duì)象, 拿到對(duì)應(yīng)的方法對(duì)象(Method)
- 對(duì)于setter, 使用getMethod(方法名, 類對(duì)象列表). 這里的類對(duì)象列表是方法名的形參類型的類對(duì)象
- 對(duì)于getter, 使用getMethod(方法名)
- 調(diào)用invoke方法
- 對(duì)于setter, 傳入實(shí)例對(duì)象(可以用類對(duì)象.newInstance()創(chuàng)建) 和 set的參數(shù)
- 對(duì)于getter, 傳入實(shí)例對(duì)象