Проблема с возвратом типа Java Generics

У меня есть следующий метод:

public <T extends Result> T execute(Command<T> command)
{
     return new LoginResult();
}

Здесь Result - это интерфейс, а класс LoginResult реализует этот интерфейс. Однако я получаю сообщение об ошибке:

Несовместимые типы, требуемые: T, найдены: com.foo.LoginResult

Тем не менее, если я изменяю сигнатуру метода на:

public Result execute(Command<T> command)

Тогда одна и та же обратная линия работает нормально, без ошибок.

Какая проблема здесь? И как я могу вернуть LoginResult из этого метода?

Изменить: причина, по которой я хочу использовать generics, заключается в том, что я могу сделать что-то вроде следующего:

Command<LoginResult> login = new Command<>();
 LoginResult result = execute( login );

Ответ 1

Чтобы ответить на отредактированный вопрос,

нет никакого способа сделать это без явного приведения. Таким образом, самым простым (но жестоким) решением было бы следующее:

public <T extends Result> T execute(Command<T> command) {
    return (T) new LoginResult();
}

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

Единственное, что могло бы помочь вам динамически создавать вещи, было бы ссылкой на фактический Class<T>.

Итак, если вы добавите в команду такой метод, как Class<T> getResultType(), вы сможете написать:

return command.getResultType().newInstance(); // instead of new SpecificResult()

Это, конечно, означает, что у вас есть конструктор по умолчанию в каждой реализации Result и так далее...

Более дружественный подход OO (без отражения) заключается в том, чтобы позволить команде создать собственный результат (с помощью метода factory T instantiateResult()):

return command.instantiateResult();

Ответ 2

Вы не можете этого сделать, потому что компилятор не может подтвердить, что LoginResult имеет тип T, так как он выводится на сайт вызова (т.е. вызывающий принимает решение о том, какой тип параметра будет использоваться).

Ответ 3

Развернуть на SimonC немного.

У вас есть классы:

Result

LoginResult extends Result

OtherResult extends Result

Если ваш тип T равен OtherResult, то попытка вернуть LoginResult является бессмысленной. Компилятор будет компилировать его только в том случае, если он может ГАРАНТИРОВАТЬ чувствительность во время компиляции. В его нынешнем виде это невозможно, так как T может быть несовместимым с LoginResult.

Тип возврата <T extends Result> НЕ означает, что вы должны вернуть что-то, что является Result. Это означает, что вам нужно вернуть что-то, что является T, но T должно быть подклассом Result

Относительно вашего редактирования

Изменить: причина, по которой я хочу использовать дженерики, - значит, я могу сделать что-то вроде следующее:

Command<LoginResult> login = new Command<>();
 LoginResult result = execute( login );

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

Тогда у вас будет

public T execute()
{

}

Проблема заключается в необходимости создания экземпляра LoginResult. Здесь мы нуждаемся в дополнительной информации в вашей конкретной проблеме, чтобы дать подробный ответ.

Я бы сделал статический метод в Result, называемом newInstance. Тогда, поскольку вы знаете, что T - это некоторый подкласс результата, вы можете вызвать T.newInstance(). Тогда ваш потенциальный конструктор для loginResult может быть закрытым, и вы вызываете его через метод newInstance.

Для этого требуется, чтобы ваша команда определялась как:

public class Command<T extends Result>

И результат должен иметь метод с сигнатурой:

public static Result newInstance()

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

public class ResultCommand<T extends Result> extends Command

Ответ 4

Более безопасное решение, чем неконтролируемое исполнение:

public <T extends Result> T execute(Command<T> command, Class<T> cls)
{
    return cls.cast(new LoginResult());
}

LoginResult result = execute(loginCommand, LoginResult.class);

Обоснование наличия обоих параметров состоит в том, что контракт execute подразумевается как:

  • Либо используйте метод Command, чтобы получить результат типа T, чтобы вернуть его.
  • Или создайте результат каким-либо другим способом, но убедитесь, что он имеет тип T, опуская его.

С генериками Java только параметры для общего метода формы <T> T foo(Bar<T>, Baz<T>) могут безопасно "создать" объект типа T. Таким образом, если в вашем случае вы не создаете результат с помощью параметра Command, вам нужен еще один параметр, который может позаботиться о проверке типа.

Ответ 5

Компилятор должен знать, что возвращаемое значение будет соответствовать типу T, и оно не может. Он будет соответствовать, если вы передадите параметр Command as, но это может не произойти, если вы передадите команду, так как T будет определен для этого другого типа.

Вы можете использовать его во время выполнения, и он будет генерировать ClassCastException, если возвращаемый объект не соответствует T.

public <T extends Result> T execute(Command<T> command, Class<T> type)
{

     return type.cast( new LoginResult() );
}

Вы бы назвали это следующим образом:

Command<LoginResult> login = new Command<>();
LoginResult result = execute( login, LoginResult.class );

Обратите внимание на избыточную информацию здесь, но это необходимо, поскольку в компиляции теряются дженерики.