Подтвердить что ты не робот

Generics и Class.forName

Я хотел бы создать экземпляр указанного класса, используя его имя. Мой код показан ниже.

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

public static <T> T create(final String className) {
    try {
        final Class<?> clazz = Class.forName(className);

        //WARNING: Type safety: Unchecked cast from capture#2-of ? to T
        return (T) create(clazz); 
    }
    catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

public static <T> T create(final Class<T> classToCreate) {
    final Constructor<T> constructor;
    try {
        constructor = classToCreate.getDeclaredConstructor();
        final T result = constructor.newInstance();
        return result;
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

Спасибо

4b9b3361

Ответ 1

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

public static <T> T create(final String className, Class<T> ifaceClass) 
throws ClassNotFoundException {
    final Class<T> clazz = Class.forName(className).asSubclass(ifaceClass);
    return create(clazz); 
}

Невозможно выполнить приведение типов с использованием параметра типа... без этих досадных предупреждений о безопасности типов.

Кстати, если вы игнорируете эти предупреждения, метод create может создать экземпляр некоторого класса, который несовместим с фактическим типом, используемым вызывающим. Вероятно, это приведет к неожиданному ClassCastException позже; например когда экземпляр назначен.


EDIT: @Pascal указывает, что нам нужно добавить приведение типа для компиляции; то есть.

Class<T> clazz = (Class<T>) Class.forName(className).asSubclass(ifaceClass);

К сожалению, нам также нужно добавить аннотацию @SuppressWarnings.

Ответ 2

Я думаю, это потому, что Class.forName(..) не параметризуется для T. Когда вы запускаете автозаполнение eclipse, он предполагает, что clazz.newInstance() возвращает Object. Итак, сохраните бросок и добавьте @SuppressWarnings. Если вы не используете этот метод правильно (т.е. String str = Utils.create("java.util.ArrayList");), тогда будет ClassCastException, но это нормально.

Ответ 3

Второй метод в порядке.


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

Точка метода должна была бы инициировать класс, который вызывающий код не знает во время компиляции, он знает об этом только во время выполнения.

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

Поэтому я бы сказал, что весь метод не имеет смысла.


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

public static <T> T create(final Class<T> superType, final String className) {
  try {
    final Class<?> clazz = Class.forName(className);
    final Object object = clazz.newInstance();
    if (superType.isInstance(object)) {
      return (T)object; // safe cast
    }
    return null; // or other error 
  } // catch and other error code
}

Ответ 4

Вы не можете ограничить параметр типа, который должен содержать тип nameName. Следовательно, вызывающий может предоставить любой тип функции create (String), которая, конечно же, не безопасна для типа.

Так как вы не можете статически применять то, что возвращенный экземпляр реализует какой-либо интерфейс (без того, чтобы вызывающий абонент сказал вам, передав соответствующий объект класса), я бы обойтись без дженериков и заставить вызывающего пользователя использовать любой тип, который он ожидает.

public static Object create(final String className) {
    try {
        final Class<?> clazz = Class.forName(className);
        return create(clazz); 
    }
    catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

Затем вызывающий может написать:

Foo foo = (Foo) create("my.cool.FooBar");

в отличие от

Foo foo = create("my.cool.FooBar", Foo.class);

Ответ 5

Даже если вы можете заставить код выше работать, метод newInstance() конструктора предполагает конструктор нулевых аргументов.

Если класс не имеет одного конструктора нулевого аргумента класса, который вы пытаетесь создать, был явно объявлен частным или пакетом в зависимости от того, откуда вызван метод, вы получите исключение IllegalAccessException. Что-то добавить к вашему предусловию, если вы его заработаете.

Ответ 6

Я нашел интересную вещь: Type можно преобразовать в Class напрямую, если это не общий тип. Но я все еще не могу Class.forName("java.util.List<SomeType>").

import java.lang.reflect.*;
import java.util.*;

public class Main {
    public void func(List<List<String>> listlist, Map<String, List<String>> list, String s) {}

    public static void main(String[] args) throws ClassNotFoundException {
        Method m = Main.class.getDeclaredMethods()[1];

        Type t0 = m.getGenericParameterTypes()[0];
        boolean b0 = t0 instanceof Class;

        boolean b01 = ((ParameterizedType)t0).getRawType() instanceof Class;

        Type t1 = m.getGenericParameterTypes()[2];
        boolean b1 = t1 instanceof Class;
    }
}

введите описание изображения здесь