反射機制就是在程序“運行狀態”時:對任意一個類,我們通過反射都能夠知道這個類的所有屬性和方法;對於任意一個對象,我們同樣也都能夠調用它的任意一個方法和屬性。
目錄
一、反射的入口
想要使用反射,我們必須獲取到某一個類的Class對象,java中一共有三種方式可以獲取到某個類的Class對象:
- Class.forName(類的全限定名);
- 類名.class;
- 對象.getClass();(getClass()方法是Object類中的一個native方法)
下面我們結合代碼實例具體的看一下反射的用法。先創建一個接口和一個實現類,並在實現類裏面賦予一些成員屬性和方法。
接口1:
public interface MyInterface {
void interfaceMethod();
}
接口2:
public interface MyInterface2 {
void interfaceMethod2();
}
實現類:
public class Person implements MyInterface,MyInterface2{
private int id;
private String name;
private int age;
public String gender;
@Override
public void interfaceMethod() {
System.out.println("interface Method......");
}
@Override
public void interfaceMethod2() {
System.out.println("interface2 Method......");
}
//準備一個靜態方法
public static void staticMehtod(){
System.out.println(" static method......");
}
//準備一個公有方法
public void publicMethod(){
System.out.println("public Method......");
}
//準備一個私有方法
private void privateMethod(){
System.out.println("private Method......");
}
//無參構造
public Person() {
}
//一參構造
public Person(int id) {
this.id = id;
}
//三餐構造
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
//私有構造
private Person(String name){
this.name = name;
}
//get和set方法
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
測試類:
//通過反射獲取類
public class RelectDemo01 {
public static void main(String[] args) {
//1.Class.forName(類的全限定名) 一般推介使用此方式
try {
Class<?> personClass = Class.forName("com.shhxbean.test.reflect.Person");
System.out.println(personClass);
}catch (ClassNotFoundException e){
e.printStackTrace();
}
//2.類名.class
Class<Person> personClass2 = Person.class;
System.out.println(personClass2);
//3.對象.getClass()
Person person = new Person();
Class<? extends Person> personClass3 = person.getClass();
System.out.println(personClass3);
}
}
輸出:
class com.shhxbean.test.reflect.Person
class com.shhxbean.test.reflect.Person
class com.shhxbean.test.reflect.Person
二、反射可以拿到什麼
其實一個類或對象,只要你裏面有的,通過反射都可以拿到,不管你是實現了多個接口,繼承了某個類,裏面有什麼屬性、什麼方法,在反射面前一切都是虛無......下面我們具體展開看一看:
2.1獲取所有實現的接口
上面的代碼實例中我們我們讓Person實現了兩個接口,現在通過反射拿到這兩個接口,關鍵方法就是getInterfaces();
public class RelectDemo02 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//獲取Person實現的所有接口
Class<?>[] interfaces = personClass.getInterfaces();
for(Class<?> inter : interfaces){
System.out.println(inter);
}
}
}
輸出:
interface com.shhxbean.test.reflect.MyInterface
interface com.shhxbean.test.reflect.MyInterface2
2.2獲取所有的父類
Person類沒有顯式的繼承某個父類,但它有默認的父類Object,我們通過反射也可以拿到,關鍵方法getSuperClass();
public class RelectDemo03 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//獲取Person的父類
Class<?> superClass = personClass.getSuperclass();
System.out.println(superClass);
}
}
輸出:
class java.lang.Object
2.3獲取所有的構造方法
在Person類中定義了三個構造方法,我們通過反射來拿到他們,關鍵字getConstractors();這個也是獲取的public修飾的構造方法,如果構造方法是private修飾的,這種方式是拿不到的,那如何拿private的,getDeclaredConstractors()即可。
public class RelectDemo04 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//獲取Person的構造方法
Constructor<?>[] constructors = personClass.getConstructors();
for(Constructor<?> constructor :constructors){
System.out.println(constructor);
}
}
}
輸出:
public com.shhxbean.test.reflect.Person()
public com.shhxbean.test.reflect.Person(int)
public com.shhxbean.test.reflect.Person(int,java.lang.String,int)
2.4獲取方法
這裏獲取方法又分爲兩種情況:
- 獲取所有的公共方法(本類及其父類、接口中所有的public修改的方法),關鍵字getMethods();
- 只獲取當前類中所有的方法(包括public、private修飾的所有方法),關鍵字getDeclaredMethods();
public class RelectDemo05 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//情況一:獲取所有公共的方法(包括父類、接口中所有public的方法)
System.out.println("情況一:獲取所有公共的方法:");
Method[] methods = personClass.getMethods();
for(Method method : methods){
System.out.println("情況一:" + method);
}
System.out.println("==================================");
//情況二:只獲取當前類中所有方法(包括public\private)
System.out.println("情況一:獲取所有公共的方法:");
Method[] declaredMethods = personClass.getDeclaredMethods();
for(Method declaredMethod : declaredMethods){
System.out.println("情況二:" + declaredMethod);
}
}
}
輸出:
情況一:獲取所有公共的方法:
情況一:public java.lang.String com.shhxbean.test.reflect.Person.getName()
情況一:public int com.shhxbean.test.reflect.Person.getId()
情況一:public void com.shhxbean.test.reflect.Person.setName(java.lang.String)
情況一:public void com.shhxbean.test.reflect.Person.interfaceMethod()
情況一:public void com.shhxbean.test.reflect.Person.interfaceMethod2()
情況一:public void com.shhxbean.test.reflect.Person.setId(int)
情況一:public int com.shhxbean.test.reflect.Person.getAge()
情況一:public void com.shhxbean.test.reflect.Person.setAge(int)
情況一:public static void com.shhxbean.test.reflect.Person.staticMehtod()
情況一:public void com.shhxbean.test.reflect.Person.publicMethod()
情況一:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
情況一:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
情況一:public final void java.lang.Object.wait() throws java.lang.InterruptedException
情況一:public boolean java.lang.Object.equals(java.lang.Object)
情況一:public java.lang.String java.lang.Object.toString()
情況一:public native int java.lang.Object.hashCode()
情況一:public final native java.lang.Class java.lang.Object.getClass()
情況一:public final native void java.lang.Object.notify()
情況一:public final native void java.lang.Object.notifyAll()
==================================
情況一:獲取所有公共的方法:
情況二:public java.lang.String com.shhxbean.test.reflect.Person.getName()
情況二:public int com.shhxbean.test.reflect.Person.getId()
情況二:public void com.shhxbean.test.reflect.Person.setName(java.lang.String)
情況二:public void com.shhxbean.test.reflect.Person.interfaceMethod()
情況二:public void com.shhxbean.test.reflect.Person.interfaceMethod2()
情況二:public void com.shhxbean.test.reflect.Person.setId(int)
情況二:public int com.shhxbean.test.reflect.Person.getAge()
情況二:public void com.shhxbean.test.reflect.Person.setAge(int)
情況二:public static void com.shhxbean.test.reflect.Person.staticMehtod()
情況二:public void com.shhxbean.test.reflect.Person.publicMethod()
情況二:private void com.shhxbean.test.reflect.Person.privateMethod()
通過上面的輸出結果我們可以看到情況一拿到了很多方法,既有類本身的public方法,也有其父類Object中的wait()、equals()等方法,還有接口中的方法。而情況二中只拿到了類本身中的方法,包括public和private修飾的方法。
2.5獲取屬性
屬性的獲取和上面方法的獲取類似,也是分兩種情況:
- 獲取所有的公共屬性,關鍵字getFields();
- 獲取所有屬性,關鍵字getDeclaredFields();
public class RelectDemo06 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
////情況一:獲取所有公共的屬性(包括父類、接口中所有public的屬性)
System.out.println("情況一:獲取所有公共的屬性:");
Field[] fields = personClass.getFields();
for(Field field :fields){
System.out.println("情況一:" + field);
}
System.out.println("==================================");
//情況二:只獲取當前類中所有屬性(包括public\private)
System.out.println("情況二:獲取所有屬性:");
Field[] declaredfields = personClass.getDeclaredFields();
for(Field declaredfield : declaredfields){
System.out.println("情況二:" + declaredfield);
}
}
}
輸出:
情況一:獲取所有公共的屬性:
情況一:public java.lang.String com.shhxbean.test.reflect.Person.gender
==================================
情況二:獲取所有屬性:
情況二:private int com.shhxbean.test.reflect.Person.id
情況二:private java.lang.String com.shhxbean.test.reflect.Person.name
情況二:private int com.shhxbean.test.reflect.Person.age
情況二:public java.lang.String com.shhxbean.test.reflect.Person.gender
2.6獲取對象實例
上面列舉的是通過反射拿到了一個類中的一些方法、屬性等,我們也可以通過反射來創建某個類(或接口)的實例對象,關鍵字newInstance();
public class RelectDemo07 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object instance = personClass.newInstance();
Person person = (Person) instance;
//通過實例對象調用方法
person.interfaceMethod();
}
}
輸出:
interface Method......
三、利用反射獲取對象實例,並操作對象
上面的內容只是大體的羅列了一下通過反射可以拿到的一些東西,現在我們具體的通過反射機制創建一個實例對象,並對其做一些特定的操作。
3.1操作屬性
public class RelectDemo08 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//操作屬性
try {
//利用反射機制獲取實例對象
Person person = (Person)personClass.newInstance();
//getDeclaredField()中的參數就是屬性的名字
Field idField = personClass.getDeclaredField("id");
//因爲id屬性是private的,所以需要通過setAccessible(true)屏蔽掉private限制
idField.setAccessible(true);
//第一個參數是對象名,第二個參數是要給此屬性賦的值
idField.set(person,1);
System.out.println("id=" + person.getId());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
輸出:
id=1
這裏面需要注意的就三點:
- getDeclaredField(參數)方法,我們在上面講的getDeclaredFields()方法沒有參數,它拿到的是類中的所有屬性,返回的也是一個Field數組,而getDeclaredField(參數)方法中需要傳遞參數,這個參數就是屬性名,通過這個方法可以拿到指定的屬性,返回值就是一個Field,而不是數組;
- setAccessible()方法,如果類中的某個屬性/方法是private修飾的,我們需要調用Field/Method.setAccessible(true)才能不受限制的給其賦值或調用;
- set()方法的兩個參數,第一個是對象引用,第二個是你要賦的具體的值。
3.2操作方法
public class RelectDemo09 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//操作方法
try {
//利用反射機制獲取實例對象
Person person = (Person)personClass.newInstance();
//getDeclaredMethod()中的參數:第一個是方法名,第二個是方法的參數類型
Method method = personClass.getDeclaredMethod("privateMethod",null);
//因爲privateMethod方法是private的,所以需要通過setAccessible(true)屏蔽掉private限制
method.setAccessible(true);
//第一個參數是對象名,第二個是要給privateMethod方法傳遞的參數
method.invoke(person,null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
輸出:
private Method......
操作方法同上面的操作屬性類似,這裏需要注意的有以下幾點:
- getDeclaredMethod(參數1,參數2)方法拿到的是某個具體的方法,第一個參數是你要獲取方法的方法名,第二個是這個方法的參數類型。比如我們person類中的privateMethod()這個方法,它的方法名就是privateMethod,並且它沒有參數,所以我們第二個參數就直接寫null。如果它是privateMethod(String s)這種類型,那麼它的參數類型是String,這個時候getDeclaredMethod方法就改寫成getDeclaredMethod("privateMethod",String.class)。
- 屬性的賦值是通過set方法,而方法的調用則是通過invoke()方法
3.3操作構造方法
public class RelectDemo10 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//操作構造方法
try {
//利用反射機制獲取實例對象
Person person = (Person)personClass.newInstance();
//getConstructor中的參數是構造函數的參數類型
//在反射中,根據類型獲取方法時:基本類型(int char...)和包裝類(Integer Character...)是不同的類型
//如果是私有的構造方法通過getDeclaredConstructor即可獲取,其他的不變
Constructor<?> constractor = personClass.getConstructor(int.class);
System.out.println(constractor);
//通過構造方法創建對象,因爲我們在上面是通過personClass.getConstructor(int.class)
//拿到的構造方法(有參構造),所以通過它新建對象的時候也必須傳遞一個參數,否則會報錯
Person person2 = (Person) constractor.newInstance(16);
System.out.println("person2=" + person2);
//獲取私有構造
Constructor<?> constructor2 = personClass.getDeclaredConstructor(String.class);
constructor2.setAccessible(true);
//通過私有構造創建對象
Person person3 = (Person) constructor2.newInstance("周杰倫");
System.out.println("person3=" + person3);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
四、補充
4.1動態加載類
有時候在程序運行之前我們不知道要調用那個類的那個方法,只有在運行的時候才知道。這個時候反射就會發揮出他的價值。我們可以把類名、方法名等配置在一個文件中,通過讀取文件來讓程序取執行不同類的不同方法,進而避免修改程序文件,而只是根據實際情況修改配置文件即可。假如我們現在有兩個類Student和Teacher,如下:
public class Student {
public void study(){
System.out.println("I am studying...");
}
}
public class Teacher {
public void teach(){
System.out.println("I am teaching...");
}
}
現在我們再在工程目錄下創建一個專門的配置文件class.txt(名字隨便起),裏面專門配置類和方法名,如下:
內容如下:
classname=com.shhxbean.test.reflect.Teacher
methodname=teach
下面我們寫個demo演示一下:
public class ReflectDemo11 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//動態加載類名和方法
Properties properties = new Properties();
properties.load(new FileReader("class.txt"));
String classname = properties.getProperty("classname");
String methodname = properties.getProperty("methodname");
try {
Class<?> aClass = Class.forName(classname);
//getMethod()、invoke()爲可變參數類型...
Method method = aClass.getMethod(methodname);
method.invoke(aClass.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出:
I am teaching...
可以看到我們通過Properties讀取了class.txt文件的內容,拿到方法名和類名,最後通過反射實現了對其的調用。這時如果我們想要調用Student類的study方法,只要在class.txt文件中把classname和methodname做相應的修改即可,而不需要修改程序。
4.2越過泛型檢查
我們知道,假如在一個List裏面有泛型限定,那麼你只能向這個List裏面添加指定類型的元素,其他元素是添加不進去的,但是通過反射就可以繞過泛型檢查,想添加啥添加啥,如下demo:
public class ReflectDemo12 {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//因爲有泛型的約束,所以下面的這行代碼在編譯期就會報錯,它不允許你往list裏面放String
//類型的元素
//list.add("a");
//通過反射繞過泛型檢查
Class<? > listClass = list.getClass();
//利用反射調用add方法,繞過泛型檢查,Object.class表示了可以往進添加任何類型的元素
Method method = listClass.getMethod("add", Object.class);
method.invoke(list,"aaaaa");
System.out.println(list);
}
}
輸出:
[1, 2, 3, aaaaa]
不過需要提醒的是雖然可以通過反射機制訪問private等訪問修飾符不允許訪問的屬性/方法,也可以忽略掉泛型的約束,但在實際開發中不建議這樣做,因爲可能會造成程序的混亂。