java.lang.Class;
java.lang.reflect.Constructor; java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;
//第一種方式:
Classc1 = Class.forName("Employee");
//第二種方式:
//java中每個(gè)類型都有class 屬性.
Classc2 = Employee.class;
//第三種方式:
//java語(yǔ)言中任何一個(gè)java對(duì)象都有g(shù)etClass 方法
Employeee = new Employee();
Classc3 = e.getClass(); //c3是運(yùn)行時(shí)類 (e的運(yùn)行時(shí)類是Employee)
Class c =Class.forName("Employee");
//創(chuàng)建此Class 對(duì)象所表示的類的一個(gè)新實(shí)例
Objecto = c.newInstance(); //調(diào)用了Employee的無(wú)參數(shù)構(gòu)造方法.
//獲取整個(gè)類
Class c = Class.forName("java.lang.Integer");
//獲取所有的屬性?
Field[] fs = c.getDeclaredFields();
//定義可變長(zhǎng)的字符串,用來(lái)存儲(chǔ)屬性
StringBuffer sb = new StringBuffer();
//通過追加的方法,將每個(gè)屬性拼接到此字符串中
//最外邊的public定義
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() +"{\n");
//里邊的每一個(gè)屬性
for(Field field:fs){
sb.append("\t");//空格
sb.append(Modifier.toString(field.getModifiers())+" ");//獲得屬性的修飾符,例如public,static等等
sb.append(field.getType().getSimpleName() + " ");//屬性的類型的名字
sb.append(field.getName()+";\n");//屬性的名字+回車
}
sb.append("}");
System.out.println(sb);
public static void main(String[] args) throws Exception{
<span style="white-space:pre"> </span>//以前的方式:
/*
User u = new User();
u.age = 12; //set
System.out.println(u.age); //get
*/
//獲取類
Class c = Class.forName("User");
//獲取id屬性
Field idF = c.getDeclaredField("id");
//實(shí)例化這個(gè)類賦給o
Object o = c.newInstance();
//打破封裝
idF.setAccessible(true); //使用反射機(jī)制可以打破封裝性,導(dǎo)致了java對(duì)象的屬性不安全。
//給o對(duì)象的id屬性賦值"110"
idF.set(o, "110"); //set
//get
System.out.println(idF.get(o));
}
4,獲取方法,和構(gòu)造方法,不再詳細(xì)描述,只來(lái)看一下關(guān)鍵字:
方法關(guān)鍵字
含義
getDeclaredMethods()
獲取所有的方法
getReturnType()
獲得方法的放回類型
getParameterTypes()
獲得方法的傳入?yún)?shù)類型
getDeclaredMethod("方法名",參數(shù)類型.class,……)
獲得特定的方法
構(gòu)造方法關(guān)鍵字
含義
getDeclaredConstructors()
獲取所有的構(gòu)造方法
getDeclaredConstructor(參數(shù)類型.class,……)
獲取特定的構(gòu)造方法
父類和父接口
含義
getSuperclass()
獲取某類的父類
getInterfaces()
獲取某類實(shí)現(xiàn)的接口
這樣我們就可以獲得類的各種內(nèi)容,進(jìn)行了反編譯。對(duì)于JAVA這種先編譯再運(yùn)行的語(yǔ)言來(lái)說,反射機(jī)制可以使代碼更加靈活,更加容易實(shí)現(xiàn)面向?qū)ο?br />
五,反射加配置文件,使我們的程序更加靈活:
在設(shè)計(jì)模式學(xué)習(xí)當(dāng)中,學(xué)習(xí)抽象工廠的時(shí)候就用到了反射來(lái)更加方便的讀取數(shù)據(jù)庫(kù)鏈接字符串等,當(dāng)時(shí)不是太理解,就照著抄了。看一下.NET中的反射+配置文件的使用:
當(dāng)時(shí)用的配置文件是app.config文件,內(nèi)容是XML格式的,里邊填寫鏈接數(shù)據(jù)庫(kù)的內(nèi)容:
<configuration>
lt;appSettings>
<add key="" value=""/>
lt;/appSettings>
</configuration>
反射的寫法:
assembly.load("當(dāng)前程序集的名稱").CreateInstance("當(dāng)前命名空間名稱".要實(shí)例化的類名);
這樣的好處是很容易的方便我們變換數(shù)據(jù)庫(kù),例如我們將系統(tǒng)的數(shù)據(jù)庫(kù)從SQL Server升級(jí)到Oracle,那么我們寫兩份D層,在配置文件的內(nèi)容改一下,或者加條件選擇一下即可,帶來(lái)了很大的方便。
當(dāng)然了,JAVA中其實(shí)也是一樣,只不過這里的配置文件為.properties,稱作屬性文件。通過反射讀取里邊的內(nèi)容。這樣代碼是固定的,但是配置文件的內(nèi)容我們可以改,這樣使我們的代碼靈活了很多!
綜上為,JAVA反射的再次學(xué)習(xí),靈活的運(yùn)用它,能夠使我們的代碼更加靈活,但是它也有它的缺點(diǎn),就是運(yùn)用它會(huì)使我們的軟件的性能降低,復(fù)雜度增加,所以還要我們慎重的使用它。
那么如何利用反射API在運(yùn)行的時(shí)候知道一個(gè)類的信息呢?
代碼示例:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.swing.JOptionPane;
/**
*本類用于測(cè)試反射API,利用用戶輸入類的全路徑,
*找到該類所有的成員方法和成員屬性
*/
public class MyTest {
/**
*構(gòu)造方法
*/
public MyTest(){
String classInfo=JOptionPane.showInputDialog(null,"輸入類全路徑");//要求用戶輸入類的全路徑
try {
Class cla=Class.forName(classInfo);//根據(jù)類的全路徑進(jìn)行類加載,返回該類的Class對(duì)象
Method[] method=cla.getDeclaredMethods();//利用得到的Class對(duì)象的自審,返回方法對(duì)象集合
for(Method me:method){//遍歷該類方法的集合
System.out.println(me.toString());//打印方法信息
}
System.out.println("********");
Field[] field=cla.getDeclaredFields();//利用得到的Class對(duì)象的自審,返回屬性對(duì)象集合
for(Field me:field){ //遍歷該類屬性的集合
System.out.println(me.toString());//打印屬性信息
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new MyTest();
}
}
運(yùn)行的時(shí)候,我們輸入javax.swing.JFrame,那么運(yùn)行結(jié)果如下:
public void javax.swing.JFrame.remove(java.awt.Component)
public void javax.swing.JFrame.update(java.awt.Graphics)
…………
********
public static final int javax.swing.JFrame.EXIT_ON_CLOSE
private int javax.swing.JFrame.defaultCloseOperation
…………
大家可以發(fā)現(xiàn),類的全路徑是在程序運(yùn)行的時(shí)候,由用戶輸入的。所以虛擬機(jī)事先并不知道所要加載類的信息,這就是利用反射機(jī)制來(lái)對(duì)用戶輸入的類全路徑來(lái)對(duì)類自身的一個(gè)自審。從而探知該類所擁有的方法和屬性。
通過上面代碼,大家可以知道編譯工具為什么能夠一按點(diǎn)就能列出用戶當(dāng)前對(duì)象的屬性和方法了。它是先獲得用戶輸入對(duì)象的字符串,然后利用反射原理來(lái)對(duì)這樣的類進(jìn)行自審,從而列出該類的方法和屬性。
使用反射機(jī)制的步驟:
u導(dǎo)入java.lang.relfect 包
u遵循三個(gè)步驟
第一步是獲得你想操作的類的 java.lang.Class 對(duì)象
第二步是調(diào)用諸如 getDeclaredMethods 的方法
第三步使用 反射API 來(lái)操作這些信息
獲得Class對(duì)象的方法
u如果一個(gè)類的實(shí)例已經(jīng)得到,你可以使用
【Class c = 對(duì)象名.getClass(); 】
例: TextField t = new TextField();
Class c = t.getClass();
Class s = c.getSuperclass();
u如果你在編譯期知道類的名字,你可以使用如下的方法
Class c = java.awt.Button.class;
或者
Class c = Integer.TYPE;
u如果類名在編譯期不知道, 但是在運(yùn)行期可以獲得, 你可以使用下面的方法
Class c = Class.forName(strg);
package
public class MyTest {
public static void main(String[] args) {
TestOne one=null;
try{
Class cla=Class.forName("com.TestOne");//進(jìn)行com.TestOne類加載,返回一個(gè)Class對(duì)象
System.out.println("********");
one=(TestOne)cla.newInstance();//產(chǎn)生這個(gè)Class類對(duì)象的一個(gè)實(shí)例,調(diào)用該類無(wú)參的構(gòu)造方法,作用等同于new TestOne()
}catch(Exception e){
e.printStackTrace();
}
TestOne two=new TestOne();
System.out.println(one.getClass() == two.getClass());//比較兩個(gè)TestOne對(duì)象的Class對(duì)象是否是同一個(gè)對(duì)象,在這里結(jié)果是true。說明如果兩個(gè)對(duì)象的類型相同,那么它們會(huì)有相同的Class對(duì)象
}
}
class TestOne{
static{
System.out.println("靜態(tài)代碼塊運(yùn)行");
}
TestOne(){
System.out.println("構(gòu)造方法");
}
}
靜態(tài)代碼塊運(yùn)行
***********
構(gòu)造方法
構(gòu)造方法
Class.forName("com.TestOne")的時(shí)候,實(shí)際上是對(duì)com.TestOne進(jìn)行類加載,這時(shí)候,會(huì)把靜態(tài)屬性、方法以及靜態(tài)代碼塊都加載到內(nèi)存中。所以這時(shí)候會(huì)打印出"靜態(tài)代碼塊運(yùn)行"。但這時(shí)候,對(duì)象卻還沒有產(chǎn)生。所以"構(gòu)造方法"這幾個(gè)字不會(huì)打印。當(dāng)執(zhí)行cla.newInstance()的時(shí)候,就是利用反射機(jī)制將Class對(duì)象生成一個(gè)該類的一個(gè)實(shí)例。這時(shí)候?qū)ο缶彤a(chǎn)生了。所以打印"構(gòu)造方法"。當(dāng)執(zhí)行到TestOne two=new TestOne()語(yǔ)句時(shí),又生成了一個(gè)對(duì)象。但這時(shí)候類已經(jīng)加載完畢,靜態(tài)的東西已經(jīng)加載到內(nèi)存中,而靜態(tài)代碼塊只執(zhí)行一次,所以不用再去加載類,所以只會(huì)打印"構(gòu)造方法",而"靜態(tài)代碼塊運(yùn)行"不會(huì)打印。
反射機(jī)制不但可以例出該類對(duì)象所擁有的方法和屬性,還可以獲得該類的構(gòu)造方法及通過構(gòu)造方法獲得實(shí)例。也可以動(dòng)態(tài)的調(diào)用這個(gè)實(shí)例的成員方法。
代碼示例:
package reflect;
import java.lang.reflect.Constructor;
/**
*
* 本類測(cè)試反射獲得類的構(gòu)造器對(duì)象,
* 并通過類構(gòu)造器對(duì)象生成該類的實(shí)例
*
*/
public class ConstructorTest {
public static void main(String[] args) {
try {
//獲得指定字符串類對(duì)象
Class cla=Class.forName("reflect.Tests");
//設(shè)置Class對(duì)象數(shù)組,用于指定構(gòu)造方法類型
Class[] cl=new Class[]{int.class,int.class};
//獲得Constructor構(gòu)造器對(duì)象。并指定構(gòu)造方法類型
Constructor con=cla.getConstructor(cl);
//給傳入?yún)?shù)賦初值
Object[] x={new Integer(33),new Integer(67)};
//得到實(shí)例
Object obj=con.newInstance(x);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Tests{
public Tests(int x,int y){
System.out.println(x+" "+y);
}
}
運(yùn)行的結(jié)果是” 33 67”。說明我們已經(jīng)生成了Tests這個(gè)類的一個(gè)對(duì)象。
同樣,也可以通過反射模式,來(lái)執(zhí)行Java類的方法
代碼示例:
package reflect;
import java.lang.reflect.Method;
/**
*
* 本類測(cè)試反射獲得類的方法對(duì)象,
* 并通過類對(duì)象和類方法對(duì)象,運(yùn)行該方法
*
*/
public class MethodTest {
public static void main(String[] args) {
try {
//獲得窗體類的Class對(duì)象
Class cla=Class.forName("javax.swing.JFrame");
//生成窗體類的實(shí)例
Object obj=cla.newInstance();
//獲得窗體類的setSize方法對(duì)象,并指定該方法參數(shù)類型為int,int
Method methodSize=cla.getMethod("setSize", new Class[]{int.class,int.class});
/*
* 執(zhí)行setSize()方法,并傳入一個(gè)Object[]數(shù)組對(duì)象,
* 作為該方法參數(shù),等同于 窗體對(duì)象.setSize(300,300);
*/
methodSize.invoke(obj, new Object[]{new Integer(300),new Integer(300)});
//獲得窗體類的setSize方法對(duì)象,并指定該方法參數(shù)類型為boolean
Method methodVisible=cla.getMethod("setVisible", new Class[]{boolean.class});
/*
* 執(zhí)行setVisible()方法,并傳入一個(gè)Object[]數(shù)組對(duì)象, *作為該方法參數(shù)。 等同于 窗體對(duì)象.setVisible(true);
*/
methodVisible.invoke(obj, new Object[]{new Boolean(true)});
} catch (Exception e) {
e.printStackTrace();
}
}
}
反射技術(shù)大量用于Java設(shè)計(jì)模式和框架技術(shù),最常見的設(shè)計(jì)模式就是工廠模式(Factory)和單例模式(Singleton)。
單例模式(Singleton)
這個(gè)模式主要作用是保證在Java應(yīng)用程序中,一個(gè)類Class只有一個(gè)實(shí)例存在。在很多操作中,比如建立目錄 數(shù)據(jù)庫(kù)連接都需要這樣的單線程操作。這樣做就是為了節(jié)省內(nèi)存空間,保證我們所訪問到的都是同一個(gè)對(duì)象。
單例模式要求保證唯一,那么怎么樣才能保證唯一性呢?對(duì)了,這就是靜態(tài)變量。單例模式有以下兩種形式:
第一種形式:
package reflect;
public class Singleton {
/*
* 注意這是private私有的構(gòu)造方法, 只供內(nèi)部調(diào)用
* 外部不能通過new的方式來(lái)生成該類的實(shí)例
*/
private Singleton() {
}
/*
* 在自己內(nèi)部定義自己一個(gè)實(shí)例,是不是很奇怪?
* 定義一個(gè)靜態(tài)的實(shí)例,保證其唯一性
*/
private static Singleton instance = new Singleton();
// 這里提供了一個(gè)供外部訪問本class的靜態(tài)方法,可以直接訪問
public static Singleton getInstance() {
return instance;
}
}
/**
*測(cè)試單例模式
*/
class SingRun{
public static void main(String[] args){
//這樣的調(diào)用不被允許,因?yàn)闃?gòu)造方法是私有的。
//Singleton x=new Singleton();
//得到一個(gè)Singleton類實(shí)例
Singleton x=Singleton.getInstance();
//得到另一個(gè)Singleton類實(shí)例
Singleton y=Singleton.getInstance();
//比較x和y的地址,結(jié)果為true。說明兩次獲得的是同一個(gè)對(duì)象
System.out.println(x==y);
}
}
第二種形式:
public class Singleton {
//先申明該類靜態(tài)對(duì)象
private static Singleton instance = null;
//創(chuàng)建一個(gè)靜態(tài)訪問器,獲得該類實(shí)例。加上同步,表示防止兩個(gè)線程同時(shí)進(jìn)行對(duì)象的創(chuàng)建
public static synchronized Singleton getInstance() {
//如果為空,則生成一個(gè)該類實(shí)例
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
工廠模式(Factory)
工廠模式是我們最常用的模式了,著名的Jive論壇 ,就大量使用了工廠模式,工廠模式在Java程序系統(tǒng)可以說是隨處可見。
為什么工廠模式是如此常用?是因?yàn)楣S模式利用Java反射機(jī)制和Java多態(tài)的特性可以讓我們的程序更加具有靈活性。用工廠模式進(jìn)行大型項(xiàng)目的開發(fā),可以很好的進(jìn)行項(xiàng)目并行開發(fā)。就是一個(gè)程序員和另一個(gè)程序員可以同時(shí)去書寫代碼,而不是一個(gè)程序員等到另一個(gè)程序員寫完以后再去書寫代碼。其中的粘合劑就是接口和配置文件。
之前說利用接口可以將調(diào)用和實(shí)現(xiàn)相分離。
那么這是怎么樣去實(shí)現(xiàn)的呢?工廠模式可以為我們解答。
我們先來(lái)回顧一下軟件的生命周期,分析、設(shè)計(jì)、編碼、調(diào)試與測(cè)試。其中分析就是指需求分析,就是知道這個(gè)軟件要做成什么樣子,要實(shí)現(xiàn)什么樣的功能。功能知道了,這時(shí)就要設(shè)計(jì)了。設(shè)計(jì)的時(shí)候要考慮到怎么樣高效的實(shí)現(xiàn)這個(gè)項(xiàng)目,如果讓一個(gè)項(xiàng)目團(tuán)隊(duì)并行開發(fā)。這時(shí)候,通常先設(shè)計(jì)接口,把接口給實(shí)現(xiàn)接口的程序員和調(diào)用接口的程序員,在編碼的時(shí)候,兩個(gè)程序員可以互不影響的實(shí)現(xiàn)相應(yīng)的功能,最后通過配置文件進(jìn)行整合。
代碼示例:
/**
*
*定義接口
*/
interface InterfaceTest{
public void getName();//定義獲得名字的方法
}
接口有了,那么得到這個(gè)接口,進(jìn)行實(shí)現(xiàn)編碼的程序員應(yīng)該怎么做呢?對(duì)了,實(shí)現(xiàn)這個(gè)接口,重寫其中定義的方法
接口實(shí)現(xiàn)方:
/**
*第一個(gè)程序員書寫的,實(shí)現(xiàn)這個(gè)接口的類
*/
class Test1 implements InterfaceTest{
/*
* 根據(jù)業(yè)務(wù),重寫方法
*/
public void getName() {
System.out.println("test1");
}
}
/**
*第二個(gè)程序員書寫的,實(shí)現(xiàn)這個(gè)接口的類
*/
class Test2 implements InterfaceTest{
/*
* 根據(jù)業(yè)務(wù),重寫方法
*/
public void getName() {
System.out.println("test2");
}
}
大家可以發(fā)現(xiàn),當(dāng)接口定義好了以后,不但可以規(guī)范代碼,而且可以讓程序員有條不紊的進(jìn)行功能的實(shí)現(xiàn)。實(shí)現(xiàn)接口的程序員根本不用去管,這個(gè)類要被誰(shuí)去調(diào)用。
那么怎么能獲得這些程序員定義的對(duì)象呢?在工廠模式里,單獨(dú)定義一個(gè)工廠類來(lái)實(shí)現(xiàn)對(duì)象的生產(chǎn),注意這里返回的接口對(duì)象。
工廠類,生產(chǎn)接口對(duì)象:
/**
* 本類為工廠類,用于生成接口對(duì)象
*/
class Factory{
//創(chuàng)建私有的靜態(tài)的Properties對(duì)象
private static Properties pro=new Properties();
//靜態(tài)代碼塊
static{
try {
//加載配置文件
pro.load(new FileInputStream("file.txt"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 單例模式,保證該類只有一個(gè)對(duì)象
*/
private static Factory factory=new Factory();
private Factory(){}
public static Factory getFactory(){
return factory;
}
/**
* 本方法為公有方法,用于生產(chǎn)接口對(duì)象
* @return InterfaceTest接口對(duì)象
*/
public InterfaceTest getInterface(){
InterfaceTest interfaceTest=null;//定義接口對(duì)象
try {
//根據(jù)鍵,獲得值,這里的值是類的全路徑
String classInfo=pro.getProperty("test");
//利用反射,生成Class對(duì)象
Class c=Class.forName(classInfo);
//獲得該Class對(duì)象的實(shí)例
Object obj=c.newInstance();
//將Object對(duì)象強(qiáng)轉(zhuǎn)為接口對(duì)象
interfaceTest=(InterfaceTest)obj;
} catch (Exception e) {
e.printStackTrace();
}
//返回接口對(duì)象
return interfaceTest;
}
}
配置文件內(nèi)容:
test=factory.Test2
通過這個(gè)類,大家可以發(fā)現(xiàn),在調(diào)用的時(shí)候,得到的是個(gè)接口對(duì)象。而一個(gè)接口變量可以指向?qū)崿F(xiàn)了這個(gè)接口的類對(duì)象。在利用反射的時(shí)候,我們并沒有直接把類的全路徑寫出來(lái),而是通過鍵獲得值。這樣的話,就有很大的靈活性,只要改變配置文件里的內(nèi)容,就可以改變我們調(diào)用的接口實(shí)現(xiàn)類,而代碼不需做任何改變。在調(diào)用的時(shí)候,我們也是通過接口調(diào)用,甚至我們可以連這個(gè)接口實(shí)現(xiàn)類的名字都不知道。
調(diào)用方:
public class FactoryTest {
public static void main(String[] args) {
//獲得工廠類的實(shí)例
Factory factory=Factory.getFactory();
//調(diào)用獲得接口對(duì)象的方法,獲得接口對(duì)象
InterfaceTest inter=factory.getInterface();
//調(diào)用接口定義的方法
inter.getName();
}
}
上面的代碼就是調(diào)用方法。大家可以發(fā)現(xiàn),在調(diào)用的時(shí)候,我們根本沒有管這個(gè)接口定義的方法要怎么樣去實(shí)現(xiàn)它,我們只知道這個(gè)接口定義這個(gè)方法起什么作用就行了。上面代碼運(yùn)行結(jié)果要根據(jù)配置文件來(lái)定。如果配置文件里的內(nèi)容是test=factory.Test2。那么表示調(diào)用factory.Test2這個(gè)類里實(shí)現(xiàn)接口的方法,這時(shí)候打印“test2”。如果配置文件里的內(nèi)容是test=factory.Test1。那么表示調(diào)用factory.Test1這個(gè)類里實(shí)現(xiàn)接口的方法,這時(shí)候打印“test1”。
反射機(jī)制是框架技術(shù)的原理和核心部分。通過反射機(jī)制我們可以動(dòng)態(tài)的通過改變配置文件(以后是XML文件)的方式來(lái)加載類、調(diào)用類方法,以及使用類屬性。這樣的話,對(duì)于編碼和維護(hù)帶來(lái)相當(dāng)大的便利。在程序進(jìn)行改動(dòng)的時(shí)候,也只會(huì)改動(dòng)相應(yīng)的功能就行了,調(diào)用的方法是不用改的。更不會(huì)一改就改全身。
如對(duì)本文有疑問,請(qǐng)?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會(huì)為你解答!! 點(diǎn)擊進(jìn)入論壇