浅说Java中的反射机制
Posted 叶十一少
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅说Java中的反射机制相关的知识,希望对你有一定的参考价值。
写过一篇Java中的反射机制,不算是写,应该是抄了,因为那是别人写的,这一篇也是别人写的,摘抄如下:
引自于Java基础--反射机制的知识点梳理,作者醉眼识朦胧。(()为我手记)
什么是反射?
正常编译执行java文件时,会生成一个.class文件,反射就是一个反编译的过程,它可以通过.class文件得到一个java对象。一个类会有很多组成部分,比如成员变量、成员方法、构造方法等,反射可以通过加载类(加载类是个什么东西?一直搞不清楚),解剖出类的各个组成部分。
为什么要用反射?
我们需要访问一个类的方法或字段的时候,直接new一个该类的对象然后调用就好了,干嘛要用反射呢?刚学的时候我也不懂,并且学了也没机会用到,或者根本用不到,后来开始接触strut2、hibernate等一些框架的时候,才慢慢懂得了一点反射的强大之处。在框架中会有很多配置文件,而配置文件中一般只有一些类的全路径的字符串,而就是通过这些个字符串,就能得到这个类的对象,以及类中所有的信息。我给你这个类,当然你可以轻松得到它的实例,但如果我给你的只是这个类的路径的字符串呢?没有反射你就傻眼了吧。学习反射一般只会在写框架的时候用到,如果你没有达到写框架的高度,能够看懂别人的框架也是极好的。
这里面还涉及到一个静态编译和动态编译(静态编译和动态编译应该是过的,但是忘记了)的概念,前者的意思就是在编译的时候已经绑定了对象,确定了对象;而动态编译就是直到运行的时候,才根据需要去绑定对象。
加载类:
加载类就是获得类的字节码。要想通过反射获取到一个类的内部信息,首先得先获取到这个类的字节码对象。
加载类有三种方式:
现在我有一个类User,我希望加载这个类,获得它的字节码对象:
package cn.ren.reflect2; public class Demo { public static void main(String[] args) throws ClassNotFoundException { //第一种方式, 通过Class的forName方法,注意使用目标类的全路径 Class clazz = Class.forName("cn.ren.reflect2.User"); //第二种方式,通过Object类的getClass()方法 Class clazz1 = new User().getClass(); //第三种方式,通过类的.class属性 Class clazz2 = User.class; } }
一般第一种方式最常用,也最好用,因为看代码我们可以发现,后两种在编译前就必须要知道具体的类的,否则就无法通过编译,而第一种方式,没有那么大的强制性,他只是提供了一个空间,运行时你可以想把谁传给它就把谁传给它,这个类甚至可以不存在,后果只是抛出一个异常。
反射类的方法(Method):
一个类中总会有实现某些功能的方法,这些方法可能有返回值也可能没有返回值,可能有参数也可能没参数,可能是静态方法也可能是普通方法。下面通过例子分别讲解:
package cn.ren.reflect2; import java.lang.reflect.Method; public class Test { public static void main(String[] args) throws Exception { User u = new User(); //通过Class的forName方法,注意使用目标类的全路径 Class clazz = Class.forName("cn.ren.reflect2.User"); /* * 获取无参无返回值的方法login * 获得一个方法名为"login"但是没有参数的Method对象 */ Method method = clazz.getMethod("login", null); /* * 第一个参数:要想执行这个login方法你总得告诉她是哪个User的login方法吧 * 第二个参数就是login方法的参数(实参),这里为null */ method.invoke(u, null); //获取有参有返回值的方法eat Method method1 = clazz.getMethod("eat", String.class); String agrs = (String) method1.invoke(u, "超人"); /* * 获取带参无返回值的静态方法sleep * 静态方法,可以不提供User对象,传一个null即可 */ Method method2 = clazz.getMethod("sleep", String.class); method2.invoke(null, "蝙蝠侠"); } }
打印结果为:
无参构造函数
用户登录了
超人去吃饭了
蝙蝠侠去睡觉了
反射类的字段:
package cn.ren.reflect2; import java.lang.reflect.Field; public class Test1 { public static void main(String[] args) throws Exception { User u = new User(); u.setName("钢铁侠"); //通过Class的forName方法,注意使用目标类的全路径 Class clazz = Class.forName("cn.ren.reflect2.User"); Field f = clazz.getDeclaredField("name"); //设置属性为可访问的 f.setAccessible(true); //获取实例u中name属性的值 String name = (String) f.get(u); System.out.println(name); } }
下面介绍一个使用反射的简单例子:
(作者例子也举的不好,不知道要表达什么意思)
package cn.ren.reflect2; interface Person { public void location(String location); } class Student implements Person { @Override public void location(String location) { System.out.println("学生的工作地点是:"+location); } } class Teacher implements Person { @Override public void location(String location) { System.out.println("老师的工作地点是:"+location); } } class Factory { public static Person getInstance(String str) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Person person = (Person)Class.forName(str).newInstance(); return person; } } public class TestReflect { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Student s = (Student)Factory.getInstance("cn.ren.reflect2.Student"); s.location("学校"); } }
这个例子使用了简单的工厂模式,调用person的location方法,通过反射,实际应用中并不会出现这样的代码,这里只是做一个反射的运用的演示。在测试代码中,只要简单的修改ForName方法中的字符串,就可以调用相应的方法。
以下为我的手记,真是不要脸,依然是摘抄别人的。我多借鉴于传智播客李勇Jdbc视频笔记(26-33),作者:longdechuanren
以下紧接我的《JDBC第三次学习》的内容。
传递给你一个Class的对象,用它来构造一对象如果这个对象有无参的构造方法那么就clazz.newInstance(),直接newInstance就可以了,但是如果没有无参的构造方法,就要首先根据参数类型得到构造方法,然后再去构造对象。例如:
static Object create(Class clazz) throws Exception { Constructor con = clazz.getConstructor(String.class); /* * 利用反射技术构造一个实例 */ Object obj = con.newInstance("test name"); return obj; }
Java反射的更多的细节:
static Object create(Class clazz) throws Exception { Constructor con = clazz.getConstructor(String.class); /* * 利用反射技术构造一个实例 */ Object obj = con.newInstance("test name"); return obj; } static void invoke1(Object obj, String methodName) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { /* * 得到类中所有声明的方法,包括私有方法但不能得到从父类继承来的任何方法 */ Method[] ms = obj.getClass().getDeclaredMethods(); /* * 得到所有的方法包括从父类继承来的,但得不到私有方法 * 下面得到属性和得到注解都一样,主要理解两个方法getDeclaredMethods()与getMethods()的不同 */ ms = obj.getClass().getMethods();//推荐使用 for(Method m : ms) { //System.out.println(m.getName()); if(methodName.equals(m.getName())) m.invoke(obj, null); } Method m = obj.getClass().getMethod(methodName, null); m.invoke(obj, null); } static void field(Class clazz) throws Exception { Field[] fs = clazz.getDeclaredFields(); //fs = clazz.getFields();//取得的属性都是public for(Field f : fs) { System.out.println(f.getName()); } } static void annon(Class clazz) throws Exception { Annotation[] as = clazz.getAnnotations(); }
利用Java反射技术将查询结果封装为对象,
在这个示例中,约定数据库的字段名要和属性的字段名一致, String methodName = "set" + 数据库的字段名。用这样的方法来构成,JavaBean中属性的set方法来实现赋值操作,那么在传递sql语句的时候就有一定的局限性了,例如:你不能写 select id , name from user。因为这样得到的set方法setid、setname这样就没法完成赋值操作,要这样写:select id as Id, name as Name fromt user,然后用ResultSetMetadata的getColumnLabel()得到它的别名,然后遍历JavaBean中所有的方法找到和我们这个名字一样的,然后执行就可以了,但是通过sql语句来操纵属性名称然后构成方法名,这样的方式不大好。
代码如下(一个简单的对象关系映射Object Relational Mapping):
/* * 对象关系映射(ORM) */ public class ORMTest { public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SQLException, InstantiationException { User user = (User)getObject("select id as Id,name as Name,birthday as Birthday,money as Money" + " from user where id = 1", User.class); System.out.println(user); Bean b = (Bean)getObject("select id as Id,name as Name,birthday as Birthday,money as Money" + " from user where id = 1", Bean.class); System.out.println(b); } /* * 明确返回类型是User,写死在程序里 */ static User getUser(String sql) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SQLException { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount(); String[] colNames = new String[count]; for(int i = 1; i <= count; i++) { colNames[i-1] = rsmd.getColumnLabel(i); } User user = null;//User类是符合JavaBean规范的 Method[] ms = user.getClass().getMethods(); if(rs.next()) { user = new User(); for(int i = 0; i < colNames.length; i++) { String colName = colNames[i]; String methodName = "set"+colName; System.out.println(methodName); for(Method m : ms) { if(methodName.equals(m.getName())) { m.invoke(user, rs.getObject(colName));//setXxx()方法传递参数:rs.getObject("colName") } } } } return user; } finally { JdbcUtils.free(rs, ps, conn); } } /* * 由于返回类型是Object,所以此方法更为通用 */ static Object getObject(String sql, Class clazz) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, SQLException, InstantiationException { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount(); String[] colNames = new String[count]; for(int i = 1; i <= count; i++) { colNames[i-1] = rsmd.getColumnLabel(i); } Object obj = null; Method[] ms = clazz.getMethods(); if(rs.next()) { obj = clazz.newInstance(); for(int i = 0; i < colNames.length; i++) { String colName = colNames[i]; String methodName = "set"+colName; for(Method m : ms) { if(methodName.equals(m.getName())) { m.invoke(obj, rs.getObject(colName));//setXxx()方法传递参数:rs.getObject("colName") } } } } return obj; } finally { JdbcUtils.free(rs, ps, conn); } } }
以上是关于浅说Java中的反射机制的主要内容,如果未能解决你的问题,请参考以下文章