本文共 4105 字,大约阅读时间需要 13 分钟。
首先,大家应该先明白两个概念:编译期和运行期。编译期就是编译器帮你把源代码翻译成机器能执行的文件,比如编译器把java代码编译成jvm能识别的字节码文件,而运行期指的是将可执行文件交给操作系统去执行。Java反射(Reflection)就是Java程序在运行时,可以加载、探知、使用编译期间完全未知的类。也就是说,Java可以加载一个运行时才得知类名的类,获得类的完整构造方法,并实例化出对象,给对象属性设定值或者调用对象的方法。这种在运行时动态获取类的信息以及动态调用对象的方法的功能称为Java的反射机制。
想要使用反射机制,就必须先获取到该类的字节码文件对象(.class),通过字节码对象,就能够通过该类的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等),每个类对应着一个字节码文件,也就对应着一个Class类型的的对象,也就是字节码文件对象。
下面介绍三种能获取字节码对象的途径:
//通过Class类的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件对象,并没有变为字节码文件。 Class class1=Class.forName("reflection.User"); //当类被加载为.class文件时,此时User类变成了.class,在获取该字节码文件对象,也就是获取自己,该类处于字节码阶段 Class class2=User.class; //通过类的实例获取该类的字节码文件对象,该类属于创建对象阶段,一般不会这么用,因为实例都获取到了,没必要多此一举 User user=new User(); Class class3=user.getClass();
Class的常用API详解
1.通过字节码对象,创建实例对象。
//获取字节码文件对象 Class clazz = Class.forName("reflection.User"); //创建User实例,这里是通过User的无参构造函数来创建对象 User user1=(User) clazz.newInstance(); //通过getConstructor获取构造器,且填写参数,创建实例,当然也可以不写参数,调用无参的构造器 User user2=(User) clazz.getConstructor(String.class,String.class,Integer.class).newInstance("张三","123",25);
总结:Class类的newInstance()方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用getConstructor(String.class,String.class,Integer.class)方法获取一个指定的构造函数,然后再调用Constructor类的newInstance(“张三”,“123”,25)方法创建对象.
2.获取成员变量并使用
//获取字节码文件对象 Class clazz = Class.forName("reflection.User"); //获得到其实例对象 User user =(User) clazz.newInstance(); //获取成员变量 clazz.getField(name),通过name来指定成员变量 //如果该成员变量是私有的,则应该使用clazz.getDeclaredField(name) Field field=clazz.getDeclaredField("name"); //因为属性是私有的,获得其属性对象的后,还需要让其打开可见权限 field.setAccessible(true); //对其成员变量进行操作 //赋值操作 field.set(user, "李四"); //获取成员变量的值, System.out.println(field.get(user));
总结:Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField(“name”)方法获取,通过set(obj, “李四”)方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值。
3.获取方法并使用
//获取字节码对象 Class clazz=Class.forName("reflection.User"); //实例化 User user=(User)clazz.newInstance(); /** * clazz.getMethod(name,parameterType) * name:方法的名字 * parameterType:方法的参数类型的Class类型,没有则什么都不填 */ Method method=clazz.getMethod("login", String.class,String.class); //获取私有的方法,和获取私有属性一样,需要开启权限 method.setAccessible(true); /** * 调用方法:invoke(obj,args) * obj:方法的对象 * args:实际的参数值,没有则不填 */ method.invoke(user, "王五","123");
反射机制的应用实例:
1.利用反射,在泛型为int的arraylist集合中存放一个String类型的对象。
原理:集合中的泛型只在编译期有效,编译后生成字节码文件泛型是被擦除的;Listlist=new ArrayList (); list.add(1); list.add(2); Class clazz=list.getClass(); Method method=clazz.getMethod("add", Object.class); method.invoke(list, "yechengchao"); System.out.println(list);
2.利用反射封装BaseServlet,在这边涉及到Servlet的执行流程。
编写BaseServlet,继承HttpServlet
public class BaseServlet extends HttpServlet { @Override public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // localhost:8080/store/productServlet?method=addProduct String method = req.getParameter("method"); if (null == method || "".equals(method) || method.trim().equals("")) { method = "execute"; } // 注意:此处的this代表的是子类的对象 // System.out.println(this); // 子类对象字节码对象 Class clazz = this.getClass(); try { // 查找子类对象对应的字节码中的名称为method的方法.这个方法的参数类型是:HttpServletRequest.class,HttpServletResponse.class Method md = clazz.getMethod(method, HttpServletRequest.class, HttpServletResponse.class); if(null!=md){ //执行方法 String jspPath = (String) md.invoke(this, req, resp); if (null != jspPath) { req.getRequestDispatcher(jspPath).forward(req, resp); } } } catch (Exception e) { e.printStackTrace(); } } // 默认方法 public String execute(HttpServletRequest req, HttpServletResponse resp) throws Exception { return null; }}
编写具体的Servlet,继承BaseServlet
public class UserServlet extends BaseServlet { public String registUI(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { return "/jsp/register.jsp"; } }
总结:需要理解Servlet的生命周期,当访问servlet都会先执行service()方法,如果子类没有重写这个方法,就会去父类找并执行这个方法,然后获取参数即知道了需要调用什么方法,因为具体的方法都写在子类中,所以通过反射,找到子类中对应的方法并运行,其中需要注意的是this这个参数在BaseServlet中的用法。
转载地址:http://yiyen.baihongyu.com/