ServiceLoader, где тип parm сам по себе является общим
class ServiceLoader<S> implements Iterable<S> {
// ...
}
interface Foo<T> {
// ...
}
class FooRepository {
void add(Iterable<Foo<?>> foos) {
// ...
}
}
FooRepository repo = new FooRepository();
repo.add(ServiceLoader.load(Foo.class));
Это приводит к ошибке компилятора: "Метод add(Iterable<Foo<?>>)
в типе FooRepository не применим для аргументов (ServiceLoader<Foo>)
".
Я надеялся, что смогу вылечить Iterable<Foo>
как Iterable<Foo<?>>
, но само по себе это не похоже на работу. Что было бы самым чистым способом трансмутировать результат ServiceLoader.load(Foo.class)
в то, что я могу накормить FooRepository.add()
?
3 ответа
Если вы говорите о java.util.ServiceLoader<S>
затем его Javadoc говорит:
Класс провайдера обычно является не самим провайдером, а прокси-сервером, который содержит достаточно информации, чтобы решить, способен ли провайдер удовлетворить определенный запрос вместе с кодом, который может создать фактического провайдера по требованию. [...] Единственным требованием, предъявляемым этой возможностью, является то, что классы провайдеров должны иметь конструктор с нулевым аргументом, чтобы их можно было создавать во время загрузки.
В сочетании с тем, что класс провайдера явно создается с помощью отражения, это говорит мне о том, что класс провайдера, объявленный с аргументом универсального типа, будет фактически создан как необработанный тип.
Таким образом, потому что вы передаете объект типа Class<Foo>
- и в Java нет способа создать объект типа Class<Foo<Bar>>
- то, что вы получите, это то, что вы просили, Iterable<Foo>
, где Foo
это необработанный тип, созданный из Foo<T>
,
К сожалению, я думаю простой ответ: не делай этого тогда! Либо сделайте класс вашего провайдера конкретным подклассом универсального типа, либо сделайте класс провайдера просто фабрикой, которая может создавать новые объекты соответствующего универсального типа.
Честно говоря, я не могу придумать какой-либо веской причины для создания универсального класса поставщика услуг - где бы вы предоставили аргументы типа? Я думаю, что заводской механизм - ваш лучший выбор.
Это потому, что универсальные типы предназначены только для времени компиляции. Они стираются во время выполнения (тип стирания). Универсальный тип T интерфейса Foo больше не будет известен во время выполнения, поэтому метод "add" класса Repo не позволит вам (в общем) вводить тип аргумента (не позволит вам иметь аргумент типа Foo<?>
, поскольку он не может гарантировать, что внутри метода вы сможете обработать этот аргумент как аргумент типа F<?>
, Он только знает, что он относится к типу Foo, потому что это фактический тип, но он не позволит вам набрать его в дальнейшем. Ваш код будет работать, если вы измените метод на
void add(Iterable<Foo> foos)
Вина ложится на систему типов Java. Вам нужен грубый бросок.
Любой актерский состав Foo.class
в Class<Foo<?>>
, или же ServiceLoader<Foo>
в ServiceLoader<Foo<?>>
, Оба явно безвредны.