Java反射,带你全面解析Java反射

31 篇文章 0 订阅

一、前言

相信很多没有听说过反射,这也很正常,因为在一般的开发中反射基本上用不着、但是我再Android开发中经常涉及到内存优化、性能优化的问题,需要使用反射,那么今天带给大家一顿丰富的Java反射大餐。

二、反射定义

反射就是在运行中获取你想获取的一个类中的类实例、方法、成员变量。即使这个类不对外暴露

三、反射的优点

  1. 可以在程序运行中,操作这些对象
  2. 解耦、提高程序的可扩展性

四、反射的机制

在这里插入图片描述

五、反射中Class对象的获取方式

  1. Class.forName(“全类名”):将字节码文件加载进内存、返回Class对象
//下面这个做java开发应该都见到过吧,java访问mysql时候加载驱动是需要用到的
 Class.forName("com.mysql.jdbc.Driver");
 //例子	
 Class<?> person = Class.forName("bean.Person");
 System.out.println(person); //  打印class bean.Person
  1. 类型.class,通过类型的.class属性来获取
  Class<?> person2 = Person.class;
        System.out.println(person2); 

3.通过对象.getClass来获取,这种方式需要一个对象

Person p = new Person();
        Class<?> person3 = p.getClass();
        System.out.println(person3); //  打印class bean.Person

那么上面的三种方式获取的Person对象是否相等呢,我们通过比较堆内存地址来判断(这里提前告诉大家,其实是一样)
在这里插入图片描述

通过对对象地址的比较,我们发现三个对象指向的堆内存一直是一模一样的,那也就是三个对象是同一个,=> 这三种方式都可行是一样的。

那么上面三种方式一般的用在哪里呢

  • Class.forName 多用于配置文件,将类名定义在字节码文件中
  • 类名.class 多用于参数的传递
  • 对象.getClass() 多用于对象获取字节码,有对象获取字节码

六、Class的功能

获取功能:

**这里郑重声明在获取方法属性的时候没有带Declared的,只能获取共有的,不能获取私有的,详细的请看下面的API介绍**
  • 获取成员变量
//返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 类对象。
Field[] getFields()
//通过制定名称来获取指定的字段,这个字段也必须是公共的字段
Field getField(String name) 
//返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象。 这个可以使private
Field[]  getDeclaredFields() 
//通过指定名称来获取指定的字段,这种访问也可以访问私有变量
Field getDeclaredField(String name)
  • 获取构造方法
//返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。 此方法不能访问私有的构造方法 
Constructor<T> getConstructor(<?>... parameterTypes) 
// 返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。 此方法不能访问私有的构造方法 
Constructor<?>[] getConstructors() 
  
//返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定类函数。 可以访问私有的构造方法
Constructor<T> getDeclaredConstructor(<?>... parameterTypes) 
//返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组类 。可以访问私有的构造方法 
Constructor<?>[] getDeclaredConstructors() 

  • 获取成员方法
//返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类对象。这个只能返回公共的方法
Method getMethod(String name,<?>... parameterTypes) 
//返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。 也只能访问公共的方法
Method[] getMethods()
//返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 类对象。 
Method getDeclaredMethod(String name,<?>... parameterTypes)  
//返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。 
Method[] getDeclaredMethods() 
  • 获取类名
//返回由 类对象表示的实体(类,接口,数组类,原始类型或空白)的名称,作为 String 。 
String getName() 

七、字段Field的相关使用

1、定义一个Person类

public class Person {
    public String name = "hongbiao";
    int age;
    private String sex="未知";
    protected float salary;

    public void running(){
        System.out.println("标哥正在跑步");
    }
    public void eat(){
        System.out.println("标哥正在吃饭");
    }
    public Person(String name, int age, String sex, float salary) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
    }

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

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

    public int  showboune(){
        return 10000;
    }
    public void printSalary(int in){
        System.out.println(in+10);
    }
    public Person() {
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }

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

2、获取成员变量
在这里插入图片描述
从上面的结果我们可以看到:

getDeclaredFields()可以获取私有的属性,而getFields()只能获取共有的

3、对成员变量进行操作
获取到的成员变量无非进行两种操作

  1. 获取成员变量里面的值
  2. 设置成员变量里面的值
    在这里插入图片描述
    我们可以获取类里面的所有方法,包括私有的方法,反射是可以忽略修饰符的,但是当我们访问获取私有变量的值的时候,我们会发现报错:如下图
    在这里插入图片描述
    其实在修改访问权限为私有的字段时候,要忽略访问修饰符的检查如下图所示,这样就可以修改了:
    在这里插入图片描述

七、反射构造方法的使用

构造方法的获取主要使用来创建对象,拿到构造方法,设置值,就相当于创建爱你了一个对象。如下图的相关操作。
在这里插入图片描述

八、反射成员方法的使用介绍

