Java反射

一、反射的概念

Java反射是指在运行状态中,对任意一个类,都能知道这个类的所有属性及方法,对于任何一个对象,都能调用他的任何一个方法和属性,这种动态获取新的对象及动动态调用对象的方法的功能叫做反射。

二、Class类(java.lang.Class)

Java中万物皆对象,每个类也是一个对象,每个类的Java文件在编译的时候会产生同名的.class文件,这个.class文件包含了这个Java类的元数据信息,包括成员变量、属性、接口、方法等,生成class文件的同时会产生其对应的Class对象,并放在.class文件的末尾。当我们new一个新的对象或者引用静态成员变量时,Java虚拟机中的类加载器子系统会将对应的Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要的实例对象或者提供静态变量的引用值,在JVM中只有一个Class对象,即在内存中每个有且只有一个相应的Class对象。

三、使用方法

我们先新建一个Person类和Man类,需要注意一下他们的继承关系及成员变量和方法的修饰符** (正常情况下不会这么写,我方便后面方法说明,就刻意的给了不同的修饰符)**
Person.java:

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

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

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

Man.java

public class Man extends Person {
    private String address;
    public int phoneNum;
    
    public Man(){
    }
    public Man(String address){
        this.address = address;
    }
    
    public Man(int phoneNum,String address){
        this.address = address;
        this.phoneNum= phoneNum;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(int phoneNum) {
        this.phoneNum = phoneNum;
    }
}

3.1. 获取类的Class对象

前面我们讲到Java虚拟机会根据这个类型信息相关的Class对象创建我们需要的实例对象或者提供静态变量的引用值,所以我们要用到反射,就首先要获取到这个类的Class对象,获取Class对象有以下三种方法:
1.直接通过类.class,获取其对象

Class manClass = Man.class;

** 2.通过Class的forName方法,该方法如果路径找不到会抛出 ClassNotFoundException 异常**

