JAVA反射练习
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
既然基本数据类型在作为参数传递时,都变成了各自的包装类,那么只要在定义方法时,方法参数不允许使用基本数据类型就可以了。 - 解决方案2
在获取类的Class对象后,去获取类的所有方法,然后遍历获取的方法对象,拿方法对象名与methodName去比较,若相同,则调用该方法。
大致的解决思路就是这样,后来仔细思考了一下,这两种解决方案都不是特别完美。
- 方案1的缺陷
程序的通用性很差,如果我使用这个方法去调用别人定义的类中的方法,假如其他人在定义方法时,并没有将基本数据类型写成包装类,那么我使用这个方法就会发生错误,不能正确的执行。 - 方案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反射练习的更多相关文章
- 第28章 java反射机制
java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...
- Java反射机制
Java反射机制 一:什么事反射机制 简单地说,就是程序运行时能够通过反射的到类的所有信息,只需要获得类名,方法名,属性名. 二:为什么要用反射: 静态编译:在编译时确定类型,绑定对象,即通过 ...
- java反射(基础了解)
package cn.itcast_01; /** *Person类 */ public class Person { /** 姓名 */ private String name; ...
- java基础知识(十一)java反射机制(上)
java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...
- java基础知识(十一)java反射机制(下)
1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...
- java反射学习之二万能EXCEL导出
一.EXCEL导出的实现过程 假设有一个对象的集合,现在需要将此集合内的所有对象导出到EXCEL中,对象有N个属性:那么我们实现的方式是这样的: 循环这个集合,在循环集合中某个对象的所有属性,将这个对 ...
- java反射学习之一反射机制概述
一.反射机制背景概述 1.反射(reflection)是java被视为动态语言的一个关键性质 2.反射机制指的是程序在运行时能获取任何类的内部所有信息 二.反射机制实现功能概述 1.只要给定类的全名, ...
- java反射 之 反射基础
一.反射 反射:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为 ...
- java反射 cglib asm相关资料
有篇文章对java反射的调用的效率做了测试,写的比较好.猛击下面地址 http://www.blogjava.net/stone2083/archive/2010/09/15/332065.html ...
- 超详细的java反射教程
看技术博客时,看到关于java反射的博文,写的非常好.猛击下面的地址,开始java反射之旅 中文翻译地址:http://ifeve.com/java-reflection/ 英文原版地址:http:/ ...
随机推荐
- Select 标签的默认值设置
以下是网上“借鉴”的:(http://blog.csdn.net/nairuohe/article/details/6307367/) 比如<select class="selecto ...
- assert.equal()
assert.equal(actual, expected[, message]) 使用相等运算符(==)测试 actual 参数与 expected 参数是否相等(通俗解释equal方法接受三个参数 ...
- 5.6 安装SqlDeveloper
首先,将安装包准备好: 打开终端: 这样,sqldeveloper就安装完毕了. 在Ubuntu中搜索,sql,会出现: 点击,可以进入:
- 22. Bypass X-WAF SQL注入防御(多姿势)
0x00 前言 X-WAF是一款适用中.小企业的云WAF系统,让中.小企业也可以非常方便地拥有自己的免费云WAF. 本文从代码出发,一步步理解WAF的工作原理,多姿势进行WAF Bypass. 0x0 ...
- doxygen+ graphviz 开源工具生成源码调用树的wiki
当拿到一含有大量代码的工程怎么看?!这时一个好的代码分析工具非常有用,网上有很多开源工具,但资料都参差不齐,偶然发现doxygen+ graphviz这两工具非常棒,使用工具直接生成函数调用链图,帮助 ...
- 基于XML的AOP配置
创建spring的配置文件并导入约束 此处要导入aop的约束 <?xml version="1.0" encoding="UTF-8"?> < ...
- Codeforces#514E(贪心,并查集)
#include<bits/stdc++.h>using namespace std;long long w[100007],sum[100007];int fa[100007],degr ...
- MySQL主从配置实战笔记
其实网上已经有关于MySQL主从复制的很丰富全面的资料了,这里写点东西主要是为了给自己加深印象. 复制原理 MySQL主从复制是内建的非常强大的功能,主要应用于数据备份,负载均衡等方面.因为配置相对比 ...
- 洛谷P1932 A+B A-B A*B A/B A%B Problem
P1932 A+B A-B A*B A/B A%B Problem 题目背景 这个题目很新颖吧!!! 题目描述 求A.B的和差积商余! 由于数据有修改,减法运算结果可能带负号! 输入输出格式 输入格式 ...
- RRTI的概念以及Class对象作用
深入理解Class对象 RRTI的概念以及Class对象作用 认识Class对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一 ...