深入理解Java反射机制(1)---应用篇

0. 引言

  在实际的Android开发中,很少接触发射。最近使用MVVM重构MapTool app,view和viewModel通过泛型实现动态绑定并在base类中完成一些共有的初始化逻辑,由于base类在编译期并不确定泛型,所以无法按照常规的new对象的逻辑来获取viewModel对象,只能通过反射在运行时创建并初始化。
  后来搜索java反射相关文档,看到了一句话“反射是框架的灵魂”,深有同感。看过很多的源码,包括LiveData,ViewModel 这些搭建MVVM的开源库,都不乏发射的影子。自己在实践过程中,又遇到了反射帮忙解决的问题。所以决定对反射做一个系统的总结。

1. 什么是Java反射

  JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
  注意到,这里提到的一个关键点"动态获取",相较静态可以降低耦合性,所以极为适合搭建框架。

2. 认识class对象

  我们知道每一个.java文件,经过编译之后,都会生成一个或者多个.class文件。每一个对象对应一个.class文件,.class文件被JVM加载到内存中,会在堆区自动创建一个对应的class对象,这个对象就是java反射的基础。(这里我们先不展开对类加载过程的详解)
  要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
  Class对象在java api中有专门的定义,java反射其实本质上是调用Class对象相关方法实现的

获取Class对象有三种方式,

  1. Object ——> getClass();
  2. 任何数据类型(包括基本数据类型)都有一个“静态”的class属性,String.class,Integer.class
  3. 通过Class类的静态方法:forName(String className)

  前两种能够拿到类对象没必要用反射,反射主要是使用第三种方式,拿到了类对象,你就可以为所欲为了

2. java反射的典型使用

