Не уверен, как выразить это словами, поэтому я просто начну с кода, который у меня есть:
// "root type" for all resources
// fixed
public class ResourceClassA
{ }
// "base type" for all resources for a specific domain
// fixed
public class ResourceClassB extends ResourceClassA
{ }
// specific resource type
// can be derived from further but don't think that matters here
// not fixed but heavily constrained in other ways
public class ResourceClassC extends ResourceClassB
{ }
// only needed for a negative example below, irrelevant otherwise
public class ResourceClassD extends ResourceClassB
{ }
// fixed
public class Remote
{
public <T extends ResourceClassA> Set<T> read(Class<T> baseType, Class<? extends T> subType);
}
// semi-fixed: read() signature can be modified
public interface AbstractRemoteAccess<T extends ResourceClassA>
{
Set<T> read(Class<? extends T> clazz);
}
// semi-fixed: read() signature can be modified
public class SpecificRemoteAccess<T extends ResourceClassA> implements AbstractRemoteAccess<T>
{
private Class<T> _baseType;
private Remote _remote;
public Set<T> read(Class<? extends T> clazz)
{
return _remote.read(_baseType, clazz);
}
}
// not fixed
public class ConsumerClass
{
public void doSomething(AbstractRemoteAccess<ResourceClassB> remoteAccess)
{
Set<ResourceClassB> rawObjects = remoteAccess.read(ResourceClassC.class);
Set<ResourceClassC> castedObjects = rawObjects.stream()
.map(c -> (ResourceClassC) c)
.collect(Collectors.toSet());
}
}
Все классы, отмеченные fixed , не могут быть изменены, они предоставляются как есть - наоборот для не фиксировано . Класс SpecificRemoteAccess - это тот, который я хочу изменить: я бы хотел, чтобы метод read ()
- не возвращал свой результат как Установите , но как Установите <> типа generi c соответствия clazz
- , чтобы вызывающая сторона не должна приводить результат метода, см. ConsumerClass.doSomething ()
- и все это без потери безопасности типов
Самый простой способ, который я видел, это сделать
Set<V> read(Class<V extends T> clazz)
но это выдает эту ошибку:
Incorrect number of arguments for type Class<T>; it cannot be parameterized with arguments <V, T>
, что, если я правильно его интерпретирую, означает, что компилятор обрабатывает V & T как отдельный тип Аргументы для Class , который не соответствует его определению.
Затем я попытался добавить второй тип generi c V и использовать его как тип generi c для типа возврата читать () . Я начал с
<V extends T> Set<V> read(Class<? extends T> clazz)
, который не ограничивает V до clazz , то есть оба они будут приняты компилятором
Set<ResourceClassC> correct = remoteAccess.read(ResourceClassC.class);
Set<ResourceClassD> incorrect = remoteAccess.read(ResourceClassC.class);
Объявление типа для неверно семантически неверно, но синтаксически хорошо. Поэтому я попытался ограничить V на основе clazz , но единственное решение, о котором я мог подумать, это
<V extends T> Set<V> read(Class<V extends T> clazz, Class<V> classV)
, которое, в некоторой степени, решает проблему сверху:
// compiles
Set<ResourceClassC> correct = remoteAccess.read(ResourceClassC.class,
ResourceClassC.class);
// error: Type mismatch: cannot convert from Set<ResourceClassC> to Set<ResourceClassD>
Set<ResourceClassD> incorrect = remoteAccess.read(ResourceClassC.class,
ResourceClassC.class);
но не только делает вызов read () громоздким (пользователям будет интересно, почему они должны передавать одну и ту же информацию дважды), но и подвержен ошибкам:
// compiles
Set<ResourceClassC> correct = remoteAccess.read(ResourceClassC.class,
ResourceClassC.class);
// type error
Set<ResourceClassD> incorrect = remoteAccess.read(ResourceClassC.class,
ResourceClassC.class);
// compiles but will cause run-time cast failures
Set<ResourceClassD> incorrect2 = remoteAccess.readAndCast2(ResourceClassC.class,
ResourceClassD.class);
Учитывая, что разработчики на стороне потребителя сталкиваются с сотнями классов ресурсов, таких как ResourceClass C, делая read () подверженным ошибкам просто не вариант.
Буду признателен, если кто-нибудь укажет на мою ошибку.