【发布时间】:2020-02-08 04:00:39
【问题描述】:
[此帖子已被编辑,以包含基本问题的简化复制/粘贴版本。]
我正在开发一个反射项目,该项目将具有一些类似于 JUnit 的功能,但我遇到了一个障碍,程序似乎觉得我有同一个类的 2 个不同版本。
我编写了一个简单的 Car 类,如下所示。
public class Car {
private String name;
public Car(String n) {
name = n;
System.out.println(name + " was constructed.");
}
public void honk() {
System.out.println("beep beep");
}
public void crash(Car other) {
System.out.println(name + " crashes into " + other.name);
}
}
我可以像这样成功测试汽车的功能:
public class CarRunner {
public static void main(String[] args) {
Car a = new Car("Model T");
Car b = new Car("Tesla");
a.honk(); //prints "beep beep"
a.crash(b); //prints "Model T crashes into Tesla"
}
}
上面的所有东西都可以正常工作。
现在,我想重现 CarRuner 的结果,但我使用反射编写的一些功能测试方法。使用反射,我可以请求创建对象并使用这些对象调用方法。直到最后的测试,用户定义的类被用作参数时,它都可以正常工作。
import java.io.*;
import java.lang.invoke.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.net.*;
public class TesterTool {
//Where are the class files that I am testing?
private static File classPath = new File("C:\\Users\\Spatter\\Desktop\\Autograder\\SimpleCarDemo");
public static Object makeObject(String nameOfClass, Object[] arguments) {
Object retObject = null; //goal is to get an object in here of the requested class.
try {
//What type of object are we trying to construct?
URL classUrl = classPath.toURI().toURL();
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{classUrl});
Class<?> c = Class.forName(nameOfClass, true, classLoader);
//What kind of arguments do we have?
Class[] argumentTypes = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++) {
argumentTypes[i] = arguments[i].getClass();
}
//Lets find a constructor that can accept the type of arguments we have
Constructor con = c.getConstructor(argumentTypes);
FutureTask<?> theTask = new FutureTask<Object>(new Callable<Object>()
{
public Object call() {
Object retObject = null;
try {
retObject = con.newInstance(arguments);
} catch (Exception e) { return e; }
return retObject;
}
});
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(theTask);
retObject = theTask.get(3, TimeUnit.SECONDS);
es.shutdownNow();
if (retObject instanceof Exception) throw new Exception();
} catch (Exception e) {
System.out.print("Error: Unable to construct object" + e);
}
return retObject;
}
public static Object testMethod(Object invokingObject, String methodName, Object[] arguments) {
Object retObject = null; //if the method we test returns an object, we will do the same.
try {
//What type of object are we trying to construct?
Class c = invokingObject.getClass();
//Alternate version of getting class type using ClassLoader
//Class originalc = invokingObject.getClass();
//String nameOfClass = originalc.getName();
//URL classUrl = classPath.toURI().toURL();
//URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{classUrl});
//Class<?> c = Class.forName(nameOfClass, true, classLoader);
//What kind of arguments do we have?
Class[] argumentTypes = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++) {
argumentTypes[i] = arguments[i].getClass();
}
//Lets find a method that can accept the type of arguments we have
Method m = c.getMethod(methodName, argumentTypes);
FutureTask<?> theTask = new FutureTask<Object>(new Callable<Object>()
{
public Object call() {
Object retObject = null;
try {
retObject = m.invoke(invokingObject, arguments);
} catch (Exception e) { return e; }
return retObject;
}
});
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(theTask);
retObject = theTask.get(3, TimeUnit.SECONDS);
es.shutdownNow();
if (retObject instanceof Exception) throw new Exception();
} catch (Exception e) {
System.out.print("Error: Unable to run method " + e);
}
return retObject;
}
public static void main(String[] args) {
//Find the Car class and invoke the constructor that receives a String parameter.
Object o1 = makeObject("Car", new Object[]{"Model T"}); //this works fine.
Object o2 = makeObject("Car", new Object[]{"Tesla"}); //this works fine.
//Invoke the honk method of object o1. No parameters required.
//The result is that "beep beep" is printed.
testMethod(o1, "honk", new Object[] {}); //this works fine.
//Invoke the crash(Car c) method of o1 using o2 as the parameter.
//This should print "Model T crashes into Tesla".
testMethod(o1, "crash", new Object[] {o2}); //this doesn't work.
}
}
最后一个测试是我的问题所在。 testMethod 似乎无法找到与我的请求匹配的崩溃方法版本。 crash 方法应该接收一个 Car 对象,它确实如此,但它似乎还不够好。
我还尝试了一个非常复杂的替代版本,在该版本中,我获取了 Car 类的所有方法并尝试找到与签名匹配的方法,但似乎感觉 Class car 的对象不是类车的对象。 (见下文。)
Class objectClass = o2.getClass();
Class[] paramTypes = method.getParameterTypes(); //where method is the Method object for crash
Class paramClass = paramTypes[0]; //there was only 1 paramType. I confirmed that it's the Car class.
System.out.println(objectClass); //prints class Car
System.out.println(paramClass); //prints class Car
if (paramClass.isAssignableFrom(objectClass)) { //always returns false?
System.out.println("I want to run this method because the signature matches.");
// o1 should invoke method using FutureTask
}
isAssignableFrom() 总是返回 false,即使它们都是 Car 类。知道可能是什么问题吗?我检查了两个 Class 对象(objectClass 和 paramClass),它们看起来是相同的,甚至到 ClassLoaders 中的路径也是如此。
代替isAssignableFrom(),我也尝试过isInstance,但也没有用:
if (paramClass.isInstance(o2)) { //also always returns false
【问题讨论】:
-
你能分享你的代码(在一个最小的工作示例中)以便人们可以重新创建吗?
-
不要编写需要测试的测试代码,也不要在这里提问。保持简单,必要时保持重复,保持明显,
-
首先,永远不要使用默认包。如果上面的代码清单(以及 cmets 中的断言)是可靠的,那么您在默认包中加载了两个名为“Car”的不同类。您是否在搞乱类加载器?检查
objectClass.getClassLoader() == paramClass.getClassLoader() -
同时发布您的
testMethod()。 -
是的,我正在使用 ClassLoaders,因为我正在测试的类文件不在运行测试的程序的工作目录中(并且永远不会在)。我将对我的程序进行简化版本,以便人们可以重新创建问题。
标签: java class reflection