天天看點

Java反射體系詳解

文章目錄

    • 1.擷取一個類的對應的Class對象 (Class的C是大寫)
        • (1)調用Object提供的getClass方法
        • (2)類名稱 . class
        • (3)調用Class類提供的靜态方法:Class . forName(類的全名稱)
    • 2.當我們拿到一個類的class對象後,可以做什麼呢?
      • 1. 建立該類的新對象
      • 2. 取得包名、父類、父接口資訊
      • 3. 取得構造方法、普通方法、普通屬性(⭐)
        • (1)取得所有參數構造方法:
        • (2)取得指定參數的構造方法
        • (3)建立新對象
          • (1):通過Class類的newInstance()方法
          • (2)通過Constructor的newInstance方法擷取指定參數構造方法執行個體化的對象
        • (4)取得方法和調用方法(⭐)
          • 1.擷取所有普通方法(傳回值類型都為Method[ ])
          • 2.擷取指定參數的普通方法
          • 3. 擷取私有方法以及調用私有方法(setAccessible⭐)
        • (5)取得屬性
          • 1. 擷取所有屬性或者單個屬性
          • 2. 操作屬性(Field類中的兩個方法)
      • 4. Java反射的繼承結構

定義:根據現有對象倒推類的組成

最核心:在JVM中任何一個類都有一個唯一的Class對象 ,此對象記錄該類的組成結構,通過該class對象,可以反向查找到這個類的資訊,稱之為反射;

class對象是在當類加載時由JVM産生,使用者隻能取得此對象,無法建立此對象; ?

1.擷取一個類的對應的Class對象 (Class的C是大寫)

要想在Java中應用反射,首先取得該類的Class對象;有三種方法可以擷取:

(1)調用Object提供的getClass方法

class Person {

}
public class Test1 {
    public static void main(String[] args) {
        Person per = new Person();
        System.out.println(per.getClass());
    }
}
           

(2)類名稱 . class

public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException{
        Class cla = Test1.class;
        System.out.println(cla);
    }
}
           

(3)調用Class類提供的靜态方法:Class . forName(類的全名稱)

class Person {

}

