Почему StreamEx заставляет меня добавлять "? Extends" к типу переменной при сборе в список?
Я заметил, что в случае ниже стандартного потока Java лучше "вычислить" тип переменной, чем StreamEx. Это странно, потому что люди любят StreamEx и используют его повсюду, но затем код загрязняется знаком "?". Я хочу использовать
List<Class<?>
но StreamEx заставляет меня использовать
List<? extends Class<?>>
. Может ли кто-нибудь объяснить, почему StreamEx работает таким образом? Могу ли я как-то использовать желаемый тип переменной с StreamEx?
private static List<? extends Class<?>>
fooList_StreamEx_Compiles(List<Integer> input) {
return StreamEx.of(input)
.map(x -> foo())
.toList();
}
private static List<Class<?>>
fooList_StreamEx_Error(List<Integer> input) {
return StreamEx.of(input)
.map(x -> foo())
// Error: incompatible types: java.util.List<java.lang.Class<capture#1 of ?>>
// cannot be converted to java.util.List<java.lang.Class<?>>
.toList();
}
private static List<Class<?>> fooList(List<Integer> input) {
return input
.stream()
.map(x -> foo())
.collect(Collectors.toList());
}
private static Class<?> foo() {
return String.class;
}
Я использую StreamEx 0.7.0 и Java 11
1 ответ
Это не
StreamEx
вопрос. Когда вы используете
collect(Collectors.toList())
на
StreamEx
, он работает одинаково хорошо. Проблема связана с
toList()
метод удобства, который является стандартным
Stream
даже не предлагает. Это общая проблема, что
StreamEx
не виноват.
В
toList
метод на
StreamEx
имеет следующую подпись:
public List<T> toList()
В идеальном мире было бы законно создать список, параметризованный супертипом, т.е.
public <R super T> List<R> toList()
но этот синтаксис был опущен при создании универсальных шаблонов Java. В
toArray
методы обоих,
Collection
и
Stream
страдают аналогичным ограничением; они не могут объявить тип элемента результирующего массива супертипом типа элемента коллекции. Но поскольку проверяется фактическая операция сохранения массива, эти методы просто допускают любой тип элемента. Для
List
результат, при условии стирания типа это невозможно.
Когда используешь
collect(Collectors.toList())
с другой стороны, можно создать список с супертипом из-за
collect
подпись:
<R,A> R collect(Collector<? super T,A,R> collector)
что позволяет перейти в
Collector
параметризованный супертипом (? super T
), который в большинстве сценариев будет выводиться из целевого типа.
Это ограничение
toList
объявление взаимодействует с другим ограничением - необоснованной обработкой подстановочных знаков. Я не уверен, кроется ли причина этой проблемы в компиляторе или спецификации, но в
map(x -> foo())
step, тип подстановочного знака был захвачен, и такой захваченный тип будет считаться отличным от любого другого захваченного типа подстановочного знака, даже если он происходит из того же источника.
Когда я компилирую ваш код с
javac
, он говорит:
error: incompatible types: List<Class<CAP#1>> cannot be converted to List<Class<?>>
.toList();
^
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
В
CAP#1
это захваченный тип. Все захваченные типы нумеруются, чтобы различать их. Как уже было сказано, каждый из них считается отдельным типом, отличающимся от всех остальных.
Class<?>
это супертип
Class<CAP#1>
, поэтому он работает с
collect
который позволяет собирать список, параметризованный этим супертипом, как сказано выше, но не
toList
.
Вы можете исправить это, используя возвращаемый тип
List<? extends Class<?>>
, чтобы обозначить, что фактический тип элемента списка является подтипом
Class<?>
, но лучший вариант - заставить компилятор не использовать захваченный тип:
private static List<Class<?>> fooList_StreamEx_Solved(List<Integer> input) {
return StreamEx.of(input)
.<Class<?>>map(x -> foo())
.toList();
}
Не волнуйтесь, если вы не до конца понимали необходимость вставки здесь явного типа, я не утверждаю, что поведение компиляторов Java было вообще понятным, когда используются подстановочные типы...