я могу рефлексивно создать универсальный тип в Java? - PullRequest
15 голосов
/ 10 декабря 2008

Возможно ли рефлексивно создать экземпляр универсального типа в 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. Но, как указал Эриксон, обобщенная информация все еще находится на уровне типов, но не на уровне времени выполнения, поэтому все еще можно рефлексивно проверить, реализует ли данный класс правильный обобщенный тип.

Ответы [ 4 ]

9 голосов
/ 10 декабря 2008

Общая информация теряется во время выполнения. Не существует эквивалента времени выполнения Creator .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 ""; }
}
5 голосов
/ 10 декабря 2008

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

  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> и имеете класс, реализующий это. Это можно улучшить, но я оставлю это как упражнение для склонных.

3 голосов
/ 10 декабря 2008

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

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);
}
0 голосов
/ 10 декабря 2008

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...