public class Test1 {
    public static void main(String[] args){
        try {
            Class<?> cla = Class.forName("com.yy.readBlackTree.Person");
            System.out.println(cla.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
           
輸出:com.yy.readBlackTree.Person

2.當我們拿到一個類的class對象後,可以做什麼呢?

1. 建立該類的新對象

反射與工廠模式

package www.bitten.java;

/**
 * @Author : YangY
 * @Description :   反射與工廠模式
 * @Time : Created in 9:37 2019/3/17
 */
interface Buycomputer {
    void buy();
}
class Lenove implements Buycomputer {
    @Override
    public void buy() {
        System.out.println("買一台聯想電腦");
    }
}
class Mac implements Buycomputer {
    @Override
    public void buy() {
        System.out.println("買一台蘋果電腦");
    }
}
class ComputerFactory {
    public static Buycomputer getInstance(String computerClass) {
        try {
            Class<?> cls = Class.forName(computerClass);
            Buycomputer computer = (Buycomputer) cls.newInstance();
            return computer;
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }catch (IllegalAccessException e){
            e.printStackTrace();
        }catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }
}

public class Test1 {
    public static void main(String[] args) {
        ComputerFactory.getInstance("www.bitten.java.Lenove").buy();
    }
}
           

2. 取得包名、父類、父接口資訊

interface ILife {

}
interface IAnimal {

}
class Person implements ILife,IAnimal {

}
public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException{
        Class<?> cla = Person.class;
        //列印包名
        System.out.println(cla.getPackage().getName()); 
        //列印它的父類名稱
        System.out.println(cla.getSuperclass().getName());
        //用這個類的Class對象生成它的父接口數組
        Class<?>[] clasArr = cla.getInterfaces();  //也可寫成Class[] ....
        //輸出父接口
        for(Class<?> element: clasArr) {
            System.out.println(element);
        }

    }
}
           

輸出:

www.bitten.java

java.lang.Object

interface www.bitten.java.ILife

interface www.bitten.java.IAnimal

3. 取得構造方法、普通方法、普通屬性(⭐)

Constructor :用來描述一個類的構造方法的類

(1)取得所有參數構造方法:

  • Class 類提供的getConstructors() :隻能取得權限為public的構造方法 ;(包通路權限都不行,隻能public)
  • Class類提供的getDeclaredConstructors():取得所有權限的構造方法 ;
class Person  {
    private String name;
    private int age;
    public Person() {

    }
    Person(String name) {

    }
    private Person(String name, int age) {

    }
}
public class Test1 {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<?> cla = Person.class;
        //隻能取得權限為public的構造方法
        Constructor<?>[] constructor = cla.getConstructors();
        //取得所有權限的構造方法
        Constructor<?>[] constructors = cla.getDeclaredConstructors();
        //輸出權限為public的所有構造方法
        for (Constructor<?> constructor1 : constructor) {
            System.out.println(constructor1);
        }
        //輸出所有權限的構造方法
        System.out.println("--------------------------");
        for (Constructor<?> constructor2 : constructors) {
            System.out.println(constructor2);
        }
    }
}
           

輸出:

public www.bitten.java.Person()

public www.bitten.java.Person()

www.bitten.java.Person(java.lang.String)

private www.bitten.java.Person(java.lang.String,int)

(2)取得指定參數的構造方法

  • Class類提供的getConstructor(參數) : 隻能取得權限為public的構造方法 (包通路都不行,隻能public)
  • Class類提供的getDeclaredConstructor(參數) : 可以取得類中所有構造方法,與權限無關

例碼:

class Person  {
    private String name;
    private int age;
    public Person() {

    }
    public Person(String name) {

    }
    public Person(String name, int age) {

    }
}
public class Test1 {
    public static void main(String[] args)throws NoSuchMethodException{
        Class<?> cla = Person.class;
        //注意:getConstructor這個方法隻能取得權限為public的構造方法
        Constructor constructor = cla.getConstructor(String.class,int.class);
        System.out.println(constructor.getName());
    }
}
           

輸出:

www.bitten.java.Person

(3)建立新對象

利用反射建立新對象有兩種方法:

  • 通過Class類的newInstance()方法 ,此方法隻能調用無參構造來産生執行個體化對象;
  • 通過Constructor的newInstance方法擷取指定參數構造方法執行個體化的對象;
(1):通過Class類的newInstance()方法
class Person  {
    private String name;
    private int age;
    public Person() {

    }
    public Person(String name) {

    }
    public Person(String name, int age) {

    }
    public void fun() {
        System.out.println("hello");
    }
}
public class Test1 {
    public static void main(String[] args)throws InstantiationException,IllegalAccessException{
        Class<?> cla = Person.class;
        //Class類的newInstance方法隻能擷取無參構造執行個體化的對象
        Person per =(Person) cla.newInstance();
        System.out.println(per);
        per.fun();
    }
}
           

輸出:

[email protected]

hello

結論:我們在編寫Java程式時要養成保留無參構造的習慣 (⭐)

(2)通過Constructor的newInstance方法擷取指定參數構造方法執行個體化的對象
class Person  {
        private String name;
        private int age;
        public Person() {

        }
        public Person(String name) {
            this.name = name;
        }
        public Person(String name, int age) {

        }
        public void fun() {
            System.out.println("hello");
        }
        public String getName() {
            return name;
        }

    }
    public class Test1 {
        public static void main(String[] args)throws NoSuchMethodException,InstantiationException
                ,IllegalAccessException,InvocationTargetException {
            Class<?> cla = Person.class;
            Constructor constructor = cla.getConstructor(String.class);
            Person per =(Person) constructor.newInstance("yy");
            System.out.println(per);
            System.out.println(per.getName());
        }
}
           

(4)取得方法和調用方法(⭐)

Class類中提供了擷取所有普通方法和擷取指定參數普通方法的方法;

1.擷取所有普通方法(傳回值類型都為Method[ ])
  • (1):getMethods(),該方法傳回對應的所有普通方法(不能傳回private修飾的方法) ,它還傳回父類,包括Object類的相關方法,也能傳回所實作接口的普通方法;
  • (2):getDeclaredMethods(),該方法傳回對應的所有普通方法,包括private修飾的普通方法,它不會傳回父類的方法,但它能傳回所實作接口中的方法;

最大差別:(1)方法能傳回所繼承父類的方法,而(2)不能;⭐

2.擷取指定參數的普通方法
public Method getMethod(String name, Class<?>... parameterTypes)
           
class PP {
    public void funfun() {

    }
}
class Person extends PP{
    private String name;
    public Person() {
    }

    public String getName() {
        return name;
    }
    public void fun() {

    }

    public void setName(String name) {
        this.name = name;
    }

}
public class Test1 {
    public static void main(String[] args) throws Exception{
        Class cla = Person.class;
        Person per = (Person)cla.newInstance();
        //通過class對象的getDeclaredMethods()方法擷取所有普通方法;
        Method[] arrMethod = cla.getDeclaredMethods();
        for(Method method:arrMethod) {
            System.out.println(method);
        }
        Method method1 = cla.getMethod("setName", String.class);
        Method method2 = cla.getMethod("getName");  //該方法需要抛出NoSuchMethodException;
        //要調用普通方法,必須得先建立它的執行個體化對象,per則是執行個體化
        method1.invoke(per,"yy");
        System.out.println(method2.invoke(per));   //輸出yy
    }
}
           

輸出:

public void www.bitten.java.Person.fun()

public java.lang.String www.bitten.java.Person.getName()

public void www.bitten.java.Person.setName(java.lang.String)

yy

可見getDeclaredMethods方法沒有傳回父類的方法;

3. 擷取私有方法以及調用私有方法(setAccessible⭐)
class Person {
    String name;

    private void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class Test1 {
    public static void main(String[] args) throws Exception {
        //擷取Person類的Class對象
        Class<?> cla = Class.forName("com.yy.readBlackTree.Person");
        //擷取該類的執行個體化對象,通過無參構造生成(上面的Person類預設有無參構造,但如果上面隻有有參構造的話,則必須顯示的加上無參構造)
        Person person = (Person) cla.newInstance();
        //擷取方法對象,因為這個方法是private修飾的,是以必須用getDeclaredMethod而不是getMethod
        Method method = cla.getDeclaredMethod("setName", String.class);
        //通過設定這個可以調用private修飾的方法⭐,否則會報錯
        method.setAccessible(true);
        //調用方法,參數需要該方法的一個執行個體化對象,還有它的所有參數;必須填完所有參數,否則抛出NoSuchMethodException;
        method.invoke(person, "Bob");
        System.out.println(person.getName());   //輸出:Bob
    }
}
           

特别注意:

  • 要想調用一個private修飾的方法,必須調用這個方法對象的setAccessible方法,并且設定參數為true,不調用該方法的時候預設為false;
  • 另外,擷取私有方法對象需要用getDeclaredMethod方法;

(5)取得屬性

前提:類中的所有屬性一定在類對象執行個體化之後才會進行空間配置設定,是以此時如果要想調用類的屬性,必須保證有執行個體化對象。通過反射的newInstance()可以直接取得執行個體化對象(Object類型)

在Class類中提供有兩組取得屬性的方法:

  1. 第一組(父類中)-取得類中全部屬性: public Field[] getFields() throws SecurityException
  2. 第一組(父類中)-取得類中指定名稱屬性: public Field getField(String name) throws

    NoSuchFieldException, SecurityException

  3. 第二組(本類中)-取得類中全部屬性: public Field[] getDeclaredFields() throws SecurityException
  4. 第二組( 本類中)-取得類中指定名稱屬性 : public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
而後就需要關注屬性的核心描述類:java.lang.reflect.Field,在這個類之中有兩個重要方法:
  1. 設定屬性内容 : public void set(Object obj, Object value) throws IllegalArgumentException,

    IllegalAccessException

  2. 取得屬性内容 : public Object get(Object obj) throws IllegalArgumentException,

    IllegalAccessException

1. 擷取所有屬性或者單個屬性
class Person {
    private String name;
    public Integer age;
    String adress;

}

public class Test1 {
    public static void main(String[] args) throws Exception {
        //擷取Person類的Class對象
        Class<?> cla = Class.forName("com.yy.readBlackTree.Person");
        System.out.println("==================擷取權限為public的所有屬性====================");
        Field[] arr = cla.getFields();
        for(Field field: arr) {
            System.out.println(field.getName());
        }
        System.out.println("==================擷取所有屬性====================");
        Field[] arr2 = cla.getDeclaredFields();
        for(Field field: arr2) {
            System.out.println(field.getName());
        }
        System.out.println("==================擷取指定屬性=====================");
        //因為name這個屬性是private修飾的,是以不能用getField()方法;
        Field field = cla.getDeclaredField("name");
        System.out.println(field.getName());
    }
}
           

輸出:

擷取權限為public的所有屬性==

age

擷取所有屬性==

name

age

adress

擷取指定屬性===

name

2. 操作屬性(Field類中的兩個方法)
class Person {
    private String name;
    public Integer age;
    String adress;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", adress='" + adress + '\'' +
                '}';
    }
}

public class Test1 {
    public static void main(String[] args) throws Exception {
        //擷取Person類的Class對象
        Class<?> cla = Class.forName("com.yy.readBlackTree.Person");
        Person person = (Person) cla.newInstance();
        Field field = cla.getDeclaredField("name");
        //因為name屬性是private修飾的,是以需要設定為true,除了private,其他的通路權限都不需要設定setAccessible方法為true;
        field.setAccessible(true);
        field.set(person, "Bob");
        System.out.println(person);

        //注意喲:get方法傳入的參數是執行個體化對象而不是Class對象喲!!⭐
        System.out.println(field.get(person));
    }
}
           

輸出:

Person{name=‘Bob’, age=null, adress=‘null’}

Bob

4. Java反射的繼承結構

Java反射體系詳解

注意:

  • 綠色箭頭都是繼承(extends) 關系;
  • AccessibleObject類提供了setAccessible()方法,這是一個很重要的方法,能夠讓反射變得更将大,即能夠操作由private通路權限的構造器、方法、屬性;
    public void setAccessible(boolean flag) throws SecurityException
               
  • 可以看到這些類都是在reflect包下,注意的是Class類與其餘幾個類都沒有直接關系,隻是Class類提供了擷取那幾個類的對象的方法(上面已經詳細講述過了);

繼續閱讀