Generics и Class.forName - PullRequest
       53

Generics и Class.forName

22 голосов
/ 14 ноября 2009

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

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

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();
    }
}

Спасибо

Ответы [ 6 ]

32 голосов
/ 14 ноября 2009

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

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 указывает, что нам нужно добавить typecast, чтобы сделать эту компиляцию; т.е.

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

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

2 голосов
/ 14 ноября 2009

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


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

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

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

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


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

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
}
2 голосов
/ 14 ноября 2009

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

1 голос
/ 14 ноября 2009

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

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

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);
0 голосов
/ 29 марта 2016

Я нашел интересную вещь: 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;
    }
}

enter image description here

0 голосов
/ 14 ноября 2009

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

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

...