反射获取类的方法主要是为了调用,在使用的时候需要注意几点

 带参数的方法需要在获取的时候声明
 1. personClass.getMethod("printSalary",int.class);
 调用的时候使用的invoke()
 2. 调用无参数的方法
    method2.invoke(p);
    调用带参数的方法
    method.invoke(p,1000);
方法也存在暴力反射,针对那些设置了修饰符的方法
3.methods[i].setAccessible(true);//也可以进行暴力反射
    public static void  test5()throws Exception{
        //1 获取Person的class对象
        Class personClass = Person.class;
        //这种事方法中需要传递参数
        Method method = personClass.getMethod("printSalary",int.class);
        //这种事方法中不需要传递参数
        Method method2 = personClass.getMethod("running");
        Person p = new Person();
        //调用无参数的方法
        method2.invoke(p);
        //调用带参数的方法
        method.invoke(p,1000);
        System.out.println("-----------------获取所有的方法------------");
        Method[] methods = personClass.getMethods();
        for (int i = 0; i < methods.length; i++) {
            //会包含object的方法
            System.out.println(methods[i]);
            methods[i].setAccessible(true);//也可以进行暴力反射
        }
        System.out.println("-----------------获取方法名称-----------");
        for (int i = 0; i < methods.length; i++) {
            //会包含object的方法
            System.out.println(methods[i].getName());
            methods[i].setAccessible(true);//也可以进行暴力反射
        }
        System.out.println("-----------------获取类名-----------");
        System.out.println(personClass.getName());

    }

打印结果

标哥正在跑步
1010
-----------------获取所有的方法------------
public java.lang.String bean.Person.toString()
public java.lang.String bean.Person.getName()
public void bean.Person.setName(java.lang.String)
public void bean.Person.running()
public void bean.Person.setSex(java.lang.String)
public float bean.Person.getSalary()
public void bean.Person.setAge(int)
public java.lang.String bean.Person.getSex()
public void bean.Person.setSalary(float)
public void bean.Person.printSalary(int)
public int bean.Person.showboune()
public int bean.Person.getAge()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
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 boolean java.lang.Object.equals(java.lang.Object)
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()
-----------------获取方法名称-----------
toString
getName
setName
running
setSex
getSalary
setAge
getSex
setSalary
printSalary
showboune
getAge
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll
-----------------获取类名-----------
bean.Person

Process finished with exit code 0

九、反射的魅力所在

利用类加载器运行类中的方法,并且不用改变代码可以调用其他类中的方法

Person p = new person();
p.run();

这是我们平调用方法的常见思路,今天利用反射换一种调用方式,更改配置文件中的的参数就能控制调用的方法,是不是很神奇呢,接下来开始进入重点。
在这里插入图片描述

一、创建一个类

这里我们还是用的之前的Person

二、创建一个配置文件 pro.properties
className = bean.Person
methodName = eat

里面设置两个字段,第一个字段是类的全包名
第二个字段是方法名
在这里插入图片描述

三、使用

使用方法如下:
读取配置文件,利用类加载器根据配置文件中的内容反射Person这个对象,再通过调用对象中的方法进行实现

    public static void test6() throws Exception{
        //创建Properties
        Properties properties = new Properties();
        //获取文件的路径 利用类加载器
        ClassLoader loader = ReflectTest.class.getClassLoader();
        InputStream pro = loader.getResourceAsStream("pro.properties");
        //加载配置文件
        properties.load(pro);
        //获取配置文件中定义的数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        //加载该类进内存
        Class mClass = Class.forName(className);
        //创建对象
        Object obj = mClass.newInstance();
        Method method = mClass.getMethod(methodName);
        //调用方法
        method.invoke(obj);

    }

其实上面就像一个小框架,再实际开发中我们不必要去更改这里面的很多代码,只需要更改配置文件中的内容,当然上面的代码是基于无参数的方法进行测试的,有参数的肯定会稍微复杂点,但是掌握好规律就没什么问题。

十、Android中利用反射解决内存泄露的问题

InputMethodManager 在Activity退出的时刻可能会引用context导致内存不能正常退出而引起内存泄漏,引起需要手动将里面的相关参数置空,破坏引用链

public static void fixInputMethodManagerLeak(Context destContext) {
        if (destContext == null) {
            return;
        }
 
        InputMethodManager inputMethodManager = (InputMethodManager) destContext.getSystemService(Context.INPUT_METHOD_SERVICE);
        if (inputMethodManager == null) {
            return;
        }
 
        String [] viewArray = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
        Field filed;
        Object filedObject;
 
        for (String view:viewArray) {
            try{
            //获取类中指定的字段
                filed = inputMethodManager.getClass().getDeclaredField(view);
                if (!filed.isAccessible()) {
                    filed.setAccessible(true);
                }
                filedObject = filed.get(inputMethodManager);
                if (filedObject != null && filedObject instanceof View) {
                    View fileView = (View) filedObject;
                    if (fileView.getContext() == destContext) { // 被InputMethodManager持有引用的context是想要目标销毁的
                        filed.set(inputMethodManager, null); // 置空,破坏掉path to gc节点
                    } else {
                        break;// 不是想要目标销毁的,即为又进了另一层界面了,不要处理,避免影响原逻辑,也就不用继续for循环了
                    }
                }
            }catch(Throwable t){
                t.printStackTrace();
            }
        }
    }

