Как я могу создать типичный тип в 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);
    }
}