    try {
          Class manClass = Class.forName("com.obex.practice.Man");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

3.通过类实例对象'getClass'方法

   Man man = new Man();
   Class manClass3 = man.getClass();

打印这个三个Class得到的结果都是一样的

  D/MainActivity: manClass1===class com.obex.practice.Man
  D/MainActivity: manClass2===class com.obex.practice.Man
  D/MainActivity: manClass3===class com.obex.practice.Man

拿到其Class对象,可以直接通过newInstance方法,获取到类的实例对象,并对其操作

  Man man = (Man) manClass1.newInstance();
  man.setPhoneNum(123);
  int phoneNum = man.getPhoneNum();
  Log.d("MainActivity", "getPhoneNum===" + phoneNum);

打印结果D/MainActivity: getPhoneNum===123

你可能有疑惑,绕来绕去又绕回来了,干嘛不直接new一个对象,非要绕一大圈,其实我们这里最主要的是拿到其Class对象,然后用Class对象去执行私有方法或设置私有变量

3.2通过反射获取构造方法

构造方法只针对本类

方法 说明
getConstructors() 获取当前类公有构造方法
getConstructor(Class… parameterTypes) 获取参数类型匹配的公有构造方法
getDeclaredConstructors() 获取当前类所有构造方法,包括私有。
getDeclaredConstructor(Class… parameterTypes) 获取所有参数类型匹配的构造方法(公有+私有

我们看前面Man.java类的构造方法,可以看到一个公有的无参合一个公有的单参构造方法,一个私有的双参构造方法。
1.getConstructors() 获取公有构造方法

 Constructor[] constructors = manClass1.getConstructors();
 for (Constructor c : constructors) {
      Log.d("MainActivity", "getConstructors===" + c);
     }
 D/MainActivity: getConstructors===public com.obex.practice.Man()
 D/MainActivity: getConstructors===public com.obex.practice.Man(java.lang.String)

从打印的结果可以看到,我们拿到了一个无参的构造方法和一个String参数的构造方法,而这两个构造方法刚好是用Public修饰的。

2.getDeclaredConstructors() 获取当前类所有构造方法,包括私有。

   Constructor[] constructors = manClass1.getDeclaredConstructors();
     for (Constructor c : constructors) {
         Log.d("MainActivity", "getConstructors===" + c);
        }
 D/MainActivity: getConstructors===public com.obex.practice.Man()
 D/MainActivity: getConstructors===private com.obex.practice.Man(int,java.lang.String)
 D/MainActivity: getConstructors===public com.obex.practice.Man(java.lang.String)

获取到了全部构造方法。

3.getConstructor(Class… parameterTypes) 获取参数类型匹配的公有构造方法

     Constructor constructors = null;
        try {
            constructors = manClass1.getConstructor(String.class); 
            Log.d("MainActivity", "getConstructors===" + constructors);     
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
 D/MainActivity: getConstructors===public com.obex.practice.Man(java.lang.String)

该方法只能获取公有构造,如果我们去获取私有的构造方法就会就会抛 NoSuchMethodException 异常。

4.getDeclaredConstructor(Class… parameterTypes)获取所有参数类型匹配的构造方法(公有+私有)

     try {
           Constructor constructorPublicDeclared  = manClass1.getDeclaredConstructor(String.class);
           Constructor constructorPrivateDeclared = manClass1.getDeclaredConstructor(int.class,String.class);
           Log.d("MainActivity", "constructorPublicDeclared===" + constructorPublicDeclared);
           Log.d("MainActivity", "constructorPrivateDeclared===" + constructorPrivateDeclared);
         } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
  D/MainActivity: constructorPublicDeclared===public com.obex.practice.Man(java.lang.String)
  D/MainActivity: constructorPrivateDeclared===private com.obex.practice.Man(int,java.lang.String)
  D/ActivityThread: add activity client record, r= ActivityRecord{9012f13 token=android.os.BinderProx

5.创建对象
拿到了构造方法我们直接调用newInstance()方法并传入对应参数就可以拿到类对象

try {    
    // 调用 newInstance 传入对应参数,获取Man实例并进行操作。  
    Man man = (Man) constructorPublicDeclared.newInstance("Test"); 
    String address = man.getAddress();   
    System.out.println("newInstance   address=" + address); 
} catch (InvocationTargetException e) {  
    e.printStackTrace(); }

 结果 D/MainActivity: address===Loen
 

3.2 获取方法

方法 说明
getMethods() 获取类本身及其父类所有公有方法
getMethod(String name, Class… parameterTypes) 获取类本身及其父类通过方法名参数类型指定的公有方法
getDeclaredMethods() 获取类本身所有方法
getDeclaredMethod(String name, Class… parameterTypes) 通过类本身通过方法名及参数类型获取本类指定的方法,无限制
1、Method[] getMethods() 获取当前类及其父类Public方法
 Man类的方法    
 D/MainActivity: getMethods===public java.lang.String com.obex.practice.Man.getAddress()
 D/MainActivity: getMethods===public void com.obex.practice.Man.setPhoneNum(int)
 D/MainActivity: getMethods===public int com.obex.practice.Man.getPhoneNum()
 D/MainActivity: getMethods===public void com.obex.practice.Man.setAddress(java.lang.String)
 
 Person类的方法
 D/MainActivity: getMethods===public void com.obex.practice.Person.setAge(int)
 D/MainActivity: getMethods===public int com.obex.practice.Person.getAge()
 D/MainActivity: getMethods===public java.lang.String com.obex.practice.Person.getName()
 D/MainActivity: getMethods===public void com.obex.practice.Person.setName(java.lang.String)
 
 Object类的方法
 D/MainActivity: getMethods===public final java.lang.Class java.lang.Object.getClass()
 D/MainActivity: getMethods===public int java.lang.Object.hashCode()
 D/MainActivity: getMethods===public final native void java.lang.Object.notify()
 D/MainActivity: getMethods===public final native void java.lang.Object.notifyAll()
 D/MainActivity: getMethods===public boolean java.lang.Object.equals(java.lang.Object)
 D/MainActivity: getMethods===public java.lang.String java.lang.Object.toString()
 D/MainActivity: getMethods===public final native void java.lang.Object.wait() throws java.lang.InterruptedException
 D/MainActivity: getMethods===public final void java.lang.Object.wait(long) throws java.lang.InterruptedException
 D/MainActivity: getMethods===public final native void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

可以看到获取的全是Public修饰的方法,不光获取了自身类,其父类Person类的公有方法一样打印出来了,你们可能会说Object类是什么鬼?Object类位于java.lang包中,是所有java类的父类。

2、Method[] getDeclaredMethods() 获取本类的所有方法
        Method[] methods = manClass1.getDeclaredMethods();
        for (Method method : methods){
            Log.d("MainActivity", "getDeclaredMethods===" + method);
        }

结果:

 D/MainActivity: getDeclaredMethods===public java.lang.String com.obex.practice.Man.getAddress()
 D/MainActivity: getDeclaredMethods===public int com.obex.practice.Man.getPhoneNum()
 D/MainActivity: getDeclaredMethods===public void com.obex.practice.Man.setAddress(java.lang.String)
 D/MainActivity: getDeclaredMethods===public void com.obex.practice.Man.setPhoneNum(int)
3、Method getMethod(String name, Class… parameterTypes)

获取本类及父类指定方法名和参数Class对象的公有方法,比如获取其父类的setName方法和自己的getPhoneNum方法
public void setName(String name)
public int getPhoneNum()

       try {
            Method setName = manClass1.getMethod("setName", String.class);
            Method getPhoneNum = manClass1.getMethod("getPhoneNum");
            Log.d("MainActivity", "getMethod(String name, Class... parameterTypes)--->" + setName);
            Log.d("MainActivity", "getMethod(String name, Class... parameterTypes)--->" + getPhoneNum);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }   

结果:

 D/MainActivity: getMethod(String name, Class... parameterTypes)--->public void com.obex.practice.Person.setName(java.lang.String)
 D/MainActivity: getMethod(String name, Class... parameterTypes)--->public int com.obex.practice.Man.getPhoneNum()
4、Method getDeclaredMethod(String name, Class… parameterTypes)
 获取本类指定方法名和参数Class对象的方法,无限制
      try {
            Method setAddress = manClass1.getDeclaredMethod("setAddress", String.class);
            Method getPhoneNum = manClass1.getDeclaredMethod("getPhoneNum");
            Log.d("MainActivity", "getMethod(String name, Class... parameterTypes)--->" + setAddress);
            Log.d("MainActivity", "getMethod(String name, Class... parameterTypes)--->" + getPhoneNum);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

结果

  D/MainActivity: getMethod(String name, Class... parameterTypes)--->public void com.obex.practice.Man.setAddress(java.lang.String)
  D/MainActivity: getMethod(String name, Class... parameterTypes)--->private int com.obex.practice.Man.getPhoneNum()
5、方法调用 拿到方法后,方法调用使用:
Object invoke(Object obj, Object... args)
           // 私有方法赋予权限
            setAddress.setAccessible(true);
            setAddress.invoke(manClass1.newInstance(), "重庆市");

setAccessible 当我们需要对非公有方法进行操作的时候,需要先调用此方法赋予权限,不然也会抛异常

3.3 获取成员变量

1、Field[] getFields() 获取本类及父类所有公有变量
        Field[] fields = manClass1.getFields();
        for (Field field : fields) {
            Log.d("MainActivity", "field====" + field);
        }

结果 获取到public变量:

 D/MainActivity: field====public int com.obex.practice.Man.phoneNum
 D/MainActivity: field====public int com.obex.practice.Person.age
2、Field[] getDeclaredFields()

获取本类所有成员变量

        Field[] fields = manClass1.getDeclaredFields();
        for (Field field : fields) {
            Log.d("MainActivity", "getDeclaredFields====" + field);
        }      

结果:

 D/MainActivity: getDeclaredFields====private java.lang.String com.obex.practice.Man.address
 D/MainActivity: getDeclaredFields====public int com.obex.practice.Man.phoneNum
3、Field getField(String name)

获取本类或者父类指定的成员变量

      try {
            Field age = manClass1.getField("age");
            Field phoneNum = manClass1.getField("phoneNum");
            Log.d("MainActivity", "age====" + age);
            Log.d("MainActivity", "phoneNum====" + phoneNum);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

结果:

 D/MainActivity: age====public int com.obex.practice.Person.age
 D/MainActivity: phoneNum====public int com.obex.practice.Man.phoneNum
4、Field getDeclaredField(String name)

获取本类指定的成员变量,无限制

     try {
            Field address = manClass1.getDeclaredField("address");
            Field phoneNum = manClass1.getDeclaredField("phoneNum");
            Log.d("MainActivity", "address====" + address);
            Log.d("MainActivity", "phoneNum====" + phoneNum);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

结果:

 D/MainActivity: address====private java.lang.String com.obex.practice.Man.address
 D/MainActivity: phoneNum====public int com.obex.practice.Man.phoneNum
5、成员变量赋值
            address.setAccessible(true);
            address.set(manClass1.newInstance(),"深圳市");
            phoneNum.set(manClass1.newInstance(),30);

四、实战

Android 中 StorageManager 类中的 getVolumePaths() 方法,该方法为隐藏方法,没办法正常调用,但是在实际使用中我们也可能用上,如果你有系统权限,那你就可以像 Android SD卡及U盘插拔状态监听及内容读取 这样为所欲为,如果没有权限,那你就可以通过反射来实现了。

 /**
     * Returns list of paths for all mountable volumes.
     * @hide
     */
    @Deprecated
    public @NonNull String[] getVolumePaths() {
        StorageVolume[] volumes = getVolumeList();
        int count = volumes.length;
        String[] paths = new String[count];
        for (int i = 0; i < count; i++) {
            paths[i] = volumes[i].getPath();
        }
        return paths;
    }

虽然方法时public修饰,但是使用了@hide注解,我们直接使用getVolumePaths()是找不到的,如下图:
image.png

4.1 使用反射获取

1.拿到StorageManager的Class对象

   StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
   Class storageClass = storageManager.getClass();

2.反射获取getVolumePaths方法

     try {
            Method method = storageClass.getDeclaredMethod("getVolumePaths");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

3.方法调用 ,如果方法非public修饰,还需要 使用 setAccessible(true) 赋予权限

 String[] paths = (String[]) method.invoke(storageManager,null);

4.完整代码

/**  
* 反射调用  StorageManager ——>  getVolumePaths 方法    
*/  
public void getVolumePaths() {       
    StorageManager storageManager = (StorageManager)  this.getSystemService(Context.STORAGE_SERVICE); 
    Class storageManagerClass = sm.getClass();
    try {
        // 反射拿到getVolumePaths方法    
        Method method =  storageManagerClass.getDeclaredMethod("getVolumePaths"); 
        String[] paths = (String[]) method.invoke(storageManager, null);  
        for (String path : paths) {              
            Log.d(TAG, "getVolumePaths: path=" + path);
        }       
    } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
        e.printStackTrace();         
        Log.e(TAG, "getVolumePaths: " + e.getLocalizedMessage(), e);     
    }   
}


五、结论

其实咋一看,反射还是很简单,主要区分私有和公有,以及获取的是本类的还是本类加父类,当然,还有更多方法,比如你可以通过获取的方法,获取它的修饰符,变量等等,方法类似,本文内容还是比较浅,敲一遍基本就知道是咋回事了,看的话可能有点绕,建议自己动手敲一遍,还有其其它获取反射中的知识点你也可以去拓展一下,反射在Android中使用还是挺广,后续可能会说到热修复知识点,其中也涉及到反射知识。

你可能感兴趣的:(Java反射)