Динамический типичный ввод в 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
.
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
чтобы ваши параметризованные классы всегда знали свои типы!
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()) {};
}
}
Заметки:
- Прекрасно работает для классов, которые по умолчанию не имеют конструктора arg.
- Работает лучше, чем прямое отражение, если по умолчанию нет конструкторов arg.
- Должно хорошо
.getRawType()
с Guice, позволяющим вам использовать сгенерированный Class<T>
.getRawType()
Class<T>
для передачи в getInstance()
Инжектора. еще не пробовал, просто подумал! - Вы можете использовать
Class<T>.cast()
для выполнения приведения, для которого не требуется @SuppressWarning("unchecked")
повсюду.
Ответ 3
Если вы не знаете тип, вы не можете принудительно проводить проверки времени компиляции с помощью дженериков.
Просто для того, чтобы использовать его, вы могли бы сказать
new Record<Object>(object);