Это приведение компилируется, потому что это особый случай сужающего преобразования. (Согласно & sect; 5.5 , сужающие преобразования являются одним из типов преобразований, разрешенных приведением, поэтому большая часть этого ответа будет сосредоточена на правилах сужения преобразований.)
Обратите внимание, что хотя UnaryOperator<T>
не является подтипом UnaryOperator<Object>
(поэтому приведение не является "понижением"), оно все же считается сужающим преобразованием. С & sect; 5.6.1 :
A сужающее ссылочное преобразование обрабатывает выражения ссылочного типа S
как выражения другого ссылочного типа T
, где S
не является подтипом T
. [...] В отличие от расширяющегося преобразования ссылок, типы не должны быть напрямую связаны. Однако существуют ограничения, которые запрещают преобразование между определенными парами типов, когда можно статически доказать, что никакое значение не может быть обоих типов.
Некоторые из этих "боковых" приведений заканчиваются неудачей из-за специальных правил, например, следующие будут неудачными:
List<String> a = ...;
List<Double> b = (List<String>) a;
В частности, это задается правилом в & sect; 5.1.6.1 , которое гласит:
Если существует параметризованный тип X
, который является супертипом T
, и параметризованный тип Y
, который является супертипом S
, так что стирания X
и Y
одинаковы, тогда X
и Y
не являются доказуемо различимыми ( §4.5 ).
При использовании типов из пакета java.util
в качестве примера не существует сужающего преобразования ссылок из ArrayList<String>
в ArrayList<Object>
или наоборот, поскольку аргументы типов String
и Object
доказуемо различимы , По той же причине не существует сужающего эталонного преобразования из ArrayList<String>
в List<Object>
или наоборот. Отказ от доказуемо различных типов - это простой статический элемент, предотвращающий «глупые» сужающие эталонные преобразования.
Другими словами, если a
и b
имеют общий супертип с тем же стиранием (в данном случае, например, List
), то они должны быть тем, что JLS называет «доказуемо отличным», дано & sect; 4,5 :
Два параметризованных типа достоверно различаются, если выполняется одно из следующих условий:
И & 4.5; :
Два типа аргументов доказуемо различаются , если выполняется одно из следующих условий:
Ни один из аргументов не является переменной типа или подстановочным знаком, а два аргумента не одного типа.
Один аргумент типа - это переменная типа или подстановочный знак с верхней границей (от преобразования захвата, если необходимо), равной S
; а другой аргумент типа T
не является переменной типа или подстановочным знаком; и ни |S| <: |T|
, ни |T| <: |S|
.
Каждый аргумент типа является переменной типа или подстановочным знаком с верхними границами (от преобразования захвата, если необходимо), равными S
и T
; и ни |S| <: |T|
, ни |T| <: |S|
.
Таким образом, с учетом вышеуказанных правил List<String>
и List<Double>
доказуемо различимы (через 1-е правило из 4.5.1), потому что String
и Double
являются аргументами разных типов.
Однако UnaryOperator<T>
и UnaryOperator<Object>
не доказуемо различимы (через 2-е правило из 4.5.1), потому что:
Один аргумент типа является переменной типа (T
, с верхней границей Object
.)
Граница этой переменной типа совпадает с аргументом типа для другого типа (Object
).
Так как UnaryOperator<T>
и UnaryOperator<Object>
не являются доказуемо различимыми, допускается сужающее преобразование, следовательно, отливки компилируются.
Один из способов понять, почему компилятор допускает некоторые из этих приведений, но не другие: в случае переменной типа он не может доказать, что T
определенно не Object
, Например, у нас может быть такая ситуация:
UnaryOperator<String> aStringThing = Somewhere::doStringThing;
UnaryOperator<Double> aDoubleThing = Somewhere::doDoubleThing;
<T> UnaryOperator<T> getThing(Class<T> t) {
if (t == String.class)
return (UnaryOperator<T>) aStringThing;
if (t == Double.class)
return (UnaryOperator<T>) aDoubleThing;
return null;
}
В этих случаях мы на самом деле знаем, что приведение правильное, если никто не делает что-то смешное (например, непроверенное приведение аргумента Class<T>
).
Так что в общем случае приведения к UnaryOperator<T>
мы могли бы на самом деле делать что-то законное. По сравнению со случаем приведения List<String>
к List<Double>
мы можем довольно авторитетно сказать, что это всегда неправильно.