在运行时确定正确的方法签名

时间:2009-03-30 17:26:43

标签: java reflection

我正在使用以下类org.apache.poi.hssf.usermodel.HSSFCell,其中列出了以下方法:

void setCellValue(boolean value)

void setCellValue(java.util.Calendar value)

void setCellValue(java.util.Date value)

void setCellValue(double value)

void setCellValue(HSSFRichTextString value)

void setCellValue(java.util.Calendar value)

void setCellValue(HSSFRichTextString value)

请注意,没有使用Object作为方法参数的方法。

现在,我无法在编译时确定我的值类类型。我只能在运行时确定我的值类类型。因此,如果在编译时不知道方法签名,我如何确定要调用的正确方法?

我的代码如下:

final int rowCount = tableModel.getRowCount();
for (int i = 0; i < rowCount; i++) {
    final HSSFRow row = sheet.createRow(i + 1);
    for (int j = 0; j < columnCount; j++) {
        final Object object = tableModel.getValueAt(i, j);
        final Class myClass = tableModel.getColumnClass(j);
        // How to perform casting during compiled time, and invoke
        // the setCellValue with correct signature?
        if (object != null) {
            row.createCell(j).setCellValue(??); // Does not accept Object!
        }
    }
}

如果...... instanceof的其他内容可以解决我的问题,也许很难看。但是,如果我不想要丑陋的if ...使用instanceof,有没有更好的方法呢?

3 个答案:

答案 0 :(得分:3)

处理此问题的一种方法是在运行时将该方法列表加载到Map,然后对于每个调用,使用Map。也就是说,这样的事情(这个代码被简化并省略了错误检查):

Map<? extends Object, Method> map;

Method[] methods = Setters.class.getMethods();
for (Method method : methods) {
  if (method.getName().equals("setCellValue")) {
    map.put(method.getParameterTypes()[0], method);
  }
}

然后当你想调用它时,在map参数类型中查找Method并使用该实例。

要显示此功能,请再次使用简化但这次完整代码。请注意,为了完全通用,代码会变得更复杂,如下所示。如果您不必担心原语(这取决于您的用法)或者您不必担心接口或超类,那么您可以简化下面的示例。

此外,如果您可以保证在您不必担心的参数中的接口或超类中没有重叠,您可以将所有复杂的逻辑移动到初始化(如果需要1 ms则无关紧要更长)。在这种情况下,findMethodToInvoke()中的所有逻辑都将被移动到构造函数中,您可以在其中遍历您找到的每个方法的所有接口和超类,并将它们添加到parameterTypeMap中。如果您执行此优化,则findMethodToInvoke()将成为一行:

return parameterTypeMap.get(test.getClass());

但是如果没有这种优化并具有完全的通用性,这是我的如何做到这一点的例子:

import java.lang.reflect.*;
import java.util.*;

public class Test {
  private final Map<Object, Method> parameterTypeMap = new HashMap<Object, Method>();

  private final Object[] tests = {Double.valueOf(3.1415),
                                  Boolean.TRUE,
                                  new Date(),
                                  new GregorianCalendar(),
                                  new HashMap<Object, Object>()};

  public Test() {
    Method[] methods = Setters.class.getMethods();
    for (Method method : methods) {
      if (method.getName().equals("setCellValue")) {
        Class<?>[] clazzes = method.getParameterTypes();
        if (clazzes.length != 1) {
          continue;
        }
        if (clazzes[0].isPrimitive()) {
          handlePrimitive(method, clazzes[0]);
        }
        parameterTypeMap.put(clazzes[0], method);
      }
    }
  }

  // See http://java.sun.com/javase/6/docs/api/java/lang/Class.html#isPrimitive()
  private void handlePrimitive(Method method, Class<?> clazz) {
    if (clazz == Boolean.TYPE) {
      parameterTypeMap.put(Boolean.class, method);
    } else if (clazz == Double.TYPE) {
      parameterTypeMap.put(Double.class, method);
    } // ... and so on for the other six primitive types (void doesn't matter)
  }

