Преобразовать строку в 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;