Загрузка класса forName с сохранением типов

#java #generics

#java #общие сведения

Вопрос:

Как мне вызвать, Class.forName() если результатом является универсальный тип? Обычно я могу использовать asSubclass() , но здесь единственный способ, который я вижу для этого, — это приведение, которое меня раздражает, когда все остальное красиво типизировано с помощью дженериков.

Сценарий выглядит примерно так:

Существует файл .jar с одной точкой входа, основным классом, который имеет main() . Для этого требуется параметр classname (и некоторые другие, здесь не относящиеся к делу). Данный класс реализует Callable<Integer> . Этот класс загружен, инициализирован и запущен.

Вот пример того, что мне нужно:

 Class<? extends Callable<Integer>> clazz = (Class<? extends Callable<Integer>>) Class.forName(options.valueOf(className)).asSubclass(Callable.class);
  

Есть ли какой-нибудь способ избавиться от этого приведения?

С использованием SE6.

Ответ №1:

Сначала вы, вероятно, хотите полный универсальный Class

 Class<Callable<Integer>> classCI = ...;
  

Тогда система типов Java не имеет проблем с

 Class<? extends Callable<Integer>> clazz =     
    Class.forName(options.valueOf(className))
    .asSubclass(classCI);
  

Как мы можем получить classCI ? Мы можем обмануть, не проверяя приведение

 Class<Callable<Integer>> classCI = (Class<Callable<Integer>>)Callable.class;
  

Это по своей сути небезопасно. Должны быть внешние силы, чтобы убедиться, что className действительно является Callable<Integer> . Например, если это a Callable<String> , программа выполняет все приведения без каких-либо проблем, и она запускается намного позже, когда Integer call() вызывается, и сообщение об ошибке будет очень вводящим в заблуждение.

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

 Object o = ...;
String s1 = (String)o; // may fail, no javac warning
String s2 = String.class.cast(o); // may fail, no javac warning
  

при условии, что при сбое приведения во время выполнения немедленно выдается исключение.

Чтобы быть типобезопасным, мы должны предварительно проверить общий тип className

 @SuppressWarning( "unchecked" )
Class<? Callable<Integer>> getClass(String className)
{
    Class clazz = Class.forName(className);
    via reflection, check generic super interfaces of clazz
    if there's no Callable<Integer> super interface
        throw "className is not a Callable<Integer>"

    // we have *checked*, the following cast is safe
    return (Class<? Callable<Integer>>)clazz; 
}
  

У нас есть основания исключить здесь «непроверенный», потому что реализация проверяет, чтобы убедиться, что если className на самом деле не обозначает класс, реализующий Callable<Integer> , он немедленно выдает исключение прямо там. Наше приведение «проверено», и программа типобезопасна.

Комментарии:

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

2. К вашему сведению, класс приведения<Вызываемый<Целое число>> classCI = (Класс<Вызываемый<целое число>>)Callable.class по какой-то причине не работает. В любом случае, поскольку я проверяю вызываемое<Integer> с помощью отражения, не такая уж большая проблема, просто есть о чем подумать, если вы когда-нибудь захотите это отредактировать.