好了、到此结束,欢迎阅读,献上完整代码

package chapter;

import bean.Person;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectTest {
    //反射就是获取你想获取的一个类中的类实例、方法、成员变量。即使这个类不对外暴露


    /**
     *
     *  Class对象的功能
     *  1、获取成员变量
     *  Field[] getFields()
     *  Field getField(String name)
     *
     *  Field[] getDeclaredFields()
     *  Field   getDeclaredField()
     *
     *  2、获取构造方法
     *  Constructor<?>[] getConstructors()
     *  Constrctor<T> getConstructor(类<?>... parameterTypes)
     *  Constructor<?>[] getDeclaredConstructors()
     *
     *  2、获取成员方法
     *  Method[] getMethods()
     *  Method gteMethod(String name,类<?>... parameterTypes)
     */
    public static void main(String[] args) throws Exception {
        test6();

    }

    public static void test6() throws Exception{
        //创建Properties
        Properties properties = new Properties();
        //获取文件的路径 利用类加载器
        ClassLoader loader = ReflectTest.class.getClassLoader();
        InputStream pro = loader.getResourceAsStream("pro.properties");
        //加载配置文件
        properties.load(pro);
        //获取配置文件中定义的数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        //加载该类进内存
        Class mClass = Class.forName(className);
        //创建对象
        Object obj = mClass.newInstance();
        Method method = mClass.getMethod(methodName);
        //调用方法
        method.invoke(obj);

    }
    public static void  test5()throws Exception{
        //1 获取Person的class对象
        Class personClass = Person.class;
        //这种事方法中需要传递参数
        Method method = personClass.getMethod("printSalary",int.class);
        //这种事方法中不需要传递参数
        Method method2 = personClass.getMethod("running");
        Person p = new Person();
        //调用无参数的方法
        method2.invoke(p);
        //调用带参数的方法
        method.invoke(p,1000);
        System.out.println("-----------------获取所有的方法------------");
        Method[] methods = personClass.getMethods();
        for (int i = 0; i < methods.length; i++) {
            //会包含object的方法
            System.out.println(methods[i]);
            methods[i].setAccessible(true);//也可以进行暴力反射
        }
        System.out.println("-----------------获取方法名称-----------");
        for (int i = 0; i < methods.length; i++) {
            //会包含object的方法
            System.out.println(methods[i].getName());
            methods[i].setAccessible(true);//也可以进行暴力反射
        }
        System.out.println("-----------------获取类名-----------");
        System.out.println(personClass.getName());

    }


    public static void  test4()throws Exception{
        //1 获取Person的class对象
        Class personClass = Person.class;
        //2 获取构造方法
        Constructor constructor = personClass.getConstructor(String.class, int.class);
        System.out.println(constructor);//public bean.Person(java.lang.String,int)
        //构造方法是为了创建对象,查看api 是有一个创建对象的api
        Object  person  = constructor.newInstance("hongbiaop",18);
        System.out.println(person);
        System.out.println("=====================");
        //如果是利用空参构造方法创建对象就不用这么麻烦了,直接调用class.newInstance()
        Object obj2 = personClass.newInstance();
        System.out.println(obj2);
    }

    public static void  test3()throws Exception{
        //1 获取Person的class对象
        Class personClass = Person.class;
        System.out.println("========getDeclaredFields");
        Person person = new Person();
        Field  field = personClass.getDeclaredField("sex");
        field.setAccessible(true);//暴力反射
        System.out.println("========获取属性值");
        Object obj = field.get(person);
        System.out.println(obj);
        System.out.println("========设置属性值");
        field.set(person,"男");
        System.out.println(person.toString());
    }

    public static void  test2()throws Exception{
        //1 获取Person的class对象
        Class personClass = Person.class;
        System.out.println("========getDeclaredFields");
        Field[]  fields = personClass.getDeclaredFields();
        for (int i = 0; i <fields.length ; i++) {
            System.out.println(fields[i]);
        }
        System.out.println("========getFields");
        Field[]  fields2 = personClass.getFields();
        for (int i = 0; i <fields2.length ; i++) {
            System.out.println(fields[i]);
        }

    }
    public static void  test1()throws Exception{
        System.out.println("-------------------------------------1");
        Class person = Class.forName("bean.Person");
        System.out.println(person); //  打印class bean.Person
        System.out.println("-------------------------------------2");
        Class<?> person2 = Person.class;
        System.out.println(person2); //  打印class bean.Person
        System.out.println("-------------------------------------3");
        Person p = new Person();
        Class<?> person3 = p.getClass();
        System.out.println(person3); //  打印class bean.Person
        System.out.println("检查堆内存地址是否相同-----------------------------3");
        System.out.println(person==person2);
        System.out.println(person3==person2);

    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值