Загрузка класса из строки - PullRequest
       2

Загрузка класса из строки

26 голосов
/ 01 февраля 2011

Я хочу создать экземпляр класса по значению String. Я нашел несколько учебных пособий, которые показывают несколько методов для этого. Класс ДОЛЖЕН наследоваться от определенного интерфейса, ImplementMe, который имеет специальный метод с именем runMe() Итак, вот что я попробовал:

ImplmentMe a =
   (ImplementMe) ImplementMe.class
                   .getClassLoader()
                   .loadClass("my.package.IImplementedYou")
                   .newInstance();
a.runMe();

Это работает, но это так ужасно. Я, по крайней мере, ожидал, что актерский состав не нужен. Пожалуйста, скажите мне, что есть лучший способ.

Ответы [ 8 ]

34 голосов
/ 01 февраля 2011

Нет, лучшего способа нет (по замыслу).Вы не должны этого делать, Java разработан как типобезопасный язык.Однако я могу понять, что иногда вам нужно делать такие вещи, и для этих целей вы можете создать библиотечную функцию, например, такую:

public <T> T instantiate(final String className, final Class<T> type){
    try{
        return type.cast(Class.forName(className).newInstance());
    } catch(InstantiationException
          | IllegalAccessException
          | ClassNotFoundException e){
        throw new IllegalStateException(e);
    }
}

Теперь ваш клиентский код может по крайней мере вызвать этот метод без приведения:

MyInterface thingy =
    instantiate("com.foo.bar.MyInterfaceImpl", MyInterface.class);
12 голосов
/ 01 февраля 2011

Попробуйте Class.forName("my.package.IImplementedYou").

6 голосов
/ 08 июля 2013

Вот как бы я это сделал:

ImplementMe noCastNeeded = 
    this.getClassLoader()
        .loadClass("my.package.IImplementedYou")
        .asSubclass(ImplementMe.class).newInstance();

Есть несколько исключений для ловли, но это нормально, я думаю. :)

2 голосов
/ 01 февраля 2011

По сути, это то, что произойдет, независимо от того, используете ли вы для этого сторонний инструментарий или нет. Приведение объекта по своей сути будет обязательным , если не ожидается Object. Тем не менее, вы можете сделать рутину, которая сделает это за вас:

public <T> T instantiateObject(String name, Class<T> cls) throws Exception {
    return (T) Class.forName(name).newInstance();
}

Который вы можете использовать:

AClass cls = instantiateObject("com.class.AClass", AClass.class);

Но если вы зайдете так далеко, String name на самом деле является избыточным (учитывая, что AClass - конкретный класс). Вы могли бы также:

public <T> T instantiateObject(Class<T> cls) throws Exception {
    return (T) Class.forName(cls.getCanonicalName()).newInstance();
}

Который вы можете использовать:

AClass cls = instantiateObject(AClass.class);
1 голос
/ 01 февраля 2011

Альтернативой является использование forName, но оно не становится намного лучше, чем у вас сейчас:

ImplementMe a = 
    (ImplementMe) Class.forName("my.package.IImplementedYou").newInstance();
a.runMe();

Действительно, forName будет использовать getClassLoader().loadClass() за сценой для загрузкикласс, как вы можете видеть в исходном коде из Class.java.

1 голос
/ 01 февраля 2011

Вы можете сократить его немного, как

ImplementMe a = (ImplementMe) Class
                               .forName("my.package.IImplementedYou")
                               .newInstance();

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

0 голосов
/ 01 февраля 2011

То, что у вас есть, может работать, но вам не нужно загружать класс, используя тот же загрузчик классов, который загружал ImplementMe.Это должно работать одинаково хорошо:

Object newInstance = this.getClass().getClassLoader().loadClass("my.package.IImplementedYou").newInstance();

Важно то, что загрузчик классов знает как файл класса с реализацией "my.package.IImplementedYou", так и класс с реализацией "ImplementMe".

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

if(newInstance instanceof my.package.IImplementedYou) {  
    ((ImplementMe)newInstance).runMe(); 
}

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

Class c = this.getClass().getClassLoader().loadClass("my.package.IImplementedYou");
if(ImplementMe.class.isAssignableFrom(c)) {
    Object newInstance = c.newInstance(); 
}
0 голосов
/ 01 февраля 2011

Вам понадобится приведение, потому что компилятор не может сказать из кода, что объект имеет тип ImplementMe.Таким образом, он требует, чтобы программист выполнил приведение, которое вызовет исключение ClassCastException, если объект не является экземпляром ImplementMe.

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