Динамический типичный ввод в Java

Если у меня есть класс, использующий общий тип, например

public class Record<T> {
    private T value;

    public Record(T value) {
        this.value = value;
    }
}

довольно просто ввести все во время разработки, если я знаю все типы, которые используются, например, в этом примере:

// I type explicitly
String myStr = "A";
Integer myInt = 1;
ArrayList myList = new ArrayList();

Record rec1 = new Record<String>(myStr);
Record rec2 = new Record<Integer>(myInt);
Record rec3 = new Record<ArrayList>(myList);

Что произойдет, если я получу список объектов из "где-нибудь", где я не знаю тип? Как назначить тип:

// now let assume that my values come from a list where I only know during runtime what type they have

ArrayList<Object> myObjectList = new ArrayList<Object>();
    myObjectList.add(myStr);
    myObjectList.add(myInt);
    myObjectList.add(myList);

    Object object = myObjectList.get(0);

    // this fails - how do I do that?
    new Record<object.getClass()>(object);

Ответы

Ответ 1

Java-дженерики не являются шаблонами С++.

Генераторы Java - это функция времени компиляции, а не функция времени выполнения.

Вот ссылка на учебное пособие Java generics.

Это никогда не может работать с Java:

new Record<object.getClass()>(object);

Вы должны либо использовать полиморфизм (например, каждый объект реализует известный интерфейс), либо RTTI (instanceof или Class.isAssignableFrom()).

Вы можете сделать это:

     class Record
     {
       public Record(String blah) { ... }
       public Record(Integer blah) { ... }
       ... other constructors.
     }

или вы можете использовать шаблон Builder.

Ответ 2

Создание экземпляров из универсальных типов во время выполнения

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

Эту проблему можно решить с помощью среды сценариев (Groovy, JavaScript, JRuby, Jython), которая может динамически оценивать и выполнять произвольный код для создания объектов, но это становится чрезвычайно сложным и чрезмерно сложным, просто для создания объекта.

Но, к сожалению, я думаю, что это очень пешеходное решение.

Пока существует предопределенный набор поддерживаемых типов, вы можете использовать шаблон Factory. Здесь я просто использую интерфейс Provider<>T из javax.inject/com.google.inject.

Q26289147_ProviderPattern.java

public class Q26289147_ProviderPattern
{
    private static final List<String> CLASS_NAMES = ImmutableList.of("String", "Integer", "Boolean");
    private static final Map<String, Provider<StrawManParameterizedClass>> PROVIDERS;

    static
    {
        final ImmutableMap.Builder<String, Provider<StrawManParameterizedClass>> imb = ImmutableMap.builder();
        for (final String cn : CLASS_NAMES)
        {
            switch (cn)
            {
                case "String":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<String> get() { return new StrawManParameterizedClass<String>() {}; }
                    });
                    break;
                case "Integer":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                case "Boolean":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                default:
                    throw new IllegalArgumentException(String.format("%s is not a supported type %s", cn, Joiner.on(",").join(CLASS_NAMES)));
            }
        }
        PROVIDERS = imb.build();
    }

    static <T> void read(@Nonnull final StrawManParameterizedClass<T> smpc) { System.out.println(smpc.type.toString()); }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};

        @Override
        public String toString() { return type.getRawType().getCanonicalName(); }
    }

    public static void main(final String[] args)
    {
        for (final String cn : CLASS_NAMES)
        {
            read(PROVIDERS.get(cn).get());
        }
    }
}

Отказ от ответственности:

Это просто пример концепции, я бы никогда не использовал такой оператор switch в производственном коде, как Strategy Pattern или Strategy Pattern Chain of Responsibility чтобы инкапсулировать логику того, какой тип создать на ClassName ключа ClassName.

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

Тем не менее, вам не нужно передавать экземпляры Class<?> Вы можете получить информацию об Generic Type из параметризованных классов во время выполнения с помощью TypeToken из TypeToken.

Вы даже можете создавать экземпляры любого универсального типа во время выполнения с TypeToken из библиотеки Guava.

Основная проблема в том, что этот синтаксис не поддерживается: Geography<myClass.newInstance()> geo; и я никак не могу придумать, что это подделка, кроме реализации Provider описанной выше.

Вот соломенный пример того, как использовать TypeToken чтобы ваши параметризованные классы всегда знали свои типы!

Q26289147.java

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

Заметки:

  1. Прекрасно работает для классов, которые по умолчанию не имеют конструктора arg.
  2. Работает лучше, чем прямое отражение, если по умолчанию нет конструкторов arg.
  3. Должно хорошо .getRawType() с Guice, позволяющим вам использовать сгенерированный Class<T> .getRawType() Class<T> для передачи в getInstance() Инжектора. еще не пробовал, просто подумал!
  4. Вы можете использовать Class<T>.cast() для выполнения приведения, для которого не требуется @SuppressWarning("unchecked") повсюду.

Ответ 3

Если вы не знаете тип, вы не можете принудительно проводить проверки времени компиляции с помощью дженериков.

Просто для того, чтобы использовать его, вы могли бы сказать

new Record<Object>(object);