? ?
反射的常見用法有三類,第一類是“查看”,比如輸入某個(gè)類的屬性方法等信息,第二類是“裝載“,比如裝載指定的類到內(nèi)存里,第三類是“調(diào)用”,比如通過傳入?yún)?shù),調(diào)用指定的方法。
1 查看屬性的修飾符、類型和名字
? ?
通過反射機(jī)制,我們能從.class文件里看到指定類的屬性,比如屬性的修飾符,屬性和類型和屬性的變量名。通過下面的ReflectionReadVar.java,我們看演示下具體的做法。?
??
1 import java.lang.reflect.Field; 2 import java.lang.reflect.Modifier; 3 class
MyValClass{ 4 private int val1; 5 public String val2; 6 final protected String
val3 = "Java";
? ? 我們?cè)诘?行定義了一個(gè)MyValCalss的類,并在第4到第6行里,定義了三個(gè)屬性變量。? ??
8 public class ReflectionReadVar { 9 public static void main(String[] args) {
10 Class<MyValClass> clazz = MyValClass.class; 11 //獲取這個(gè)類的所有屬性 12 Field[]
fields = clazz.getDeclaredFields(); 13 for(Field field : fields) { 14 //輸出修飾符
System.out.print(Modifier.toString(field.getModifiers()) + "\t"); 15 //輸出屬性的類型
16 System.out.print(field.getGenericType().toString() + "\t"); 17 //輸出屬性的名字 18
System.out.println(field.getName()); 19 } 20 } 21 }
? ?
在main函數(shù)的第10行里,通過MyValClass.class,得到了Class<MyValClass>類型的變量clazz,在這個(gè)變量中,存儲(chǔ)了MyValClass這個(gè)類的一些信息。
? ?
在第12行里,通過了clazz.getDeclaredFields()方法得到了MyValClass類里的所有屬性的信息,并把這些屬性的信息存入到Field數(shù)組類型的fields變量里。
? ?
通過了第13行的for循環(huán)依次輸出了這些屬性信息。具體來講,通過第14行的代碼輸出了該屬性的修飾符,通過第16行的代碼輸出了該屬性的類型,通過第18行的代碼輸出了該屬性的變量名。這段代碼的輸出如下,從中我們能看到各屬性的信息。
????????? 1????? private??? int val1
? ? ? ? ? 2????? public class java.lang.String?? val2
????????? 3????? protected final?? class java.lang.String?? val3? ?
2 查看方法的返回類型,參數(shù)和名字
? ? 通過ReflectionReadFunc.java,我們能通過反射機(jī)制看到指定類的方法。? ??
1 import java.lang.reflect.Constructor; 2 import java.lang.reflect.Method; 3
class MyFuncClass{ 4 public MyFuncClass(){} 5 public MyFuncClass(int i){} 6
private void f1(){} 7 protected int f2(int i){return 0;} 8 public String
f2(String s) {return "Java";}
? ??在第3行定義的MyFuncClass這個(gè)類里,我們定義了2個(gè)構(gòu)造函數(shù)和3個(gè)方法。
10 public class ReflectionReadFunc { 11 public static void main(String[] args)
{ 12 Class<MyFuncClass> clazz = MyFuncClass.class; 13 Method[] methods =
clazz.getDeclaredMethods(); 14 for (Method method : methods) 15 {
System.out.println(method); } 16 //得到所有的構(gòu)造函數(shù) 17 Constructor[] c1 =
clazz.getDeclaredConstructors(); 18 //輸出所有的構(gòu)造函數(shù) 19 for(Constructor ct : c1) 20
{ System.out.println(ct); } 21 } 22 }
? ?
在main函數(shù)的第12行,我們同樣是通過了類名.class的方式(也就是MyFuncClass.class的方式)得到了Class<MyFuncClass>類型的clazz對(duì)象。
? ?
在第13行里,是通過了getDeclaredMethods方法得到了MyFuncClass類的所有方法,并在第14行的for循環(huán)里輸出了各方法。在第17行里,是通過了getDeclaredConstructors方法得到了所有的構(gòu)造函數(shù),并通過第19行的循環(huán)輸出。
? ? 本代碼的輸出結(jié)果如下所示,其中第1到第3行輸出的是類的方法,第4和第5行輸出的是類的構(gòu)造函數(shù)。? ??
1 private void MyFuncClass.f1() 2 protected int MyFuncClass.f2(int) 3 public
java.lang.String MyFuncClass.f2(java.lang.String) 4 public MyFuncClass() 5
public MyFuncClass(int)
? ? 不過在實(shí)際的項(xiàng)目里,我們一般不會(huì)僅僅“查看”類的屬性和方法,在更多的情況里,我們是通過反射裝載和調(diào)用類里的方法。
3 通過forName和newInstance方法加載類
? ?
在前文JDBC操作數(shù)據(jù)庫的代碼里,我們看到在創(chuàng)建數(shù)據(jù)庫連接對(duì)象(Connection)之前,需要通過Class.forName("com.mysql.jdbc.Driver");的代碼來裝載數(shù)據(jù)庫(這里是MySQL)的驅(qū)動(dòng)。
? ? 可以說,Class類的forName方法最常見的用法就是裝載數(shù)據(jù)庫的驅(qū)動(dòng),以至于不少人會(huì)錯(cuò)誤地認(rèn)為這個(gè)方法的作用是“裝載類”。
? ?
其實(shí)forName方法的作用僅僅是返回一個(gè)Class類型的對(duì)象,它一般會(huì)和newInstance方法配套使用,而newInstance方法的作用才是加載類。
? ? 通過下面的ForClassDemo.java這段代碼,我們來看下綜合使用forName和newInstance這兩個(gè)方法加載對(duì)象的方式。? ??
1 class MyClass{ 2 public void print() 3 { System.out.println("Java"); } 4 } 5
public class ForClassDemo { 6 public static void main(String[] args) { 7
//通過new創(chuàng)建類和使用類的方式 8 MyClass myClassObj = new MyClass(); 9
myClassObj.print();//輸出是Java 10 //通過forName和newInstance加載類的方式 11 try { 12
Class<?> clazz = Class.forName("MyClass"); 13 MyClass myClass =
(MyClass)clazz.newInstance(); 14 myClass.print();//輸出是Java 15 } catch
(ClassNotFoundException e) { 16 e.printStackTrace(); 17 } catch
(InstantiationException e) { 18 e.printStackTrace(); 19 } catch
(IllegalAccessException e) { 20 e.printStackTrace(); 21 } 22 } 23 }
? ? 在第1行定義的MyClass這個(gè)類里,我們?cè)谄渲械牡?行定義了一個(gè)print方法。
? ? Main函數(shù)的第8和第9行里,我們演示了通過常規(guī)new的方式創(chuàng)建和使用類的方式,通過第9行,我們能輸出“Java”這個(gè)字符串。
? ?
在第12行,我們通過Class.forName("MyClass")方法返回了一個(gè)Class類型的對(duì)象,請(qǐng)注意,forName方法的作用不是“加載MyClass類”,而是返回一個(gè)包含MyClass信息的Class類型的對(duì)象。這里我們是通過第13行的newInstance方法,加載了一個(gè)MyClass類型的對(duì)象,并在第14行調(diào)用了其中的print方法。
? ?
既然forName方法的作用僅僅是“返回Class類型的對(duì)象”,那么在JDBC部分的代碼里,為什么我們能通過Class.forName("com.mysql.jdbc.Driver");代碼來裝載MySQL的驅(qū)動(dòng)呢?在MySQL的com.mysql.jdbc.Driver驅(qū)動(dòng)類中有如下的一段靜態(tài)初始化代碼。?
?
1 static { 2 try { 3 java.sql.DriverManager.registerDriver(new Driver()); 4 }
catch (SQLException e) { 5 throw new RuntimeException(“Can’t register
driver!”); 6 } 7 }
? ?
也就是說,當(dāng)我們調(diào)用Class.forName方法后,會(huì)通過執(zhí)行這段代碼會(huì)新建一個(gè)Driver的對(duì)象,并調(diào)用第3行的DriverManager.registerDriver把剛創(chuàng)建的Driver對(duì)象注冊(cè)到DriverManager里。
? ? 在上述的代碼里,我們看到了除了new之外,我們還能通過newInstance來創(chuàng)建對(duì)象。
? ?
其實(shí)這里說“創(chuàng)建”并不準(zhǔn)確,雖然說通過new和newInstance我們都能得到一個(gè)可用的對(duì)象,但newInstance的作用其實(shí)是通過Java虛擬機(jī)的類加載機(jī)制把指定的類加載到內(nèi)存里。
? ?
我們?cè)诠S模式中,經(jīng)常會(huì)通過newInstance方法來加載類,但這個(gè)方法只能是通過調(diào)用類的無參構(gòu)造函數(shù)來加載類,如果我們?cè)趧?chuàng)建對(duì)象時(shí)需要傳入?yún)?shù),那么就得使用new來調(diào)用對(duì)應(yīng)的帶參的構(gòu)造函數(shù)了。
4 通過反射機(jī)制調(diào)用類的方法
? ? 如果我們通過反射機(jī)制來調(diào)用類的方式,那么就得解決三個(gè)問題,第一,通過什么方式來調(diào)?第二,如何傳入?yún)?shù),第三,如何得到返回結(jié)果?
? ? 通過下面的CallFuncDemo.java代碼,我們將通過反射來調(diào)用類里的方法,在其中我們能看下上述三個(gè)問題的解決方法。? ??
1 import java.lang.reflect.Constructor; 2 import
java.lang.reflect.InvocationTargetException; 3 import java.lang.reflect.Method;
4 class Person { 5 private String name; 6 public Person(String name) 7
{this.name = name;} 8 public void saySkill(String skill) { 9
System.out.println("Name is:"+name+",skill is:" + skill); 10 } 11 public int
addSalary(int current) 12 { return current + 100;} 13 }
? ?
在第4行里,我們定義了一個(gè)Person類,在其中的第6行里,我們定義了一個(gè)帶參的構(gòu)造函數(shù),在第8行里,我們定義了一個(gè)帶參但無返回值得saySkill方法,在第11行里,我們定義了一個(gè)帶參而且返回int類型的addSalary方法。?
??
14 public class CallFuncDemo { 15 public static void main(String[] args) { 16
Class c1azz = null; 17 Constructor c = null; 18 try { 19 c1azz =
Class.forName("Person"); 20 c = c1azz.getDeclaredConstructor(String.class); 21
Person p = (Person)c.newInstance("Peter"); 22 //output: Name is:Peter, skill
is:java 23 p.saySkill("Java"); 24 // 調(diào)用方法,必須傳遞對(duì)象實(shí)例,同時(shí)傳遞參數(shù)值 25 Method method1 =
c1azz.getMethod("saySkill", String.class); 26 //因?yàn)闆]返回值,所以能直接調(diào) 27 //輸出結(jié)果是Name
is:Peter, skill is:C# 28 method1.invoke(p, "C#"); 29 Method method2 =
c1azz.getMethod("addSalary", int.class); 30 Object invoke = method2.invoke(p,
100); 31 //輸出200 32 System.out.println(invoke); 33 } catch
(ClassNotFoundException e) { 34 e.printStackTrace(); 35 } catch
(NoSuchMethodException e1) { 36 e1.printStackTrace(); 37 } catch
(InstantiationException e) { 38 e.printStackTrace(); 39 } catch
(IllegalAccessException e) { 40 e.printStackTrace(); 41 } catch
(InvocationTargetException e) { 42 e.printStackTrace(); 43 } 44 } 45 }
? ?
在第19行里,我們通過Class.forName得到了一個(gè)Class類型的對(duì)象,其中包含了Person類的信息。在第20行里,通過傳入String.class參數(shù),得到了Person類的帶參的構(gòu)造函數(shù),并通過了第21行的newInstance方法,通過這個(gè)帶參的構(gòu)造函數(shù)創(chuàng)建了一個(gè)Person類型的對(duì)象。隨后在第23行里調(diào)用了saySkill方法。這里我們演示通過反射調(diào)用類的構(gòu)造函數(shù)來創(chuàng)建對(duì)象的方式。
? ?
在第25行里,我們通過了getMethod方法,得到了帶參的saySkill方法的Method類型的對(duì)象,隨后通過第28行的invoke方法調(diào)用了這個(gè)saySkill方法,這里第一個(gè)參數(shù)是由哪個(gè)對(duì)象來調(diào)用,通過第二個(gè)參數(shù),我們傳入了saySkill方法的String類型的參數(shù)。
? ?
用同樣的方式,我們?cè)诘?9和30行通過反射調(diào)用了Person類的addSalary方法,由于這個(gè)方法有返回值,所以我們?cè)?0行用了一個(gè)Object類型的invoke對(duì)象來接收返回值,通過第32行的打印語句,我們能看到200這個(gè)執(zhí)行結(jié)果。
?
熱門工具 換一換