Как я могу создать типичный тип в Java?
Я добавил в приложение удобный для пользователя файл конфигурации, используя java.util.Properties
и пытаюсь добавить вокруг него обертку, чтобы упростить преобразования типов. В частности, я хочу, чтобы возвращаемое значение "наследовало" его тип из предоставленного значения по умолчанию. Вот что у меня до сих пор:
protected <T> T getProperty(String key, T fallback) {
String value = properties.getProperty(key);
if (value == null) {
return fallback;
} else {
return new T(value);
}
}
(Полный пример источника.)
Возвращаемое значение из getProperty("foo", true)
будет тогда логическим, независимо от того, было ли оно прочитано из файла свойств и аналогично для строк, целых чисел, удвоений и c. Конечно, приведенный выше фрагмент фактически не компилируется:
PropertiesExample.java:35: unexpected type
found : type parameter T
required: class
return new T(value);
^
1 error
Я делаю это неправильно или просто пытаюсь сделать что-то, что не может быть сделано?
Изменить: Пример использования:
// I'm trying to simplify this...
protected void func1() {
foobar = new Integer(properties.getProperty("foobar", "210"));
foobaz = new Boolean(properties.getProperty("foobaz", "true"));
}
// ...into this...
protected void func2() {
foobar = getProperty("foobar", 210);
foobaz = getProperty("foobaz", true);
}
Ответы
Ответ 1
Из-за типа erasure вы не можете создавать экземпляры универсальных объектов. Обычно вы можете сохранить ссылку на объект Class
, представляющий этот тип, и использовать его для вызова newInstance()
. Однако это работает только для конструктора по умолчанию. Поскольку вы хотите использовать конструктор с параметрами, вам нужно найти объект Constructor
и использовать его для создания экземпляра:
protected <T> T getProperty(String key, T fallback, Class<T> clazz) {
String value = properties.getProperty(key);
if (value == null) {
return fallback;
} else {
//try getting Constructor
Constructor<T> constructor;
try {
constructor = clazz.getConstructor(new Class<?>[] { String.class });
}
catch (NoSuchMethodException nsme) {
//handle constructor not being found
}
//try instantiating and returning
try {
return constructor.newInstance(value);
}
catch (InstantiationException ie) {
//handle InstantiationException
}
catch (IllegalAccessException iae) {
//handle IllegalAccessException
}
catch (InvocationTargetException ite) {
//handle InvocationTargetException
}
}
}
Однако, видя, сколько проблем для достижения этой цели, включая затраты на использование рефлексии, стоит сначала взглянуть на другие подходы.
Если вам абсолютно необходимо выполнить этот маршрут, и если T
ограничен отдельным набором типов, известных во время компиляции, компромисс должен состоять в том, чтобы сохранить статический Map
of Constructor
s, который загружен при запуске - таким образом вам не придется динамически искать их при каждом вызове этого метода. Например, Map<String, Constructor<?>>
или Map<Class<?>, Constructor<?>>
, который заполняется с помощью статического блока.
Ответ 2
Генерики реализуются с использованием стирания типа в Java. В английских терминах большая часть общей информации теряется во время компиляции, и вы не можете узнать фактическое значение T
во время выполнения. Это означает, что вы просто не можете создавать общие типы.
Альтернативное решение - предоставить вашему классу тип во время выполнения:
class Test<T> {
Class<T> klass;
Test(Class<T> klass) {
this.klass = klass;
}
public void test() {
klass.newInstance(); // With proper error handling
}
}
Изменить: Новый пример ближе к вашему случаю
static <T> T getProperty(String key, T fallback, Class<T> klass) {
// ...
if (value == null) {
return fallback;
}
return (T) klass.newInstance(); // With proper error handling
}
Ответ 3
Это то, что вы не можете сделать.
Из-за стирания типа тип T
, известный во время компиляции, недоступен для JVM во время выполнения.
Для вашей конкретной проблемы я считаю, что наиболее разумным решением является запись кода вручную для каждого другого типа:
protected String getProperty(String key, String fallback) { ... return new String(value); }
protected Double getProperty(String key, Double fallback) { ... return new Double(value); }
protected Boolean getProperty(String key, Boolean fallback) { ... return new Boolean(value); }
protected Integer getProperty(String key, Integer fallback) { ... return new Integer(value); }
Примечания:
- В стандартном API Java вы найдете много мест, где есть набор связанных методов, которые отличаются только типами ввода.
- В С++ ваш, вероятно, можно было бы решить с помощью шаблонов. Но С++ представляет много других проблем...
Ответ 4
Если вы хотите сохранить свою существующую подпись метода, сделайте это так.
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;
public class Main
{
private final Properties properties;
public Main()
{
this.properties = new Properties();
this.properties.setProperty("int", "1");
this.properties.setProperty("double", "1.1");
}
public <T> T getProperty(final String key, final T fallback)
{
final String value = this.properties.getProperty(key);
if (value == null)
{
return fallback;
}
else
{
try
{
return (T) fallback.getClass().getConstructor(new Class<?>[] { String.class } ).newInstance(value);
}
catch (final InstantiationException e)
{
throw new RuntimeException(e);
}
catch (final IllegalAccessException e)
{
throw new RuntimeException(e);
}
catch (final InvocationTargetException e)
{
throw new RuntimeException(e);
}
catch (final NoSuchMethodException e)
{
throw new RuntimeException(e);
}
}
}
public static void main(final String[] args)
{
final Main m = new Main();
final Integer i = m.getProperty("int", new Integer("0"));
final Double d = m.getProperty("double", new Double("0"));
System.out.println(i);
System.out.println(d);
}
}
Ответ 5
Попробуйте следующее:
protected <T> T getProperty(String key, T fallback) {
String value = properties.getProperty(key);
if (value == null) {
return fallback;
} else {
Class FallbackType = fallback.getClass();
return (T)FallbackType.cast(value);
}
}