Determining the correct method signature at runtime

I am using the following org.apache.poi.hssf.usermodel.HSSFCell class with a list of the following methods:

 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) 

Note that there are no methods with an Object method parameter.

Now I have no way to determine my type of value class during compiled time. I can only determine the class type of a value at runtime. Therefore, how can I determine the correct method to be called if I do not know the method signature during compiled time?

My code is below:

 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! } } } 

Perhaps ugly if ... else with instanceof will solve my problem. However, if I don't want the ugly if ... else with instanceof , is there a better way to do this?

+4
source share
3 answers

One way to handle this is to load this list of methods at runtime into Map , and then use Map for each call. That is, something like this (where this code is simplified and skips error checking):

 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); } } 

then when you want to call this, find the method in the Map type by argument and use this instance.

To show this, again with a simplified, but this time complete code. Please note that in order to be completely general, the code becomes a little more complex, as shown below. If you don’t need to worry about primitives (which depend on your use), or if you don’t need to worry about interfaces or superclasses, you can simplify the example below.

In addition, if you can guarantee that there are no matches in the interfaces or superclasses in the arguments that you need to worry about, you can move all the complex logic to initialization (which does not matter if it takes 1 ms more). In this case, all the logic in findMethodToInvoke() will be moved to the constructor, where you will findMethodToInvoke() over all the interfaces and superclasses of each method you find and add them to your TypeMap parameter. If you perform this optimization, then findMethodToInvoke() will become one line:

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

but without this optimization and with complete generality, here is my example of how to do this:

 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); } } 
+3
source

I think instanceof is the way to go. If you think your code is ugly extracting instanceof expressions into a helper method:

 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."); } } 

Alternatively, you can use reflection. Even with reflection, I think you still have to do some tweaking for primitive types, because automatic boxing doesn't work for 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) { } } 
0
source

If you don't have subclasses (you can still do this if you will, but it will be harder, let me know if you do this), you can use reflection:

 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); } } 

The downside is that you don't have a compiler that tells you if you are trying to call a method that does not exist.

Exception handling in the above code is complete crap, but I wanted to focus on the reflection part.

Using an instance is better in that it provides a time type compilation. Reflection does not need to be updated if new methods are added.

0
source

All Articles