部分引用:Java基础之反射
反射
反射是框架设计的灵魂
反射的概述
JAVA反射机制:
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射的特点:动态获取、动态创建、动态调用
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象。
所以反射的原理在于class类对象。它把java类中的各种成分映射成一个个的Java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
回顾一下类的加载过程
其中这个Class对象很特殊。我们先了解一下这个Class类
查看Class类在java中的api详解(1.7的API)
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
Class没有公共的构造方法,方法共有64个,以下是个人总结归纳的一些常用方法。
以下代码为简洁起见,约定:c = Class对象的变量名
注意:要读取私有的属性和方法,需要先取消访问控制,设置可见性。
c.setAccessible(true);
获得Class对象
- 通过Object类中的getClass() 方法
Person p = new Person();
Class c = p.getClass();
- 通过 类名.class 获取到字节码文件对象
Class c = Person.class;
- 通过Class类中的静态方法(推荐)
Class c = Class.forName(全类名);
获得Class类名
- 获取全类名
Class c = p.getClass();
String name = c.getName();
- 获取类名
Class c = P.getclass();
String name = c.getSimpleName();
获取指定类的构造方法
- 获取public修饰的指定参数的构造方法
Constructor con = c.getConstructor(String.class);
- 获取任意修饰的指定参数的构造方法
Constructor con = c.getDeclaredConstructor(String.class);
- 获取public修饰的所有构造方法
Constructor[] cons = c.getConstructors();
- 获取任意修饰的所有构造方法
Constructor[] cons = c.getDeclaredConstructors();
创建实例
- 使用Class对象的newInstance()方法
Class<?> c = String.class;
Object str = c.newInstance();
- 通过获取构造器对象,调用newInstance()方法
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
获取变量并赋值
- 获取public修饰的指定变量(包括继承变量)
Field f = c.getField("age");
- 获取任意修饰的指定变量(不包括继承变量)
Field privateField = c.getDeclaredField("privateAge");
- 获取public修饰的所有变量
Field[] f = c.getFields();
- 获取任意修饰的所有变量
Field[] f = c.getDeclaredFields();
- 为属性赋值(方法一)
Person p = new Person();
Field f = p.getClass().getDeclaredField("privateAge");
//访问私有属性要设置可见性
f.setAccessible(true);
f.set( p , value );
- 为属性赋值(方法二)
Method setPrivateAge = p.getClass().getMethod("setPrivateAge", String.class);
setPrivateAge.invoke( p , "2333" );
以上两种设置属性的方法区别在于:
- 方法一是直接访问属性,设置属性
- 方法二是调用了公开的set属性方法,设置属性,优点是可以同时执行该set方法里面的代码 (前提是有该方法,下面会继续说获取方法的代码)
获取方法并调用方法
- 获取public修饰的指定名字指定参数的方法
//获取methodClass类的add方法
Method method = c.getMethod("add", int.class, int.class);
- 获取public修饰的所有方法(包括继承类的公用方法)
Method[] methods = c.getMethods();
- 获取任意修饰的所有方法(不包括继承的方法)
Method[] declaredMethods = c.getDeclaredMethods();
- 使用指定方法(private多加步设置可见性)
//获取methodClass类的add方法
Method method = c.getMethod("add",int.class,int.class);
//调用method对应的方法 => add(1,4)
Object result = method.invoke(obj,1,4);
反射使用和操作
泛型擦除
使用反射,向有泛型约束的集合中添加任意类型的元素
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
//添加元素到集合
list.add(new Integer(30));
list.add(new Integer("12345"));
list.add(123);
//list.add("哈哈");//因为有泛型类型的约束,会报错
System.out.println(list);
//通过反射技术,实现添加任意类型的元素
//1, 获取字节码文件对象
Class c = Class.forName("java.util.ArrayList");
//2, 找到add()方法
Method addMethod = c.getMethod("add", Object.class);
//3,执行add()方法
addMethod.invoke(list, "哈哈");// list.add("哈哈");
System.out.println(list);
}
调用Main方法
假设一个对象类里存在一个main方法
//1、获取 Student 对象的字节码
Class clazz = Class.forName("fanshe.main.Student");
//2、获取main方法
Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,
//3、调用main方法
// methodMain.invoke(null, new String[]{"a","b","c"});
//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
反射获取配置文件
有一个配置文件名为 config.properties
className = com.zohn.Person
methodName = demo
public static void main(String[] args) throws Exception {
// 通过Properties集合从文件中读取数据
Properties prop = new Properties();
// 读取文件中的数据到集合中
prop.load(new FileInputStream("config.properties"));
// 获取键所对应的值
String className = prop.getProperty("className");
System.out.println(className);
// 1,获取Person.class 字节码文件对象
Class c = Class.forName(className);
// 2,获取构造方法
// public Person(String name, int age)
Constructor con = c.getConstructor(String.class, int.class);
// 3,创建对象
Object obj = con.newInstance("小明", 20);
System.out.println(obj);
// 4,获取指定的方法
// private void method5(){}
String methodName = prop.getProperty("methodName");
Method m5 = c.getDeclaredMethod(methodName, null);
// 5,开启暴力访问
m5.setAccessible(true);
// 6,执行找到的方法
m5.invoke(obj, null);
}
关于反射的实现原理,请跳转 深入分析Java方法反射的实现原理