反射(java)

反射機制就是在程序“運行狀態”時:對任意一個類,我們通過反射都能夠知道這個類的所有屬性和方法;對於任意一個對象,我們同樣也都能夠調用它的任意一個方法和屬性。

目錄

一、反射的入口

二、反射可以拿到什麼

2.1獲取所有實現的接口

2.2獲取所有的父類

2.3獲取所有的構造方法

2.4獲取方法

2.5獲取屬性

2.6獲取對象實例

三、利用反射獲取對象實例,並操作對象

3.1操作屬性

3.2操作方法

3.3操作構造方法

四、補充

4.1動態加載類

4.2越過泛型檢查


一、反射的入口

想要使用反射,我們必須獲取到某一個類的Class對象,java中一共有三種方式可以獲取到某個類的Class對象:

  1. Class.forName(類的全限定名);
  2. 類名.class;
  3. 對象.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獲取方法

這裏獲取方法又分爲兩種情況:

  1. 獲取所有的公共方法(本類及其父類、接口中所有的public修改的方法),關鍵字getMethods();
  2. 只獲取當前類中所有的方法(包括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獲取屬性

屬性的獲取和上面方法的獲取類似,也是分兩種情況:

  1. 獲取所有的公共屬性,關鍵字getFields();
  2. 獲取所有屬性,關鍵字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

這裏面需要注意的就三點:

  1. getDeclaredField(參數)方法,我們在上面講的getDeclaredFields()方法沒有參數,它拿到的是類中的所有屬性,返回的也是一個Field數組,而getDeclaredField(參數)方法中需要傳遞參數,這個參數就是屬性名,通過這個方法可以拿到指定的屬性,返回值就是一個Field,而不是數組;
  2. setAccessible()方法,如果類中的某個屬性/方法是private修飾的,我們需要調用Field/Method.setAccessible(true)才能不受限制的給其賦值或調用;
  3. 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......

操作方法同上面的操作屬性類似,這裏需要注意的有以下幾點:

  1. getDeclaredMethod(參數1,參數2)方法拿到的是某個具體的方法,第一個參數是你要獲取方法的方法名,第二個是這個方法的參數類型。比如我們person類中的privateMethod()這個方法,它的方法名就是privateMethod,並且它沒有參數,所以我們第二個參數就直接寫null。如果它是privateMethod(String s)這種類型,那麼它的參數類型是String,這個時候getDeclaredMethod方法就改寫成getDeclaredMethod("privateMethod",String.class)。
  2. 屬性的賦值是通過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等訪問修飾符不允許訪問的屬性/方法,也可以忽略掉泛型的約束,但在實際開發中不建議這樣做,因爲可能會造成程序的混亂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章