JAVA反射练习

题目

实现一个方法

public static Object execute(String className, String methodName, Object args[])

实现 “通过类的名字、方法名字、方法参数调调用方法,返回值为该方法的返回值。” 的功能。

解题思路

开始阶段

  一开始看到这个题目,以为很简单。大致思路就是通过反射获取字节码文件对象,然后该对象获取方法名的方法对象。

  将args数组转换成Class对象数组,这样来获取具体的调用某一个方法,最后调用invoke(obj,args)方法完成。

发现问题

按照这个思路写出程序,如下所示:

    public static Object execute(String className, String methodName,Object[] args) throws Exception {
// 获取类的字节码文件对象
Class cls = Class.forName(className);
// 获取方法调用的参数的Class对象
Class[] paramsCls = new Class[args.length];
for (int i = 0; i < args.length; i++) {
paramsCls[i] = args[i].getClass();
}
// 获取方法对象
Method method = cls.getMethod(methodName, paramsCls);
Object obj = cls.newInstance();
return method.invoke(obj,args);
}

  乍一看,这个程序没有什么太大的问题,可是在测试这个方法时出现了问题,如测试一个public int add(int a,int b)。在传递参数时,是一个Object类型的数组,如20,30这两个参数在传递过去时就被自动的装箱成为了Integer类型。那么获取的Class对象就成为了 class java.lang.Integer,但是add方法的参数却是int类型。

  一开始我以为没什么问题,但是在调用cls.getMethod()方法时,出现了问题。虚拟机抛出异常,表示没有这样的方法,这让我很困惑,后来百度了一下,发现int.class和Integer.class并不能混为一谈,也就是这两者并不能像自动拆装箱那样进行转换。也明白了Class类的getMethod(methodName,paramTypes)中paramTypes需要的Class类型就是方法定义时的数据类型,add(int a,int b)参数是int,那么getMethod时传递的就是int.class,如果传递一个Integer.class,并不能获取到这个方法。所以这个程序并不是特别完善。

思考

  1. 解决方案1

      既然基本数据类型在作为参数传递时,都变成了各自的包装类,那么只要在定义方法时,方法参数不允许使用基本数据类型就可以了。
  2. 解决方案2

      在获取类的Class对象后,去获取类的所有方法,然后遍历获取的方法对象,拿方法对象名与methodName去比较,若相同,则调用该方法。

大致的解决思路就是这样,后来仔细思考了一下,这两种解决方案都不是特别完美。

  1. 方案1的缺陷

      程序的通用性很差,如果我使用这个方法去调用别人定义的类中的方法,假如其他人在定义方法时,并没有将基本数据类型写成包装类,那么我使用这个方法就会发生错误,不能正确的执行。
  2. 方案2的缺陷

      方案2使用循环遍历Methods数组,并通过methodName来比较判断是不是调用的该方法。但是假如我碰到了方法的重载,如调用add(int a, int b)时,这个类里面还有add(int a,int b, int c),或者add(double a,double b)。这就是三个方法, 并且方法的名称都是"add",假如我传递过来的methodName是"add",args是{20,30},那么在循环遍历时,就会出现问题,如首先遍历的是add(int a,int b, int c),那么在invoke时就会发生错误。所以方案2还需要判断方法的重载问题。

最终解决方案

  我的最终解决方案就是在方案2的基础上,再对程序进行完善。那就是在判断method对象时,多追加几个判断条件。

  即:当方法名一样时,判断传递的参数个数是否与当前方法对象所需要的参数个数相同,若相同,则再判断每一个参数类型是否与方法所需要的类型是否一致。这样就能精确的定位调用哪一个方法了。

