【问题标题】:In Reflection, Method object's parameter class doesn't match argument class?在反射中,方法对象的参数类与参数类不匹配?
【发布时间】: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


【解决方案1】:

问题来自于我每次定位 Class 对象时创建一个新的 URLClassLoader 对象。只有 1 个 URLClassLoader 作为静态变量,问题就解决了。

【讨论】:

    猜你喜欢
    • 2021-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-06
    • 2011-04-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多