Преобразовать строку в SomeType в Java

Я решил написать свой крошечный ArgumentParser на Java, ключевой момент в том, что у него есть метод

public void addOption(String optionName, Class valueType, boolean required)

за что-то вроде

ArgumentParser parser = new ArgumentParser();
parser.addOption("--input_path", String.class, true);
Map<String, Object> options = parser.parse(args);  //args is String[]
String inputPath = (String)options.get("--input_path");  //hooray!

но я не могу понять, как попытаться автоматически преобразовать строку в какой-то определенный пользователем класс? Код ниже, очевидно, не работает

options.put(currentOption, registeredOptions.get(currentOption).valueOf(arg));

Есть какой-то простой способ сделать это, что я не могу понять? Или, может быть, вы говорите, что это плохая и порочная идея, я ценю любой совет! Спасибо!

3 ответа

Решение

Это уже делают несколько библиотек, например, JCommander, и почти все они используют шаблон Strategy. То есть они позволяют клиентам регистрировать объекты конвертера, которые преобразуют входную строку в объект определенного класса. Есть, конечно, довольно много встроенных конвертеров для самых очевидных классов, таких как Number, Boolean, File или списки.

Таким образом, у вас будет что-то вроде этого.

public interface Converter<T> {
   public T convert( String arg );
}

public class IntegerConverter implements Converter<Integer> {
   public Integer convert( String arg ) {
       return Integer.parseInt( arg );
   }
}

И в твоем ArgumentParser Вы сохраните карту своих зарегистрированных конвертеров и позвоните соответствующему.

Конечно, вы можете уточнить это, указав иерархию конвертеров, чтобы иметь более и менее конкретные стратегии, но общая идея та же.

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

Я не знаю общего решения этой проблемы.

Но есть некоторые частичные решения, которые вместе, так что я считаю, будут отвечать за то, что вы ищете:

А. Возвращаемый объект является числовым:

Для int и float решение очевидно:

int ival = Integer.parseInt(strParam);
float fval = Float.parseFloat(strParam);

B. Возвращенный объектный класс имеет строковый конструктор, т.е. конструктор, получающий единственный строковый параметр

То есть:

parser.addOption("--input_path", MySpecialClass.class, true);

где:

class MySpecialClass {
     public MySpecialClass(String param)  // <----------- string constructor
}

Здесь вы можете использовать отражение для создания экземпляра необходимого объекта путем извлечения и активации конструктора. Примечание: нашему коду не нужно знать идентификатор возвращаемого класса, но в погоде он или нет имеет строковый конструктор:

Constructor stringConstructor = classObject.getConstructor(new Class[]{String.class});
Object objectToReturn = stringConstructor.newInstance(stringParam);


Наконец, объединение вышесказанного дает нам функцию обработки аргумента, подобную этой:

Object processAnyParam(String strParam, Class returnClass, boolean mandatory) {

     if (returnClass.equals(int.class)) {
           return Integer.parseInt(strParam);
     }
     else if (returnClass.equals(float.class)) {
          return Float.parseFloat(strParam);
     }
     else if (maybe other numeric types here...) {
     }
     else {
          try { 
                Constructor stringConstructor = classObject.getConstructor(new Class[]{String.class});
          } catch (NoSuchMethodException  e) {
                // no string constructor; bail out..
                return  null;
          }
          return stringConstructor.newInstance(strParam);
     }


}

Невозможно выполнить преобразование без передачи какого-либо метода для преобразования String в нужный тип объекта (при условии, что не требуется, чтобы у всех классов был конструктор с String в качестве параметра, который вы хотите использовать для анализа String). Одна возможность будет проходить Function<String, ?> возражает против addOption Например:

public void addOption(String optionName, Function<String, ?> valueConverter, boolean required)


parser.addOption("--stringParameter", Function.identity(), true); // remains identical
parser.addOption("--integerParameter", Integer::parseInt, true); // use Integer.parseInt(String)
parser.addOption("--binaryIntParameter", s->Integer.parseInt(s, 2), true); // integer given in binary format
parser.addOption("--fileParameter", s -> new File(s), true); // use File(String) constructor

Ваша функция разбора кода, преобразующего строку в объект, будет содержать что-то вроде этого:

Map<String, Object> result = //...
// ...
String key = //...
String stringValue = //...
Function<String, ?> converter = //... get the converter from where you stored it
result.put(key, converter.apply(stringValue));
//...
return result;
Другие вопросы по тегам