я могу рефлексивно создать экземпляр универсального типа в Java?

Возможно ли рефлексивно создать универсальный тип в Java? Используя описанную здесь технику, я получаю ошибку, потому что токены классов не могут быть общими. Возьмите пример ниже. Я хочу создать экземпляр некоторого подкласса Creator, который реализует Creator. Фактическое имя класса передается в качестве аргумента командной строки. Идея состоит в том, чтобы иметь возможность указать реализацию Creator во время выполнения. Есть ли другой способ сделать то, что я пытаюсь сделать здесь?

public interface Creator<T> {
    T create();
}
public class StringCreator implements Creator<String> {
    public String create() { return new String(); }
}
public class FancyStringCreator implements Creator<String> {
    public String create() { return new StringBuffer().toString(); }
}
public static void main(String[] args) throws Exception {
    Class<?> someClass = Class.forName(args[0]);
    /*ERROR*/Class<? extends Creator<String>> creatorClass = someClass.asSubclass(Creator.class);
    Constructor<? extends Creator<String>> creatorCtor = creatorClass.getConstructor((Class<?>[]) null);
    Creator<String> creator = creatorCtor.newInstance((Object[]) null);
}

Редактировать: мне нравится подход Маркуса как самый простой и прагматичный, не обходя при этом всю сущность дженериков. Я могу использовать его в моей ситуации, потому что я могу указать, что передаваемый класс должен быть подклассом StringCreator. Но, как указал Эриксон, универсальная информация все еще находится на уровне типов, но не на уровне времени выполнения, поэтому все еще можно рефлексивно проверить, реализует ли данный класс правильный универсальный тип.

10.12.2008 16:01:47
4 ОТВЕТА
РЕШЕНИЕ

Общая информация теряется во время выполнения. Не существует эквивалента времени выполнения Creator <String> .class. Вы можете создать тип между Creator и StringCreator, который фиксирует универсальный тип:

public interface Creator<T> {
        T create();
}
public interface StringCreator extends Creator<String> { }
public class StringCreatorImpl implements StringCreator  {
        public String create() { return new String(); }
}
public class FancyStringCreator implements StringCreator  {
        public String create() { return new StringBuffer().toString(); }
}
public static void main(String[] args) throws Exception {
        Class<?> someClass = Class.forName(args[0]);
        Class<? extends StringCreator> creatorClass = someClass.asSubclass(StringCreator.class);
        Constructor<? extends StringCreator> creatorCtor = creatorClass.getConstructor((Class<?>[]) null);
        Creator<String> creator = creatorCtor.newInstance((Object[]) null);
}

Но, конечно, вы теряете немного гибкости, потому что вы не можете использовать следующий класс создателя:

public class AnotherCreator implements Creator<String> {
    public String create() { return ""; }
}
11
10.12.2008 17:43:03
Не вся общая информация теряется во время выполнения; остается достаточно проверить необходимую информацию о типе, даже не создавая другой интерфейс. Просто у компилятора недостаточно информации, чтобы вывести правильность.
erickson 10.12.2008 19:48:48
Вы здесь То, что я сказал (сложным образом), - это то, что не существует объекта класса, который представляет Creator <String> в отличие от Creator <Integer>. Но приведенный ниже код кажется разумным способом решения проблемы.
Markus 11.12.2008 17:49:03

Вам не нужна эта линия. Вам также не нужен конструктор, поскольку вы просто используете конструктор по умолчанию. Просто создайте экземпляр класса напрямую:

public static void main(String[] args) throws Exception {
        Class<?> someClass = Class.forName(args[0]);
        Creator<String> creator = (Creator<String>) someClass.newInstance();
}

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

public static void main(String[] args) throws Exception {
    Class<?> someClass = Class.forName(args[0]);
    Class<? extends Creator> creatorClass = someClass.asSubclass(Creator.class);
    Constructor<? extends Creator> creatorCtor = creatorClass.getConstructor((Class<?>[]) null);
    Creator<String> creator = (Creator<String>) creatorCtor.newInstance((Object[]) null);
}
3
10.12.2008 17:00:35
claz.newInstance () и ctor.newInstance () не ведут себя одинаково при возникновении ошибки. Метод newInstance класса является более старым и несовместимым с другими рефлексивными вызовами «метода».
erickson 10.12.2008 16:33:34

Не совсем уверен, почему вы используете дженерики здесь.

Инстанцирование объекта с использованием отражения предложило бы общее использование, но, вероятно, createв какой-то момент вы будете вызывать и присваивать результат a String, иначе зачем использовать обобщенные элементы для управления типом возвращаемого значения.

Но если вы написали следующую реализацию Creator:

public class IntegerCreator implements Creator<Integer> 
{
  public Integer create() 
  { 
    ...
  }
}

И передав его в качестве аргумента, вы получите ClassCastException при вызове createи назначении результата.

-1
10.12.2008 16:37:37

Это будет делать то, что вы пытаетесь сделать, обеспечивая безопасность типов. Нет способа избежать неконтролируемого предупреждения, но проверка типов, выполненная здесь, оправдывает его подавление.

  public static void main(String[] args)
    throws Exception
  {
    Class<? extends Creator<String>> clz = load(argv[0], String.class);
    Constructor<? extends Creator<String>> ctor = clz.getConstructor();
    Creator<String> creator = ctor.newInstance();
    System.out.println(creator.create());
  }

  public static <T> Class<? extends Creator<T>> load(String fqcn, Class<T> type)
    throws ClassNotFoundException
  {
    Class<?> any = Class.forName(fqcn);
    for (Class<?> clz = any; clz != null; clz = clz.getSuperclass()) {
      for (Object ifc : clz.getGenericInterfaces()) {
        if (ifc instanceof ParameterizedType) {
          ParameterizedType pType = (ParameterizedType) ifc;
          if (Creator.class.equals(pType.getRawType())) {
            if (!pType.getActualTypeArguments()[0].equals(type))
              throw new ClassCastException("Class implements " + pType);
            /* We've done the necessary checks to show that this is safe. */
            @SuppressWarnings("unchecked")
            Class<? extends Creator<T>> creator = (Class<? extends Creator<T>>) any;
            return creator;
          }
        }
      }
    }
    throw new ClassCastException(fqcn + " does not implement Creator<String>");
  }

Основное ограничение, которое вы должны соблюдать, заключается в том, что класс в иерархии должен указывать параметр типа. Например class MyCreator implements Creator<String>. Вы не можете использовать это с class GenericCreator<T> implements Creator<T>.

В настоящее время он не обрабатывает допустимый случай, когда вы создаете новый интерфейс interface StringCreatorIfc extends Creator<String>и имеете класс, реализующий это. Это можно улучшить, но я оставлю это как упражнение для склонных.

5
10.12.2008 17:48:55