Определить правильную подпись метода во время выполнения - PullRequest
4 голосов
/ 30 марта 2009

Я использую следующий класс 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 ... else с instanceof, есть ли лучший способ сделать это?

Ответы [ 3 ]

3 голосов
/ 30 марта 2009

Один из способов справиться с этим - загрузить этот список методов во время выполнения в 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 по типу аргумента и используйте этот экземпляр.

Чтобы показать это, снова с упрощенным, но на этот раз полным кодом. Обратите внимание, что, чтобы быть полностью общим, код становится немного более сложным, как показано ниже. Если вам не нужно беспокоиться о примитивах (что зависит от вашего использования) или если вам не нужно беспокоиться об интерфейсах или суперклассах, то вы можете упростить приведенный ниже пример.

Кроме того, если вы можете гарантировать, что не будет перекрытия в интерфейсах или суперклассах в аргументах, о которых вам нужно беспокоиться, вы можете переместить всю сложную логику в инициализацию (что не имеет значения, если это займет 1 мс больше). В этом случае вся логика в findMethodToInvoke() будет перемещена в конструктор, где вы будете циклически перебирать все интерфейсы и суперклассы каждого найденного вами метода и добавлять их в ваш параметрTypeMap. Если вы выполните эту оптимизацию, 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);
  }
}
0 голосов
/ 30 марта 2009

Если у вас нет подклассов (вы все равно можете сделать это, если у вас есть, но это будет сложнее, дайте мне знать, если у вас есть), вы можете использовать отражение:

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

Недостатком является то, что у вас нет компилятора, сообщающего вам, если вы пытаетесь вызвать метод, который не существует.

Обработка исключений в приведенном выше коде - полная чушь, но я хотел сосредоточиться на части отражения.

Использование экземпляра лучше с точки зрения безопасности типа времени компиляции. Отражение не нужно будет обновлять, если добавляются новые методы.

0 голосов
/ 30 марта 2009

Я думаю, что 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) {
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...