Безопасно литьедо <Response> - PullRequest
1 голос
/ 14 мая 2019

У меня есть класс исполнителя HTTP:

Future<? extends Response> future = service.apply(request).toJavaFuture();

Теперь я хочу удалить часть ? extends, потому что я не хочу делать это настолько общим для вызывающей стороны. В основном, я хочу вернуть Future<Response>.

С моей точки зрения:

x extends y

будет означать x это y, а y это не x.

Это означает, что x можно привести к y.

На мой взгляд, это можно сделать безопасно, потому что x всегда расширяется y.

Почему следующее небезопасно?


    Future<? extends Response> future = service.apply(request).toJavaFuture();

    Future<Response> futureResponse = (Future<Response>) future;

Ответы [ 2 ]

2 голосов
/ 14 мая 2019

Я бы сказал, что это ограничение компилятора в отношении параметров универсального типа. Компилятор знает, что вы параметризовали класс, но у него нет контекста того, как он используется.

Ваш Future является держателем данных. Вы не можете ничего в него вкладывать, вы можете только получать запросы от него. В этом случае ваше ожидание того, что Future<? extends Response> и Future<Response> будут вести себя одинаково и, таким образом, будет безопасно разыгрываться, вполне разумно. Обе эти вещи являются поставщиками запросов. Точные экземпляры могут быть подклассами, но все, что мы получим из нашего Future, определенно реализует методы Request, так что это приведение кажется безопасным.

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

interface Future<T>
{
    void setValue(T value);
    //...
}

И у нас есть Future<? extends Response>, который был создан как Future<ChildResponse>. Когда мы разыгрываем его Future<Response>, мы можем теперь вызвать setValue с Response, тогда как перед сотворением нам понадобится ChildResponse. Это причина, по которой актерский состав не считается безопасным.

Фактически, компилятор не достаточно умен, чтобы различать два случая, и поэтому лучшее решение - всегда объявлять приведение как небезопасное. Даже если компилятор может провести различие, проблема еще более усложняется тем фактом, что интерфейс может измениться - предположим, что Future были изменены, как описано выше, - так что приведение, которое ранее было безопасным, больше не будет.

В этом случае я бы лично чувствовал себя прекрасно, подавляя предупреждение. Кастинг - это создание утверждений компилятору о том, что у вас есть информация, которой у него нет, какова у вас ситуация.

1 голос
/ 14 мая 2019
Переменной

A Future<? extends Response> можно присвоить Future<Response> или Future<ResponseSubClass1> или Future<ResponseSubClass2>.

Экземпляр Future<ResponseSubClass1> нельзя безопасно назначить переменной Future<Response>, посколькупозволит назначить ResponseSubClass2 для Future<ResponseSubClass1>.

Следовательно, ваша попытка приведения является небезопасной.

...