反射基本上有三种常用场景:

  1. 根据类名创建对象(类名可以从配置文件读取,不用new,达到解耦)。
  2. 获取成员方法,用Method.invoke执行方法。
  3. 获取成员变量并操作。
    定义测试类Cat如下,它作为Main class的内部类:
	static class Cat {
        private String name;
        private int age;
        private boolean sex;
        Cat(String name) {
            this.name = name;
            System.out.println("Default 构造方法, name = " + name);
        }

        public Cat() {
            System.out.println("public 构造方法");
        }

        public Cat(int age) {
            this.age = age;
            System.out.println("public 构造方法, age = " + age);
        }

        public Cat(String name, int age) {
            this.name = name;
            this.age = age;
            System.out.println("public 构造方法, name = " + name + ", age = " + age);
        }

        protected Cat(boolean sex) {
            this.sex = sex;
            System.out.println("protected 构造方法, sex = " + sex);
        }

        private Cat(String name, boolean sex) {
            this.name = name;
            this.sex = sex;
            System.out.println("public 构造方法, name = " + name + ", sex = " + sex);
        }
        
		public void setName(String name) {
            this.name = name;
            System.out.println("公有方法:setName this.name = " + name);
        }

        private void showName() {
            System.out.println("私有方法:showName this.name = " + name);
        }

        protected void setAge(int age) {
            this.age = age;
            System.out.println("保护方法:setAge this.age = " + age);
        }

        void showAge() {
            System.out.println("default方法:showAge this.age = " + age);
        }

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

2.1 根据类名创建对象

根据类名获取构造方法,并创建对象,主要使用以下6个api:

  1. Class.forName() : 根据类名获取对象。
  2. Class.getConstructors:获取所有public构造方法。
  3. Class.getDeclaredConstructors:获取所有构造方法,私有构造方法。
  4. Class.getConstructor : 获取指定参数的某一个public构造方法。
  5. Class.getDeclaredConstructor : 获取指定参数的某一构造方法。
  6. Constructor.newInstance:创建该构造方法的声明类的实例对象。

如下代码和最终打印结果:

public class Main {
    public static void main(String[] args)  {
	// write your code here
        try {
            System.out.println("获取类对象");
            Class<?> clz = Class.forName("com.company.Main$Cat");//注意内部类用$连接

            System.out.println("获取所有 public 构造方法");
            Constructor<?>[] pubConstructors = clz.getConstructors();
            for (Constructor<?> c : pubConstructors) {
                System.out.println(c);
            }

            System.out.println("获取所有 构造方法(public,private,default,protected)");
            Constructor<?>[] allConstructors = clz.getDeclaredConstructors();
            for (Constructor<?> c : allConstructors) {
                System.out.println(c);
            }

            System.out.println("获取 public 无参和带int参数构造方法,并实例化,打印toString");
            try {
                Constructor<?> noParamC = clz.getConstructor(null);//等价于不填参数
                System.out.println("无参Constructor = " + noParamC);

                try {
                    Object obj = noParamC.newInstance();
                    Cat cat = (Cat) obj;
                    System.out.println("打印无参 cat = " + cat.toString());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

            try {
                Constructor<?> paramIntC = clz.getConstructor(int.class);//不能使用Integer
                System.out.println("int参数Constructor = " + paramIntC);
                try {
                    Object obj = paramIntC.newInstance(100);
                    Cat cat = (Cat) obj;
                    System.out.println("打印int参数 cat = " + cat.toString());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }


            System.out.println("获取 private 构造方法,并实例化,打印toString");
            try {
                Constructor<?> privC = clz.getDeclaredConstructor(String.class, boolean.class);

                try {
                    privC.setAccessible(true);
                    Object obj = privC.newInstance("xiaomi", false);
                    Cat cat = (Cat) obj;
                    System.out.println("打印String, boolean 参数 private cat = " + cat.toString());
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}
输出结果如下:
获取类对象
获取所有 public 构造方法
public com.company.Main$Cat(java.lang.String,int)
public com.company.Main$Cat()
public com.company.Main$Cat(int)

获取所有 构造方法(public,private,default,protected)
private com.company.Main$Cat(java.lang.String,boolean)
protected com.company.Main$Cat(boolean)
public com.company.Main$Cat(java.lang.String,int)
com.company.Main$Cat(java.lang.String)
public com.company.Main$Cat()
public com.company.Main$Cat(int)

获取 public 无参和带int参数构造方法,并实例化,打印toString
无参Constructor = public com.company.Main$Cat()
public 构造方法
打印无参 cat = Cat{name='null', age=0, sex=false}
int参数Constructor = public com.company.Main$Cat(int)
public 构造方法, age = 100
打印int参数 cat = Cat{name='null', age=100, sex=false}

获取 private 构造方法,并实例化,打印toString
public 构造方法, name = xiaomi, sex = false
打印String, boolean 参数 private cat = Cat{name='xiaomi', age=0, sex=false}

通过以上测试,有几点需要注意

  1. 注意内部类名用$连接。
  2. 注意int.class和Integer.class作为参数匹配方法时候是不同的。
  3. 私有构造方法,必须setAccessible(true)才能够用于实例化。
  4. 非静态内部类会导致getConstructor出问题,参见:https://www.iteye.com/blog/sha-tians-2043882

2.2 获取成员变量并使用

获取成员变量主要用到以下几个api:

  1. Class.getFileds:获取所有public字段。
  2. Class.getDeclaredFields:获取所有字段。
  3. Class.getField:根据String的字段名字,获取某一个public字段。
  4. Class.getDeclaredField:根据String的字段名,获取某一个字段。注意private字段必须SetAccessible才能够使用。

测试代码和打印结果如下:

public class Main {

    public static void main(String[] args)  {
	// write your code here
        try {
            System.out.println("获取类对象");
            Class<?> clz = Class.forName("com.company.Main$Cat");//注意内部类用$连接
            System.out.println("获取所有 pubic 字段");
            Field[] pubFields = clz.getFields();
            for (Field f : pubFields) {
                System.out.println(f);
            }

            System.out.println("获取所有字段, public private protected Default");
            Field[] allFields = clz.getDeclaredFields();
            for (Field f : allFields) {
                System.out.println(f);
            }

            System.out.println("获取某一public 字段,并使用");
            try {
                Field pubF = clz.getField("name");
                Object obj = clz.getConstructor().newInstance();
                pubF.set(obj, "小咪咪");
                System.out.println(obj.toString());
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            System.out.println("获取某一 private 字段,并使用");

            try {
                Field privF = clz.getDeclaredField("sex");
                Object obj = clz.getConstructor().newInstance();
                privF.setAccessible(true);
                privF.set(obj, true);
                System.out.println(obj.toString());
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}
打印结果如下:
获取类对象
获取所有 pubic 字段
public java.lang.String com.company.Main$Cat.name

获取所有字段, public private protected Default
public java.lang.String com.company.Main$Cat.name
int com.company.Main$Cat.age
private boolean com.company.Main$Cat.sex

获取某一public 字段,并使用
public 构造方法
Cat{name='小咪咪', age=0, sex=false}

获取某一 private 字段,并使用
public 构造方法
Cat{name='null', age=0, sex=true}

2.3 获取成员方法并调用

获取方法主要使用以下api:

  1. Class.getMethods:获取所有public方法,包括从父类继承的。
  2. Class.getDeclaredMethods:获取所有方法,不包括从父类继承的,只包含本类内声明的。
  3. Class.getMethod:函数名字加参数列表,获取某一public方法。
  4. Class.getDeclaredMethod:函数名字加参数列表,获取某一方法。注意只有private需要setAccessible,protected,default不用。

注意:这里getDeclaredMethod之所以没有获取父类的private方法,是因为子类看不到,这是符合逻辑的。
测试代码和输出结果如下:

public class Main {

    public static void main(String[] args)  {
	// write your code here
        try {
            System.out.println("获取类对象");
            Class<?> clz = Class.forName("com.company.Main$Cat");//注意内部类用$连接

            System.out.println("获取所有 public 方法");
            Method[] pubMs = clz.getMethods();//包含从父类继承的所有public方法
            for (Method m : pubMs) {
                System.out.println(m);
            }

            System.out.println("获取所有 方法, private public protected default");
            Method[] allMs = clz.getDeclaredMethods();//只包含本类内声明的方法
            for (Method m : allMs) {
                System.out.println(m);
            }

            System.out.println("获取public setName 方法,并调用");
            try {
                Method setName = clz.getMethod("setName", String.class);
                System.out.println(setName);
                Object obj = clz.getConstructor().newInstance();
                setName.invoke(obj, "大咪咪");
                System.out.println(obj.toString());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

            System.out.println("获取private showName 方法,并调用");
            try {
                Method showName = clz.getDeclaredMethod("showName");
                System.out.println(showName);
                Object obj = clz.getConstructor().newInstance();
                showName.setAccessible(true);
                showName.invoke(obj);
                System.out.println(obj.toString());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

            System.out.println("获取protected setAge 方法,并调用");
            try {
                Method setAge = clz.getDeclaredMethod("setAge", int.class);
                System.out.println(setAge);
                Object obj = clz.getConstructor().newInstance();
                setAge.invoke(obj, 123);
                System.out.println(obj.toString());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}
输出打印如下:
获取类对象
获取所有 public 方法
public java.lang.String com.company.Main$Cat.toString()
public void com.company.Main$Cat.setName(java.lang.String)
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()

获取所有 方法, private public protected default
public java.lang.String com.company.Main$Cat.toString()
public void com.company.Main$Cat.setName(java.lang.String)
void com.company.Main$Cat.showAge()
private void com.company.Main$Cat.showName()
protected void com.company.Main$Cat.setAge(int)

获取public setName 方法,并调用
public void com.company.Main$Cat.setName(java.lang.String)
public 构造方法
公有方法:setName this.name = 大咪咪
Cat{name='大咪咪', age=0, sex=false}

获取private showName 方法,并调用
private void com.company.Main$Cat.showName()
public 构造方法
私有方法:showName this.name = null
Cat{name='null', age=0, sex=false}

获取protected setAge 方法,并调用
protected void com.company.Main$Cat.setAge(int)
public 构造方法
保护方法:setAge this.age = 123
Cat{name='null', age=123, sex=false}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值