完善后程序如下:

    public static Object execute(String className, String methodName, Object args[]) throws Exception {
Class cls = Class.forName(className);
Method[] methods = cls.getMethods();
Object obj = cls.newInstance();
for (Method method : methods) {
// 获取方法所需要参数的Class对象数组
Class[] types = method.getParameterTypes();
// 判断methodName是否和方法名一致,若一致,再判断传递的参数个数是否一致。参数个数一致后再判断参数类型是否一致
if (method.getName().equals(methodName) && args.length == types.length
&& isEqualParamAndTypes(args, types)) {
// 都一致 执行该方法
return method.invoke(obj, args);
}
}
System.out.println("没有这个方法或参数不匹配");
return null;
} /**
* 判断参数数组的类型是否与方法所需要的参数类型是否一致
* @param args 方法调用参数
* @param types 方法所需要的参数类型
* @return true代表一致,false不一致
*/
private static boolean isEqualParamAndTypes(Object[] args, Class[] types) {
boolean flag = false;
for (int i = 0; i < args.length; i++) {
String clsName = args[i].getClass().toString();
String typeName = types[i].toString();
// 上面获取参数的Class对象的字符串表示形式,是为了更好的去判断参数是否为基本数据类型。
// 这里还需要去判断方法参数是否为基本数据类型。 如果是,那么照样是可以通过的
if (clsName.equals(typeName) || isBasicType(clsName).equals(typeName)) {
flag = true;
} else {
flag = false;
break;
}
}
return flag;
} /**
* 判断字节码文件对象的字符串表示形式是否为基本数据类型的包装类型,若是,则返回基本数据类型的class对象的字符串表示形式
* @param clsName 字节码文件(Class)对象的字符串表示形式
* @return 若是包装类型,返回对应类型的基本数据类型的class对象表示形式,若不是,则返回该字符串本身
*/
private static String isBasicType(String clsName) {
switch (clsName) {
case "class java.lang.Byte":
return "byte";
case "class java.lang.Short":
return "short";
case "class java.lang.Integer":
return "int";
case "class java.lang.Long":
return "long";
case "class java.lang.Float":
return "float";
case "class java.lang.Double":
return "double";
case "class java.lang.Character":
return "char";
case "class java.lang.Boolean":
return "boolean";
default:
return clsName;
}
}

结果: 能够成功的运行,并且能够很好的区分方法重载问题。

总结

  这个练习题看似简单,但还是有不少坑是可以值得挖一挖的。

  一开始我使用的是办法类似于方案1,在获取参数的Class对象时,判断一下是否为包装类型,若是,则转换成对应的基本类型的class。

判断条件:

    if(args[i].getClass().getSimpleName().equals("Integer")){
cls[i] = int.class;
}else{
cls[i] = args[i].getClass();
}

  这样转换会出现一个问题,当我指定一个方法的参数类型为Integer时,上面的判断条件又会将Integer类型转换为int.class,这样在getMethod(methodName,Class[] clsType)又会出现异常。这样使得在调用时必须确定方法的参数类型的class对象,又由于int.class和Integer.class不一样,上面的判断条件无法精确的判断方法的参数到底是int还是Integer类型。就很令人头痛。

  于是我就想到假如我获取到所有的方法对象,并遍历每一个方法对象,判断传递的方法名称是否和遍历的方法名一致,这样我不就明确的知道了方法所需要的参数个数和参数的具体类型了吗。

  知道了方法名 然后再通过传递的参数与方法所需要的参数进行匹配。一致,则调用该方法,不一致则不调用。

  这就是方案2的思路,但是在判断类型时,我使用的是Class对象的字符串表示形式去判断而不是直接使用Class对象去判断,因为这样能够使用switch()判断,因为switch只接收基本数据类型和字符串类型的,而Class对象不在此范围内。

   要是直接使用Class对象去比较判断的话,也不好解决。比如:获取到了参数的Class对象为Integer.class,判断时就是 if(cls == Integer.class){ cls = int.class} 这样就出现了上面一样的错误,假如方法参数要的是Integer.class 而上边判断又转成了int.class,这样不符合条件,就跳过了这一方法,这样一来方法就无法执行了。所以使用字符串比较就比较合理了,也就是如下的判断条件:

    if (clsName.equals(typeName) || isBasicType(clsName).equals(typeName)) {
flag = true;
} else {
flag = false;
break;
}

   这样判断的好处就是: 当传递的参数的Class对象字符串与方法需要的字符相同时,就符合条件。

当不一样时,再接着判断方法所需的参数是否是一个基本类型。若是,则符合条件。

   这个程序大致就是这个样子了,但仍然还存在一些缺陷。如:当传递的参数是数组时,又出现了基本类型数组与包装类型数组的区别。这又需要去判断一下了。这里我就没有再过多的去完善它了。

  通过写这个反射程序,让我不禁感慨写出一个健壮性很强的程序是多么的费事。更让我明白还有更多的知识等待着我去学习。

  如果有更好的方法来实现这个功能,欢迎在评论中贴出来。