  public void doTests(Setters setter) {
    for (Object test : tests) {
      Method method = findMethodToInvoke(test);
      if (method == null) {
        System.out.println("Nothing found for " + test.getClass());
        continue;
      }

      try {
        method.invoke(setter, test);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private Method findMethodToInvoke(Object test) {
    Method method = parameterTypeMap.get(test.getClass());
    if (method != null) {
      return method;
    }

    // Look for superclasses
    Class<?> x = test.getClass().getSuperclass();
    while (x != null && x != Object.class) {
      method = parameterTypeMap.get(x);
      if (method != null) {
        return method;
      }
      x = x.getSuperclass();
    }

    // Look for interfaces
    for (Class<?> i : test.getClass().getInterfaces()) {
      method = parameterTypeMap.get(i);
      if (method != null) {
        return method;
      }
    }
    return null;
  }

  public static void main(String[] args) {
    Test test = new Test();
    test.doTests(new Setters());
  }
}

class Setters {
  public void setCellValue(boolean value) {
    System.out.println("boolean " + value);
  }

  public void setCellValue(double value) {
    System.out.println("double " + value);
  }

  public void setCellValue(Calendar value) {
    System.out.println("Calendar " + value);
  }

  public void setCellValue(Date value) {
    System.out.println("Date " + value);
  }

  public void setCellValue(Map<?, ?> value) {
    System.out.println("Map " + value);
  }
}

答案 1 :(得分:0)

我认为instanceof是要走的路。如果您认为它使您的代码难以将instanceof表达式提取到辅助方法中:

public void setCellValue(HSSFCell cell, Object value) {
    if (null == cell)
        throw new IllegalArgumentException("cell");
    if (null == value)
        throw new IllegalArgumentException("value");
    if (value instanceof Double)
        cell.setCellValue((Double)value); // auto-boxing will handle this
    else if (value instanceof Boolean) {
        cell.setCellValue((Boolean)value); // auto-boxing will handle this
    } else if (value instanceof Calendar) {
        cell.setCellValue((Calendar)value);
    } else if ...
        .....
    } else {
        throw new UnsupportedTypeException("Object of class " + Value.class.getName() + " not supported.");
    }
}

或者你可以使用反射。即使使用反射我认为你仍然需要对基本类型进行一些自定义,因为自动装箱对getMethod()不起作用...

public void invokeSetCellValue(HSSFCell cell, Object obj) {
    try {
        Class<?> clazz = obj.getClass();
        if (obj instanceof Double) {
            clazz = double.class;
        } else if (obj instanceof Boolean) {
            clazz = boolean.class;
        }
        Method m = HSSFCell.class.getMethod("setCellValue", clazz);
        m.invoke(cell, obj);
    } catch (SecurityException e) {
    } catch (NoSuchMethodException e) {
    } catch (IllegalArgumentException e) {
    } catch (IllegalAccessException e) {
    } catch (InvocationTargetException e) {
    }

}

答案 2 :(得分:0)

如果你没有子类(如果你这样做,你仍然可以这样做,但如果你这样做会更难,请告诉我)你可以使用反射:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final Object o;

        if(argv.length == 0)
        {
            o = "Hello";
        }
        else
        {
            o = Integer.valueOf(42);
        }

        callFoo(o);
    }

    private static void callFoo(final Object o) 
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        Method method;

        method = Main.class.getDeclaredMethod("foo", o.getClass());
        method.invoke(null, o);
    }

    private static void foo(final String val)
    {
        System.out.println("foo(String) -> " + val);
    }

    private static void foo(final Integer val)
    {
        System.out.println("foo(Integer) -> " + val);
    }
}

缺点是如果您尝试调用不存在的方法,则没有编译器告诉您。

上面代码中的异常处理是完全废话,但我想专注于反射部分。

从编译时类型安全的角度来看,使用实例更好。如果添加新方法,则不必更新反射。