導(dǎo)語
反射機制是Java程序開發(fā)語言的重要特征之一。
主要內(nèi)容
- 認(rèn)識反射
- 理解反射作用
- 利用反射來調(diào)用類的結(jié)構(gòu)
具體內(nèi)容
認(rèn)識反射
反射的話先通過“反”來理解,既然有“反”就有“正”,在正常情況下,一定是先有類而后再產(chǎn)生對象
所謂的“反”就是指可以利用對象找到對象的出處,在Object類里面提供有一個方法:
取得Class對象:public final Class<?> getClass()。
范例:觀察反
import java.util.Date; // 先有類
public class TestDemo {
public static void main(String args[]) {
Date date = new Date(); // 產(chǎn)生對象
System.out.println(date.getClass());
}
}
輸出結(jié)果
class java.util.Date
發(fā)現(xiàn)調(diào)用了getClass()方法后的輸出就輸出了類的完整名稱,等于是找到了對象的出處。
Class類對象實例化
java.lang.Class是一個類,這個類是反射操作的源頭,即:所有的反射都要從此類開始進行,而最關(guān)鍵的是這個類有三種實例化方式:
- 第一種:調(diào)用Object類中的getClass()方法。
import java.util.Date;
public class TestDemo {
public static void main(String args[]) {
Date date = new Date();
Class<?> cls = date.getClass();
System.out.println(cls);
}
}
- 第二種:使用“類.class”取得。
import java.util.Date;
public class TestDemo {
public static void main(String args[]) {
Class<?> cls = Date.class;
System.out.println(cls);
}
}
之前是在產(chǎn)生了類的實例化對象之后取得的Class對象,但是此時并沒有實例化對象的產(chǎn)生。
- 第三種:調(diào)用Class類提供的一個方法。實例化Class對象:public static Class<?> forName(String className) throws ClassNotFoundException。
public class TestDemo {
public static void main(String args[]) {
Class<?> cls = Class.forName("java.util.Date);
System.out.println(cls);
}
}
此時可以不使用import語句導(dǎo)入一個明確的類,而類名稱是采用字符串的形式進行描述的。
反射實例化對象
當(dāng)拿到一個類的時候,肯定要直接使用關(guān)鍵字new進行對象的實例化操作,這屬于習(xí)慣 性的做法,但是如果有了Class類對象,那么就可以做到,利用反射來實現(xiàn)對象實例化操作:
- 實例化對象方法:public T newInstance() throws throws InstantiationException, IllegalAccessException。
范例:利用反射實例化對象
package cn.cc.demo;
class Book {
public Book() {
System.out.println("Book類的無參構(gòu)造方法");
}
@Override
public String toString() {
return "這是一本書!";
}
}
public class TestDemo {
public static void main(String args[]) {
Class<?> cls = Class.forName("cn.cc.demo.Book");
Object obj = cls.newInstance(); // 相當(dāng)于使用new調(diào)用無參構(gòu)造實例化
Book book = (Book) obj;
System.out.println(book);
}
}
輸出結(jié)果
Book類的無參構(gòu)造方法
這是一本書!
有了反射之后,以后進行對象實例化的操作不再只是單獨的依靠關(guān)鍵字new完成了,反射也同樣可以完成,但是這并不表示new就被完全取代了。
在任何的開發(fā)之中,new是造成耦合的最大元兇,一切的耦合都起源于new。
范例:觀察工廠設(shè)計模式
package cn.cc.test;
interface Fruit {
public void eat();
}
public class Apple implements Fruit {
@Override
public void eat() {
System.out.println("吃蘋果!");
}
}
public class Factory {
public static Fruit getInstance(String className) {
if("apple".equals(className)) {
return new Apple();
}
return null;
}
}
public class TestFactory {
public static void main(String args[]) {
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
如果此時增加了Fruit接口子類,那么就表示程序要修改工廠類。
package cn.cc.test;
interface Fruit {
public void eat();
}
public class Apple implements Fruit {
@Override
public void eat() {
System.out.println("吃蘋果!");
}
}
public class Orange implements Fruit {
@Override
public void eat() {
System.out.println("吃橘子!");
}
}
public class Factory {
public static Fruit getInstance(String className) {
if("apple".equals(className)) {
return new Apple();
} else if() {
return new Orange();
} else {
return null;
}
}
}
public class TestFactory {
public static void main(String args[]) {
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
每增加一個類就要去修改工廠類,那么如果隨時都可能增加子類呢?工廠類要一直被進行修改。
因為現(xiàn)在工廠類中的對象都是通過關(guān)鍵字new直接實例化的,而new就成了所有問題的關(guān)鍵點。要想解決這一問題,就只能夠依靠反射來完成。
package cn.cc.test;
interface Fruit {
public void eat();
}
public class Apple implements Fruit {
@Override
public void eat() {
System.out.println("吃蘋果!");
}
}
public class Orange implements Fruit {
@Override
public void eat() {
System.out.println("吃橘子!");
}
}
public class Factory {
public static Fruit getInstance(String className) {
Fruit f = null;
try {
f = (Fruit) Class.forName(className);
} catch(Exception e) {}
return f;
}
}
public class TestFactory {
public static void main(String args[]) {
Fruit f = Factory.getInstance("cn.cc.test.Apple");
f.eat();
}
}
輸出結(jié)果
吃蘋果!
此時的程序就真正完成了解耦合的目的,而且可擴展性非常的強。
使用反射調(diào)用構(gòu)造
在之前 所編寫的代碼實際上發(fā)現(xiàn)都默認(rèn)使用了類之中的無參構(gòu)造方法,可是類中還有可能不提供無參構(gòu)造。
范例:觀察代碼問題
定義Book類:
package cn.cc.po;
public class Book {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
@Overide
public String toString() {
return "書名:" + this.title + " 價格:" + this.price;
}
}
定義TestDemo類:
package cn.cc.demo;
public class TestDemo {
public static void main(String args[]) {
Class<?> cls = Class.forName("cn.cc.po.Book");
Object obj = cls.newInstance(); // 相當(dāng)于使用new調(diào)用無參構(gòu)造實例化
System.out.println(obj);
}
}
由于此時Book類沒有提供無參構(gòu)造方法,所以代碼會出錯。
Exception in thread "main" java.lang.InstantiationException: cn.cc.po.Book
at java.lang.Class.newInstance(Class.java:427)
at cn.cc.demo.TestDemo.main(TestDemo.java:5)
Caused by: java.lang.NoSuchMethodException: cn.cc.po.Book.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
...
以上所出現(xiàn)的錯誤指的就是因為當(dāng)前Book類里面并沒有無參構(gòu)造方法,所以程序無法進行對象的實例化。在這種情況下,只能夠明確的調(diào)用有參構(gòu)造方法。
在Class類里面提供有一個方法能夠取得構(gòu)造:
- 取得全部構(gòu)造:public Constructor<?>[] getConstructors() throws SecurityException。
- 取得一個指定參數(shù)順序的構(gòu)造: public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException。
以上兩個方法返回的都是“java.lang.reflect.Constructor”類的對象。在這個類中提供有一個明確傳遞有參構(gòu)造內(nèi)容的實例化對象的方法:public T newInstance(Object... args) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException。
范例:明確調(diào)用類中的有參構(gòu)造
package cn.cc.demo;
public class TestDemo {
public static void main(String args[]) {
Class<?> cls = Class.forName("cn.cc.po.Book");
// public Book(String title, double price),找到類型
Constructor<?> con = cls.getConstructor(String.class, double.class);
Object obj = con.newInstance("Java開發(fā)", 79.8); // 實例化對象
System.out.println(obj);
}
}
輸出結(jié)果
書名:Java開發(fā) 價格:79.8
簡單Java類的開發(fā)之中不管提供有多少個構(gòu)造方法,請至少保留有無參構(gòu)造(為反射實例化對象的時候方便)。
反射調(diào)用方法
類中的普通方法只有在一個類產(chǎn)生實例化對象之后才可以調(diào)用,并且實例化對象的方式有三種:
- new實例化對象。
- 克隆對象。
- 通過反射實例化對象。
在Class類里面提供有以下取得類中Method(方法)的操作:
- 取得一個類中的全部方法:public Method[] getMethods() throws SecurityException。
- 取得指定方法:public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException。
以上的兩個操作返回的是java.lang.reflect.Method類的對象,在這個類里面重點關(guān)注一個方法,調(diào)用方法:public native Object invoke(Object receiver, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException。
范例:使用反射調(diào)用方法
定義一個Book類。
package cn.cc.po;
public class Book {
private String title;
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
定義TestDemo類:
package cn.cc.demo;
public class TestDemo {
public static void main(String args[]) {
String fieldName = "title"; // 要操作的成員
Class<?> cls = Class.forName("cn.cc.po.Book");
Object obj = cls.newInstance(); // 必須給出實例化對象
Method setMet = cls.getMethod("set" + initcap(fieldName), String.class);
Method getMet = cls.getMethod("get" + initcap(fieldName);
setMet.invoke(obj, "Java開發(fā)"); // 等價于:Book類對象.setTitle("Java開發(fā)");
System.out.println(getMet.invoke(obj)); // 等價于:Book類對象.getTitle();
}
public static String initcap(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}
輸出結(jié)果
Java開發(fā)
此時完全看不見具體的操作類型,也就是說利用反射可以實現(xiàn)任意類的指定方法的調(diào)用。
反射調(diào)用成員
類中的屬性一定要在本類 實例化對象產(chǎn)生之后才可以分配內(nèi)存空間。在Class類里面提供有取得成員的方法:
- 取得全部成員:public Field[] getDeclaredFields() throws SecurityException。
- 取得指定成員:public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException。
返回的類型是java.lang.reflect.Field類,在這個類里面有兩個重要的方法:
- 取得屬性內(nèi)容:public native Object get(Object object)
throws IllegalAccessException, IllegalArgumentException。 - 設(shè)置屬性內(nèi)容:public native void set(Object object, Object value)
throws IllegalAccessException, IllegalArgumentException。
一般情況下,類中的私有屬性,按原始的做法,此時它一定無法被外部所使用。
在java.lang.reflect.AccessibleObjject類下面(JDK1.8之后修改):
- Executable:下面繼續(xù)繼承了Constructor、Method。
- Field。
在這個類中提供了一個方法:public void setAccessible(boolean flag) throws SecurityException,設(shè)置是否封裝。
范例:使用反射調(diào)用成員
定義一個Book類。
package cn.cc.po;
public class Book {
private String title;
}
定義TestDemo類:
package cn.cc.demo;
public class TestDemo {
public static void main(String args[]) {
String fieldName = "title"; // 要操作的成員
Class<?> cls = Class.forName("cn.cc.po.Book");
Object obj = cls.newInstance(); // 必須給出實例化對象
Field titleField = cls.getDeclaredField(fieldName);
titleField.setAccessible(true); // 封裝取消了,否則不能調(diào)用private屬性
titleField.set(obj, "Java開發(fā)"); // 等價于:Book類對象.title = "Java開發(fā)";
System.out.println(titleField.get(obj)); // 等價于:Book類對象.title;
}
}
輸出結(jié)果
Java開發(fā)
構(gòu)造方法與普通方法也同樣可以取消封裝,只不過很少這樣去做,而且對于屬性的訪問還是建議使用setter、getter方法完成。
總結(jié)
- 實例化對象的方式又增加了一種反射。
- 對于簡單Java類的定義應(yīng)該更加清晰了。
- 反射調(diào)用類結(jié)構(gòu)只是一個開始。