那麼什麼是Java的反射呢?java
你們都知道,要讓Java程序可以運行,那麼就得讓Java類要被Java虛擬機加載。Java類若是不被Java虛擬機加載,是不能正常運行的。如今咱們運行的全部的程序都是在編譯期的時候就已經知道了你所須要的那個類的已經被加載了。程序員
Java的反射機制是在編譯並不肯定是哪一個類被加載了,而是在程序運行的時候才加載、探知、自審。使用在編譯期並不知道的類。這樣的特色就是反射。數據庫
那麼Java反射有什麼做用呢?設計模式
假如咱們有兩個程序員,一個程序員在寫程序的時候,須要使用第二個程序員所寫的類,但第二個程序員並沒完成他所寫的類。那麼第一個程序員的代碼可否經過編譯呢?這是不能經過編譯的。利用Java反射的機制,就可讓第一個程序員在沒有獲得第二個程序員所寫的類的時候,來完成自身代碼的編譯。數組
Java的反射機制它知道類的基本結構,這種對Java類結構探知的能力,咱們稱爲Java類的「自審」。你們都用過Jcreator和eclipse。當咱們構建出一個對象的時候,去調用該對象的方法和屬性的時候。一按點,編譯工具就會自動的把該對象可以使用的全部的方法和屬性所有都列出來,供用戶進行選擇。這就是利用了Java反射的原理,是對咱們建立對象的探知、自審。框架
Class類eclipse
要正確使用Java反射機制就得使用java.lang.Class這個類。它是Java反射機制的起源。當一個類被加載之後,Java虛擬機就會自動產生一個Class對象。經過這個Class對象咱們就能得到加載到虛擬機當中這個Class對象對應的方法、成員以及構造方法的聲明和定義等信息。工具
反射API性能
u反射API用於反應在當前Java虛擬機中的類、接口或者對象信息測試
u功能
—獲取一個對象的類信息.
—獲取一個類的訪問修飾符、成員、方法、構造方法以及超類的信息.
—檢獲屬於一個接口的常量和方法聲明.
—建立一個直到程序運行期間才知道名字的類的實例.
—獲取並設置一個對象的成員,甚至這個成員的名字是
在程序運行期間才知道.
—檢測一個在運行期間才知道名字的對象的方法
利用Java反射機制咱們能夠很靈活的對已經加載到Java虛擬機當中的類信息進行檢測。固然這種檢測在對運行的性能上會有些減弱,因此何時使用反射,就要靠業務的需求、大小,以及經驗的積累來決定。
那麼如何利用反射API在運行的時候知道一個類的信息呢?
public class MyTest {
/**
*構造方法
*/
public MyTest(){
String classInfo=JOptionPane.showInputDialog(null,"輸入類全路徑");//要求用戶輸入類的全路徑
try {
Class cla=Class.forName(classInfo);//根據類的全路徑進行類加載,返回該類的Class對象
Method[] method=cla.getDeclaredMethods();//利用獲得的Class對象的自審,返回方法對象集合
for(Method me:method){//遍歷該類方法的集合
System.out.println(me.toString());//打印方法信息
}
System.out.println("********");
Field[] field=cla.getDeclaredFields();//利用獲得的Class對象的自審,返回屬性對象集合
for(Field me:field){ //遍歷該類屬性的集合
System.out.println(me.toString());//打印屬性信息
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new MyTest();
}
}
經過上面代碼,你們能夠知道編譯工具爲何可以一按點就能列出用戶當前對象的屬性和方法了。它是先得到用戶輸入對象的字符串,而後利用反射原理來對這樣的類進行自審,從而列出該類的方法和屬性。
使用反射機制的步驟:
導入java.lang.relfect 包
遵循三個步驟
第一步是得到你想操做的類的 java.lang.Class 對象
第二步是調用諸如 getDeclaredMethods 的方法
第三步使用 反射API 來操做這些信息
得到Class對象的方法
若是一個類的實例已經獲得,你可使用
【Class c = 對象名.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若是類名在編譯期不知道, 可是在運行期能夠得到, 你可使用下面的方法
Class c = Class.forName(strg);
例子一:
public class TestOne {
static{
System.out.println("靜態代碼塊運行");
}
TestOne(){
System.out.println("構造方法");
}
public static void main(String[]args) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class class1=Class.forName("reflect.TestOne");//輸出:靜態代碼塊運行
/*其實是對com.TestOne進行類加載,這時候,會把靜態屬性、方法以及靜態代碼塊都加載到內存中。因此這時候會打印出"靜態代碼塊運行"。
但這時候,對象卻尚未產生。因此"構造方法"這幾個字不會打印。當執行cla.newInstance()的時候,就是利用反射機制將Class對象生成一個該類的一個實例。
*/
Object object=class1.newInstance();//輸出:構造方法
//當執行cla.newInstance()的時候,就是利用反射機制將Class對象生成一個該類的一個實例。這時候對象就產生了。因此打印"構造方法"。
TestOne two=new TestOne();//輸出:構造方法
//當執行到TestOne two=new TestOne()語句時,又生成了一個對象。但這時候類已經加載完畢,靜態的東西已經加載到內存中,而靜態代碼塊只執行一次,因此不用再去加載類,因此只會打印"構造方法",而"靜態代碼塊運行"不會打印。
//反射機制不但能夠例出該類對象所擁有的方法和屬性,還能夠得到該類的構造方法及經過構造方法得到實例。也能夠動態的調用這個實例的成員方法。
}
}
反射機制不但能夠例出該類對象所擁有的方法和屬性,還能夠得到該類的構造方法及經過構造方法得到實例。也能夠動態的調用這個實例的成員方法。
例子二:
public class ConstructorTest {
public static void main(String[] args) {
try {
//得到指定字符串類對象
Class cla=Class.forName("reflect.Tests");
//設置Class對象數組,用於指定構造方法類型
Class[] cl=new Class[]{int.class,int.class};
//得到Constructor構造器對象。並指定構造方法類型
Constructor con=cla.getConstructor(cl);
//給傳入參數賦初值
Object[] x={new Integer(33),new Integer(67)};
//獲得實例
Object obj=con.newInstance(x);
/*運行的結果是」 33 67」。說明咱們已經生成了Tests這個類的一個對象。
一樣,也能夠經過反射模式,來執行Java類的方法*/
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Tests{
public Tests(int x,int y){
System.out.println(x+" "+y);
}
}
運行的結果是」 33 67」。說明咱們已經生成了Tests這個類的一個對象。
一樣,也能夠經過反射模式,來執行Java類的方法,構造器能夠調用有參數的構造方法。
例子三:
/** * 本類測試反射得到類的方法對象,
* 並經過類對象和類方法對象,運行該方法
*
*/
public class MethodTest {
public static void main(String[] args) {
try {
//得到窗體類的Class對象
Class cla=Class.forName("javax.swing.JFrame");
//生成窗體類的實例
Object obj=cla.newInstance();
//得到窗體類的setSize方法對象,並指定該方法參數類型爲int,int
Method methodSize=cla.getMethod("setSize", new Class[]{int.class,int.class});
/*
* 執行setSize()方法,並傳入一個Object[]數組對象,
* 做爲該方法參數,等同於 窗體對象.setSize(300,300);
*/
methodSize.invoke(obj, new Object[]{new Integer(300),new Integer(300)});
//得到窗體類的setSize方法對象,並指定該方法參數類型爲boolean
Method methodVisible=cla.getMethod("setVisible", new Class[]{boolean.class});
/*
* 執行setVisible()方法,並傳入一個Object[]數組對象, *做爲該方法參數。 等同於 窗體對象.setVisible(true);
*/
methodVisible.invoke(obj, new Object[]{new Boolean(true)});
} catch (Exception e) {
e.printStackTrace();
}
}
}
反射技術大量用於Java設計模式和框架技術,最多見的設計模式就是工廠模式(Factory)和單例模式(Singleton)。
單例模式(Singleton)
/*單例模式(Singleton)
這個模式主要做用是保證在Java應用程序中,一個類Class只有一個實例存在。在不少操做中,好比創建目錄 數據庫鏈接都須要這樣的單線程操做。這樣作就是爲了節省內存空間,保證咱們所訪問到的都是同一個對象。
單例模式要求保證惟一,那麼怎麼樣才能保證惟一性呢?對了,這就是靜態變量。單例模式有如下兩種形式:
第一種形式:*/
public class SingletonHungry {
/*注意這是private私有的構造方法, 只供內部調用
* 外部不能經過new的方式來生成該類的實例
*/
private SingletonHungry() {
}
/* 在本身內部定義本身一個實例,是否是很奇怪?
* 定義一個靜態的實例,保證其惟一性
*/
private static SingletonHungry instance = new SingletonHungry();
// 這裏提供了一個供外部訪問本class的靜態方法,能夠直接訪問
public static SingletonHungry getInstance() {
return instance;
}
public static void main(String[] args){
//這樣的調用不被容許,由於構造方法是私有的。
//Singleton x=new Singleton();
//獲得一個Singleton類實例
SingletonHungry x=SingletonHungry.getInstance();
//獲得另外一個Singleton類實例
SingletonHungry y=SingletonHungry.getInstance();
//比較x和y的地址,結果爲true。說明兩次得到的是同一個對象
System.out.println(x==y);
}
}
public class SingletonLazy {
//先申明該類靜態對象
private static volatile SingletonLazy inSingletonLazy=null;
private SingletonLazy(){}
//建立一個靜態訪問器,得到該類實例。加上同步,表示防止兩個線程同時進行對象的建立
public static SingletonLazy getSingletonLazy(){
if(null==inSingletonLazy){
synchronized (SingletonLazy.class) {
if(null==inSingletonLazy){
inSingletonLazy=new SingletonLazy();
}
}
}
return inSingletonLazy;
}
}
工廠模式(Factory)
工廠模式是咱們最經常使用的模式了,著名的Jive論壇 ,就大量使用了工廠模式,工廠模式在Java程序系統能夠說是隨處可見。
爲何工廠模式是如此經常使用?是由於工廠模式利用Java反射機制和Java多態的特性可讓咱們的程序更加具備靈活性。用工廠模式進行大型項目的開發,能夠很好的進行項目並行開發。就是一個程序員和另外一個程序員能夠同時去書寫代碼,而不是一個程序員等到另外一個程序員寫完之後再去書寫代碼。其中的粘合劑就是接口和配置文件。
以前說利用接口能夠將調用和實現相分離。
那麼這是怎麼樣去實現的呢?工廠模式能夠爲咱們解答。
咱們先來回顧一下軟件的生命週期,分析、設計、編碼、調試與測試。其中分析就是指需求分析,就是知道這個軟件要作成什麼樣子,要實現什麼樣的功能。功能知道了,這時就要設計了。設計的時候要考慮到怎麼樣高效的實現這個項目,若是讓一個項目團隊並行開發。這時候,一般先設計接口,把接口給實現接口的程序員和調用接口的程序員,在編碼的時候,兩個程序員能夠互不影響的實現相應的功能,最後經過配置文件進行整合。
代碼示例:
/**
*
*定義接口
*/
interface InterfaceTest{
public void getName();//定義得到名字的方法
}
接口有了,那麼獲得這個接口,進行實現編碼的程序員應該怎麼作呢?對了,實現這個接口,重寫其中定義的方法
接口實現方:
/**
*第一個程序員書寫的,實現這個接口的類
*/
class Test1 implements InterfaceTest{
/*
* 根據業務,重寫方法
*/
public void getName() {
System.out.println("test1");
}
}
/**
*第二個程序員書寫的,實現這個接口的類
*/
class Test2 implements InterfaceTest{
/*
* 根據業務,重寫方法
*/
public void getName() {
System.out.println("test2");
}
}
你們能夠發現,當接口定義好了之後,不但能夠規範代碼,並且可讓程序員有條不紊的進行功能的實現。實現接口的程序員根本不用去管,這個類要被誰去調用。
那麼怎麼能得到這些程序員定義的對象呢?在工廠模式裏,單獨定義一個工廠類來實現對象的生產,注意這裏返回的接口對象。
工廠類,生產接口對象:
/**
* 本類爲工廠類,用於生成接口對象
*/
class Factory{
//建立私有的靜態的Properties對象
private static Properties pro=new Properties();
//靜態代碼塊
static{
try {
//加載配置文件
pro.load(new FileInputStream("file.txt"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 單例模式,保證該類只有一個對象
*/
private static Factory factory=new Factory();
private Factory(){}
public static Factory getFactory(){
return factory;
}
/**
* 本方法爲公有方法,用於生產接口對象
* @return InterfaceTest接口對象
*/
public InterfaceTest getInterface(){
InterfaceTest interfaceTest=null;//定義接口對象
try {
//根據鍵,得到值,這裏的值是類的全路徑
String classInfo=pro.getProperty("test");
//利用反射,生成Class對象
Class c=Class.forName(classInfo);
//得到該Class對象的實例
Object obj=c.newInstance();
//將Object對象強轉爲接口對象
interfaceTest=(InterfaceTest)obj;
} catch (Exception e) {
e.printStackTrace();
}
//返回接口對象
return interfaceTest;
}
}
配置文件內容:
test=factory.Test2
經過這個類,你們能夠發現,在調用的時候,獲得的是個接口對象。而一個接口變量能夠指向實現了這個接口的類對象。在利用反射的時候,咱們並無直接把類的全路徑寫出來,而是經過鍵得到值。這樣的話,就有很大的靈活性,只要改變配置文件裏的內容,就能夠改變咱們調用的接口實現類,而代碼不需作任何改變。在調用的時候,咱們也是經過接口調用,甚至咱們能夠連這個接口實現類的名字都不知道。
調用方:
public class FactoryTest {
public static void main(String[] args) {
//得到工廠類的實例
Factory factory=Factory.getFactory();
//調用得到接口對象的方法,得到接口對象
InterfaceTest inter=factory.getInterface();
//調用接口定義的方法
inter.getName();
}
}
上面的代碼就是調用方法。你們能夠發現,在調用的時候,咱們根本沒有管這個接口定義的方法要怎麼樣去實現它,咱們只知道這個接口定義這個方法起什麼做用就好了。上面代碼運行結果要根據配置文件來定。若是配置文件裏的內容是test=factory.Test2。那麼表示調用factory.Test2這個類裏實現接口的方法,這時候打印「test2」。若是配置文件裏的內容是test=factory.Test1。那麼表示調用factory.Test1這個類裏實現接口的方法,這時候打印「test1」。
反射機制是框架技術的原理和核心部分。經過反射機制咱們能夠動態的經過改變配置文件(之後是XML文件)的方式來加載類、調用類方法,以及使用類屬性。這樣的話,對於編碼和維護帶來至關大的便利。在程序進行改動的時候,也只會改動相應的功能就好了,調用的方法是不用改的。更不會一改就改全身。