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. spring----IOC注解方式以及AOP

    技术分析之Spring框架的IOC功能之注解的方式 Spring框架的IOC之注解方式的快速入门 1. 步骤一:导入注解开发所有需要的jar包 * 引入IOC容器必须的6个jar包 * 多引入一个:S ...

  2. Material使用07 MdGridListModule的使用

    1 MatGridListModule简介 对相似数据的展现,尤其是像是图片的展示 使用起来很像表格 官方文档:点击前往 2 MatGridListModule提供的指令 2.1 mat-grid-l ...

  3. R语言中的字符处理

    R语言中的字符处理 (2011-07-10 22:29:48) 转载▼ 标签: r语言 字符处理 字符串 连接 分割 分类: R R的字符串处理能力还是很强大的,具体有base包的几个函数和strin ...

  4. JSP有哪些动作?

    JSP使用动作来动态的插入文件,实现重定向和对JavaBean的引用等功能.它公有6个基本动作:jsp:include,jsp:useBean,jsp:setProperty,jsp:getPrope ...

  5. 【mysql 统计分组之后统计录数条数】

    SELECT count(*) FROM 表名 WHERE 条件 // 这样查出来的是总记录条   SELECT count(*) FROM 表名 WHERE 条件 GROUP BY id //这样统 ...

  6. C# Newtonsoft.Json不序列字段

    [JsonObject(MemberSerialization.OptOut)] public class employeePersonalForm { [JsonIgnore] public str ...

  7. docker17.09.1-ce 执行systemctl resart docker重启失败的问题

    记录在实际操作中碰到的docker问题 环境信息: 安装完kolla ocata版本,并且编译成功各openstack 组件的容器镜像 [root@localhost ~]# docker --ver ...

  8. Flask-SQLAlchemy 配置,处理对象-关系,一对多,多对多

      ORM(Object Relational Mapper) 对象关系映射.指将面对对象得方法映射到数据库中的关系对象中. Flask-SQLAlchemy是一个Flask扩展,能够支持多种数据库后 ...

  9. Educational Codeforces Round 64 (Rated for Div. 2)D(并查集,图)

    #include<bits/stdc++.h>using namespace std;int f[2][200007],s[2][200007];//并查集,相邻点int find_(in ...

  10. DMZ主机

    DMZ称为"隔离区",也称"非军事化区".为了解决安装防火墙后外部网络不能访问内部网络服务器的问题,而设立的一个非安全系统与安全系统之间的缓冲区.这个缓冲区位于 ...