JAVA反射练习的更多相关文章

  1. 第28章 java反射机制

    java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...

  2. Java反射机制

    Java反射机制 一:什么事反射机制 简单地说,就是程序运行时能够通过反射的到类的所有信息,只需要获得类名,方法名,属性名. 二:为什么要用反射:     静态编译:在编译时确定类型,绑定对象,即通过 ...

  3. java反射(基础了解)

    package cn.itcast_01; /** *Person类 */ public class Person {    /** 姓名 */    private String name;     ...

  4. java基础知识(十一)java反射机制(上)

    java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...

  5. java基础知识(十一)java反射机制(下)

    1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...

  6. java反射学习之二万能EXCEL导出

    一.EXCEL导出的实现过程 假设有一个对象的集合,现在需要将此集合内的所有对象导出到EXCEL中,对象有N个属性:那么我们实现的方式是这样的: 循环这个集合,在循环集合中某个对象的所有属性,将这个对 ...

  7. java反射学习之一反射机制概述

    一.反射机制背景概述 1.反射(reflection)是java被视为动态语言的一个关键性质 2.反射机制指的是程序在运行时能获取任何类的内部所有信息 二.反射机制实现功能概述 1.只要给定类的全名, ...

  8. java反射 之 反射基础

    一.反射 反射:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为 ...

  9. java反射 cglib asm相关资料

    有篇文章对java反射的调用的效率做了测试,写的比较好.猛击下面地址 http://www.blogjava.net/stone2083/archive/2010/09/15/332065.html ...

  10. 超详细的java反射教程

    看技术博客时,看到关于java反射的博文,写的非常好.猛击下面的地址,开始java反射之旅 中文翻译地址:http://ifeve.com/java-reflection/ 英文原版地址:http:/ ...

随机推荐

  1. javascript之系统对话框

    浏览器通过alert(),confirm()和prompt()方法调用系统对话框,向用户显示信息. alert()接受一个字符串并显示给用户,结果是显示一个对话框,其中包括指定的文本和一个OK(“确定 ...

  2. hadoop mapreduce 计算平均气温的代码,绝对原创

    1901 46 1902 21 1903 48 1904 33 1905 43 1906 47 1907 31 1908 28 1909 26 1910 35 1911 30 1912 16 1913 ...

  3. javaIO 流分析总结

    Java中的流,可以从不同的角度进行分类. 按照数据流的方向不同可以分为:输入流和输出流. 按照处理数据单位不同可以分为:字节流和字符流. 按照实现功能不同可以分为:节点流和处理流. 输出流: 输入流 ...

  4. 面试题: 数据库 真实面试题已看1 操作语句 存储过程 挺好 sql语句练习 有用

    今天到某公司笔试,数据库考的比较多,但是说老实话,考的也比较基础.现在趁回忆得起来,将数据库知识简单整理如下: 一.建表指令 比如创建一个学生表student,它由学号Sno,姓名Sname,性别Ss ...

  5. 使用form 组件写一个用户注册,并用 bootstrap渲染

    需求:使用form组件,写一个用户注册系统,包含用户名, 密码, 确认密码,手机号,性别,爱好,注册.并用bootsrap渲染,成果如下: 首先创建一个django 项目.然后在连接pymysql数据 ...

  6. easyui学习笔记1-(datagrid+dialog)

    jQuery EasyUI是一组基于jQuery的UI插件集合体.我的理解:jquery是js的插件,easyui是基于jquery的插件.用easyui可以很轻松的打造出功能丰富并且美观的UI界面. ...

  7. Sharepoint商务智能学习笔记之Powerpivot Service Dmeo(八)

    1)在Excel上添加Powerpivot工具栏 第一步,在Excel中启用Powerpivot 工具栏,新建一个空白Excel文件,在左上角点击文件,然后点击选项 2)使用Powerpivot添加数 ...

  8. C#在Linux上的开发指南

    本人才疏学浅,在此记录自己用C#在Linux上开发的一点经验,写下这篇指南.(给想要在Linux上开发C#程序的朋友提供建议) 目前在Linux上跑的网站:http://douxiubar.com | ...

  9. 第一章 –– Java基础语法

    第一章 –– Java基础语法 span::selection, .CodeMirror-line > span > span::selection { background: #d7d4 ...

  10. MCP|LDY|Mass Spectrometry-based Absolute Quantification of 20S Proteasome Status for Controlled Ex-vivo Expansion of Human Adipose-derived Mesenchymal Stromal/Stem Cells(基于质谱技术的20S蛋白酶体绝对定量方法监控人体脂肪...

    期刊名:Mol Cell Proteomics 发表时间:(2019年4月) IF:5.232   概述 20S蛋白酶体是一种多亚基蛋白质复合物,参与许多组织细胞生命活动过程.本研究基于SILAC标记 ...