1.java.lang.Class 類
萬(wàn)事萬(wàn)物都是對(duì)象胚泌。我們平常接觸到的類绣夺,本身也是一種對(duì)象,它的類型是 Class薛耻,也可以說(shuō) Class 是類的類型输钩,即類類型 (Class Type)豺型;任何一個(gè)類,都是 java.lang.Class 的一個(gè)實(shí)例對(duì)象买乃。
Class 是 Java 的基本類之一姻氨,也是反射機(jī)制的基礎(chǔ),它的意義是類的抽象剪验,即對(duì)“類”進(jìn)行描述肴焊。比如獲得類的屬性的方法 getField,有獲得該類的所有方法功戚、所有公有方法的方法 getMethods, getDeclaredMethods娶眷。同時(shí),Class 也是 Java 類型中最重要的一種啸臀,表示原始類型(引用類型)及基本類型茂浮。
(1) 如何表示這個(gè)實(shí)例對(duì)象?
- 第一種:類名.Class
- 第二種:對(duì)象.getClass()
- 第三種:Class.forName("類的全稱");
在編譯時(shí)刻加載類壳咕,稱為靜態(tài)加載類席揽,比如通過(guò) new 關(guān)鍵字加載的類。
在運(yùn)行時(shí)刻加載類谓厘,稱為動(dòng)態(tài)加載類幌羞,Class.forName() 方法就是 Java 語(yǔ)言中唯一一種動(dòng)態(tài)加載的方法。動(dòng)態(tài)加載類在編譯不會(huì)報(bào)錯(cuò)竟稳,在運(yùn)行時(shí)才會(huì)加載属桦,使用接口標(biāo)準(zhǔn)能更方便動(dòng)態(tài)加載類的實(shí)現(xiàn)。所以功能性的類盡量使用動(dòng)態(tài)加載他爸,而不用靜態(tài)加載聂宾。
有了類的類類型,就可以獲取類的所有信息诊笤;具體如下例所示:
例:寫一個(gè)方法系谐,接受一個(gè)對(duì)象,然后打印該對(duì)象所屬類的所有信息讨跟;
package homework4_27;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class homework1 {
public static void main(String[] args) {
Object o = new Object();
printClassMsg("hello");
}
// 寫一個(gè)方法纪他,接受一個(gè)對(duì)象,然后打印該對(duì)象所屬類的所有信息晾匠; (Reflect.Demo2.java)
// 打印所有方法的返回值類型茶袒、方法名,參數(shù)類型列表
// 得到所有的聲明的成員變量凉馆,打印成員變量的類型薪寓,名稱
public static void printClassMsg(Object obj){
// 獲取類類型
Class c = obj.getClass();
System.out.println("類類型名稱"+c.getName());
// 獲取方法列表,包括方法名澜共,返回值類型向叉,參數(shù)列表
System.out.println("======================");
// getMethods(): 獲取所有公有的方法;
Method[] ms = c.getMethods();
System.out.println(c.getName() + " 的類方法:");
for(Method m : ms) {
System.out.println("------------");
System.out.println(" 類方法名:" + c.getName() + "." + m.getName());
System.out.println(" 返回值類型:" + m.getReturnType().getName());
System.out.println(" 參數(shù)類型:");
Class[] c_params = m.getParameterTypes();
for(Class param : c_params) {
System.out.println(" " + param.getName());
}
}
// 獲取類中已經(jīng)聲明的成員變量
System.out.println("======================");
// 獲取所有聲明的成員變量
// c.getFields();
// 獲取該類的所有成員變量
Field[] f = c.getDeclaredFields();
System.out.println(c.getName() + " 的成員變量:");
for(Field fie : f){
System.out.println("------------");
System.out.println(" 變量類型:" + fie.getType().getName());
System.out.println(" 變量名稱:" + fie.getName());
}
}
}
(2) 框架的原理
Java 框架就是一些類和接口的集合咳胃,通過(guò)這些類和接口協(xié)調(diào)來(lái)完成一系列的程序?qū)崿F(xiàn)植康。框架又叫做開發(fā)中的半成品展懈,它通過(guò)了編譯销睁,打成了 jar 包,但不能提供整個(gè) WEB 應(yīng)用程序的所有東西存崖。但是有了框架冻记,我們就可以集中精力進(jìn)行業(yè)務(wù)邏輯的開發(fā),同時(shí)框架會(huì)創(chuàng)建我們寫的類的實(shí)例對(duì)象来惧,并調(diào)用我們寫的方法冗栗;這樣我們就不用去關(guān)心它的技術(shù)實(shí)現(xiàn)以及一些輔助的業(yè)務(wù)邏輯。
說(shuō)白了 Java 框架就是封裝好方便程序員操作的類,在運(yùn)行時(shí)動(dòng)態(tài)加載我們的類(通過(guò) Class.forName(類名) 方法)隅居,并創(chuàng)建對(duì)象調(diào)用方法钠至。這樣可以使項(xiàng)目的開發(fā)更簡(jiǎn)單,維護(hù)起來(lái)也更容易胎源。
2. Java 的反射機(jī)制
參考網(wǎng)址:
《Java基礎(chǔ)之—反射(非常重要)》
《Java源碼解析(2) —— Class(1)》
前面提到框架是開發(fā)中的半成品棉钧,它可以在運(yùn)行過(guò)程中加載實(shí)例,并填充業(yè)務(wù)涕蚤。在框架中宪卿,反射是框架設(shè)計(jì)的靈魂。
Java 反射機(jī)制是在運(yùn)行狀態(tài)中進(jìn)行的万栅,它把 Java 類中的各種成分映射成一個(gè)個(gè)的 Java 對(duì)象佑钾。使用反射的前提條件,是首先必須得到字節(jié)碼的 Class烦粒,它用于表示 .class 文件休溶。通過(guò)動(dòng)態(tài)獲取信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為 Java 語(yǔ)言的反射機(jī)制。具體對(duì)于類和對(duì)象:
- 任意一個(gè)類撒遣,都能夠知道這個(gè)類的所有屬性和方法邮偎;
- 任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性义黎;
要想解剖一個(gè)類禾进,必須先要獲取到該類的字節(jié)碼文件對(duì)象。而解剖使用的就是 Class 類中的方法廉涕。所以先要獲取到每一個(gè)字節(jié)碼文件 *.class 對(duì)應(yīng)的 Class 類型的對(duì)象泻云。在 《Java基礎(chǔ)之—反射(非常重要)》 一文中繪制了反射加載的過(guò)程:
用好反射,關(guān)鍵在于能夠調(diào)用任意類或?qū)ο蟮姆椒ê蛯傩院伞N覀兛梢酝ㄟ^(guò) java.lang.reflect.Method 調(diào)用任意的方法宠纯,通過(guò) java.lang.reflect.Field 調(diào)用任意的屬性。
(1) java.lang.reflect.Method 方法的反射
- 獲取一個(gè)方法對(duì)象
- 方法名稱
- 參數(shù)列表(的類型的類類型)
- 如何用方法的反射操作方法层释?
- method.invoke(target, 參數(shù)列表)
方法本身也可以用對(duì)象的形式表現(xiàn)出來(lái)婆瓜,它被封裝在 java.lang.reflect.Method 中。調(diào)用一個(gè)方法對(duì)象,關(guān)鍵在于兩部分:
- 方法簽名
- 參數(shù)列表的類型的類類型
舉例說(shuō)明:假設(shè)已經(jīng)定義了方法 methodFunction(int a, double b, boolean c),對(duì)于這個(gè)方法进统,methodFunction 是它的方法簽名,(int.class, double.class, boolean.class) 是它的參數(shù)列表的類型的類類型猴蹂。
簡(jiǎn)要介紹幾種不同的獲取成員方法的方式:
- 批量獲取成員方法:
- public Method[] getMethods(): 獲取所有"公有方法";該方法包含了父類的方法楣嘁,也包含 Object 類磅轻;
- public Method[] getDeclaredMethods(): 獲取所有的成員方法珍逸,包括私有的,但不包括繼承的聋溜;
- 獲取單個(gè)成員方法:
-
public Method getMethod(String name,Class<?>... parameterTypes):
- 參數(shù):
- String name: 方法名谆膳;
- Class<?>...parameterTypes: 形參的Class類型對(duì)象
- 參數(shù):
- public Method getDeclaredMethod(String name,Class<?>... parameterTypes):
-
public Method getMethod(String name,Class<?>... parameterTypes):
方法的調(diào)用是通過(guò) Method 類的 invoke 方法實(shí)現(xiàn)的。它的定義為:
public Object invoke(Object obj,Object... args) {...}
參數(shù)說(shuō)明:
- Object obj: 要調(diào)用方法的對(duì)象勤婚;
- Object... args: 調(diào)用方式時(shí)所傳遞的實(shí)參摹量;
關(guān)于方法的反射,例程如下:
例:寫三個(gè)參數(shù)列表不同的 f 方法馒胆,獲得其方法的反射:
package reflect;
import java.lang.reflect.Method;
class A{
public void f(){
System.out.println("helloworld");
}
public int f(int a ,int b){
return a+b;
}
public String f(String a,String b,int c){
return a+","+b+","+c;
}
}
public class Demo3 {
public static void main(String[] args) {
A a1 = new A();
Class c = a1.getClass();
try {
// 獲取名為 f 的方法,參數(shù)列表的類型分別為 (int, int)
Method method1 = c.getMethod("f",int.class,int.class);
System.out.println(a1.f(10, 10));
// 傳入?yún)?shù) (10, 10)凝果,輸出 f(int, int) 的計(jì)算結(jié)果
int n = (Integer)method1.invoke(a1, 10,10);
System.out.println(n);
// 獲取無(wú)參數(shù)的 f 方法
System.out.println("================");
Method method2 = c.getMethod("f");
a1.f();
method2.invoke(a1);
// 獲取參數(shù)列表為 (String, String, int) 類型的 f 方法
System.out.println("================");
Method method3 = c.getMethod("f", String.class,String.class,int.class);
System.out.println(a1.f("hello", "world",100));
String ss = (String)method3.invoke(a1, "hello","world",100);
System.out.println(ss);
} catch (Exception e) {
e.printStackTrace();
}
}
}
該例程中首先定義了一個(gè)類 A祝迂,其中定義了幾個(gè)名稱為 f 的方法,都有不同的參數(shù)列表和返回值器净。在 getMethod 方法中傳入了不同的參數(shù)列表型雳,就可以準(zhǔn)確的獲取我們想要的具體的 f 方法。
(2) java.lang.reflect.Field 成員變量的反射
Class 類中重要的方法:
- Field[] getDeclaredFields(): 返回 Field 對(duì)象的一個(gè)數(shù)組山害,這些對(duì)象反映此 Class 對(duì)象所表示的類或接口所聲明的所有字段纠俭。
- Field getDeclaredField(String name): 返回一個(gè) Field 對(duì)象,該對(duì)象反映此 Class 對(duì)象所表示的類或接口的指定已聲明字段浪慌。
Field 類中重要的方法:
- Class<?> getType(): 返回一個(gè) Class 對(duì)象冤荆,它標(biāo)識(shí)了此 Field 對(duì)象所表示字段的聲明類型。
- void setAccessible(boolean flag): 將此對(duì)象的 accessible 標(biāo)志設(shè)置為指示的布爾值权纤。
下面用例程說(shuō)明:
- 獲取一個(gè)成員變量對(duì)象
- 如何進(jìn)行成員變量的反射操作
- field.get(target);
- field.set(target, newValue);
例:
現(xiàn)在有類 User.java 如下:
package reflect;
public class User {
private String name;
private int age;
public User() {}
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
寫一個(gè)方法钓简,接受一個(gè)對(duì)象:
- 如果該對(duì)象有字符串屬性,把其值改成大寫汹想;
- 如果該對(duì)象有 int 屬性外邓,把值都加 100;
package reflect;
import java.lang.reflect.Field;
public class Demo6 {
public static void main(String[] args) {
User user = new User("zhangsan", 20);
try {
changeValue(user);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(user);
}
public static void changeValue(Object obj) throws Exception{
Class c = obj.getClass();
// 獲取成員變量數(shù)組
Field[] fs = c.getDeclaredFields();
for (Field field : fs) {
// 當(dāng)前類型為 String
if(field.getType() == String.class){
// java 中古掏,在類的外面獲取此類的私有成員變量的值時(shí)损话,需要先用 setAccessible(true) 對(duì)變量進(jìn)行權(quán)限更改設(shè)定;
field.setAccessible(true);
// 獲取 String field 的值槽唾,并將其轉(zhuǎn)為大寫丧枪;
String oldValue = (String)field.get(obj);
field.set(obj, oldValue.toUpperCase());
}
// 當(dāng)前類型為 int
if(field.getType()==int.class){
field.setAccessible(true);
// 獲取 int 類型 field 的值,并將其加 100
int oldValue = field.getInt(obj);
field.set(obj, oldValue+100);
}
}
}
}
(3) java.lang.reflect.Constructor 構(gòu)造函數(shù)的反射
- constructor.getConstructor:獲取某個(gè)構(gòu)造函數(shù)
- constructor.newInstance(參數(shù)):構(gòu)建函數(shù)傳入?yún)?shù)的反射操作
Class 類關(guān)于構(gòu)造函數(shù)的重要方法:
- Constructor<?> getConstructor(Class<?>... parameterTypes): 返回一個(gè) Constructor 對(duì)象夏漱,它反映此 Class 對(duì)象所表示的類的指定公共構(gòu)造方法豪诲;
- Constuctor<?>[] getDeclaredConstructors(): 返回 Constructor 對(duì)象的一個(gè)數(shù)組,這些對(duì)象反映此 Class 對(duì)象表示的類聲明的所有構(gòu)造方法挂绰;
- T newInstance(): 創(chuàng)建此 Class 對(duì)象所表示的類的一個(gè)新實(shí)例屎篱。
下面依舊用上面的中的 User.java 舉例說(shuō)明:
package reflect;
import java.lang.reflect.Constructor;
public class Demo7 {
public static void main(String[] args) {
Class c = User.class;
try {
// 獲取 User 類的無(wú)參構(gòu)造函數(shù)
Constructor<User> cons = c.getConstructor();
// 構(gòu)造無(wú)參的 User 實(shí)例對(duì)象
User u = cons.newInstance();
System.out.println(u);
// 獲取 User(String, int) 的 User 構(gòu)造函數(shù)
Constructor<User> cons2 = c.getConstructor(String.class,int.class);
// 構(gòu)造 User(String, int) 的實(shí)例對(duì)象
User u2 = cons2.newInstance("lisi",20);
System.out.println(u2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
5. 數(shù)組的反射
前面說(shuō)過(guò) Java 中萬(wàn)事萬(wàn)物都是對(duì)象服赎,其中也包含數(shù)組。數(shù)組也是實(shí)例對(duì)象交播,而且數(shù)組的類類型只和類型和維數(shù)相關(guān)重虑。數(shù)組在反射機(jī)制中的類是 java.lang.reflect.Array。
依舊有上例中的 User.java秦士,下面用例程說(shuō)明:
public class Demo8 {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
int[] b = {5,6,7,8};
int[][] c = {
{1,2,3},
{4,5,6,7}
};
String[][] str = {
{"aaa","bbb"},
{"ccc","ddd","eee"}
};
Class c1 = a.getClass();
Class c2 = b.getClass();
Class c3 = c.getClass();
System.out.println(c1==c2); // true
System.out.println(c1==int[].class); // true
System.out.println(c1.getName()+","+c2.getName()); // [I,[I
System.out.println(str.getClass().getName()); // [[Ljava.lang.String;
System.out.println(str.getClass()==String[][].class); // true
}
}
輸出結(jié)果為:
true
true
[I,[I
[[Ljava.lang.String;
true
當(dāng)然通過(guò)反射的方法來(lái)創(chuàng)建數(shù)組的用法是很少見的缺厉,其實(shí)也是多余的。為什么不直接通過(guò) new 來(lái)創(chuàng)建數(shù)組呢隧土?反射創(chuàng)建數(shù)組不僅速度沒有 new 快提针,而且寫的程序也不易讀,還不如 new 來(lái)的直接曹傀。事實(shí)上通過(guò)反射創(chuàng)建數(shù)組確實(shí)很少見辐脖。
練習(xí):
寫一個(gè)函數(shù),簽名與參數(shù)列表為:public String getSql(Object obj);
- 如果傳遞的是 User 對(duì)象
- User 有 (name, age, sex) 屬性
- 寫一個(gè)數(shù)據(jù)庫(kù)操作的命令行:返回 insert into User(name, age, sex) values (?, ?, ?)
public class Demo11 {
public static void main(String[] args) {
String res = getSql(new User());
System.out.println(res);
}
public static String getSql(Object obj) {
StringBuffer s = new StringBuffer();
s.append("insert into ");
Class c = obj.getClass();
// 不包含包名的類名稱
String className = c.getSimpleName();
s.append(className).append("(");
//==============
// 獲取類的成員變量皆愉,并用逗號(hào)與括號(hào)分隔存儲(chǔ)
// 如:(name, age, sex)
//==============
Field[] fs = c.getDeclaredFields();
// 類的所有成員變量用逗號(hào)','隔開
for(int i = 0; i < fs.length; i++) {
s = i == 0 ? s.append(fs[i].getName()) : s.append(", ").append(fs[i].getName());
}
// 接上相同個(gè)數(shù)的 '?' 列表
s.append(") values ").append(getString(fs.length));
return s.toString();
}
public static String getString(int length) {
StringBuilder s = new StringBuilder();
s.append("(");
for(int i = 0; i < length; i++)
s = i == 0 ? s.append("?") : s.append(", ?");
return s.append(")").toString();
}
}
輸出為:
insert into User(name, age, sex) values (?, ?, ?)