Stream.findFirst отличается от Optional.of?
Допустим, у меня есть два класса и два метода:
class Scratch {
private class A{}
private class B extends A{}
public Optional<A> getItems(List<String> items){
return items.stream()
.map(s -> new B())
.findFirst();
}
public Optional<A> getItems2(List<String> items){
return Optional.of(
items.stream()
.map(s -> new B())
.findFirst()
.get()
);
}
}
Почему getItems2
компилируется, а getItems
выдает ошибку компилятора
incompatible types: java.util.Optional<Scratch.B> cannot be converted to java.util.Optional<Scratch.A>
Поэтому, когда я get
значение Optional
возвращаемое findFirst
и снова обертываю его с помощью Optional.of
компилятор распознает наследование, но не, если я использую непосредственно результат findFirst
.
Ответы
Ответ 1
Optional<B>
не является подтипом Optional<A>
. В отличие от других языков программирования, система универсальных типов Javas не знает "типы только для чтения" или "параметры типа вывода", поэтому не понимает, что Optional<B>
предоставляет только экземпляр B
и может работать в местах, где Optional<A>
необходимо.
Когда мы пишем выражение, как
Optional<A> o = Optional.of(new B());
Вывод типа Javas использует целевой тип, чтобы определить, что мы хотим
Optional<A> o = Optional.<A>of(new B());
который действителен, так как new B()
может использоваться там, где требуется экземпляр A
То же самое относится и к
return Optional.of(
items.stream()
.map(s -> new B())
.findFirst()
.get()
);
где методы, объявленные как возвращаемый тип, используются для вывода аргументов типа для вызова Optional.of
и передачи результата get()
, допустим экземпляр B
, где требуется A
К сожалению, этот вывод целевого типа не работает через цепочечные вызовы, поэтому для
return items.stream()
.map(s -> new B())
.findFirst();
он не используется для вызова map
. Таким образом, для вызова map
вывод типа использует тип new B()
и его тип результата будет Stream<B>
. Вторая проблема состоит в том, что findFirst()
не является универсальным, вызов его в Stream<T>
неизменно создает Optional<T>
(а универсальные Javas не позволяют объявлять переменную типа, такую как <R super T>
, поэтому это не здесь даже можно произвести Optional<R>
с желаемым типом).
→ Решение состоит в том, чтобы предоставить явный тип для вызова map
:
public Optional<A> getItems(List<String> items){
return items.stream()
.<A>map(s -> new B())
.findFirst();
}
Просто для полноты, как сказано, findFirst()
не является универсальным и, следовательно, не может использовать целевой тип. Цепочка универсального метода, допускающего изменение типа, также решит проблему:
public Optional<A> getItems(List<String> items){
return items.stream()
.map(s -> new B())
.findFirst()
.map(Function.identity());
}
Но я рекомендую использовать решение для предоставления явного типа для вызова map
.
Ответ 2
У вас проблема с наследованием для дженериков. Необязательный <B> не расширяет Необязательный <A>, поэтому он не может быть возвращен как таковой.
Я думаю, что-то вроде этого:
public Optional<? extends A> getItems( List<String> items){
return items.stream()
.map(s -> new B())
.findFirst();
}
Или же:
public Optional<?> getItems( List<String> items){
return items.stream()
.map(s -> new B())
.findFirst();
}
Будет работать нормально, в зависимости от ваших потребностей.
Редактировать: экранирование некоторых персонажей
Ответ 3
Optional<B>
не является подклассом Optional<A>
.
В первом случае у вас есть Stream<B>
, поэтому findFirst
возвращает Optional<B>
, который не может быть преобразован в Optional<A>
.
Во втором случае у вас есть потоковый конвейер, который возвращает экземпляр B
Когда вы передаете этот экземпляр в Optional.of()
, компилятор видит, что тип возвращаемого значения метода - Optional<A>
, поэтому Optional.of()
возвращает Optional<A>
(поскольку Optional<A>
может содержать экземпляр B
качестве значения (поскольку B
расширяет A
)).
Ответ 4
Посмотрите на этот похожий пример:
Optional<A> optA = Optional.of(new B()); //OK
Optional<B> optB = Optional.of(new B()); //OK
Optional<A> optA2 = optB; //doesn't compile
Вы можете сделать второй метод неудачным, переписав его так:
public Optional<A> getItems2(List<String> items) {
return Optional.<B>of(items.stream().map(s -> new B()).findFirst().get());
}
Это просто потому, что универсальные типы являются инвариантами.
Почему разница? Смотрите объявление Optional.of
:
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
Тип необязательного извлекается из цели назначения (или типа возврата в этом случае).
И Stream.findFirst()
:
//T comes from Stream<T>, it not a generic method parameter
Optional<T> findFirst();
В этом случае, однако, return items.stream().map(s → new B()).findFirst();
не .findFirst()
результат .findFirst()
на основе объявленного возвращаемого типа getItems
(T
строго основан на аргументе типа Stream<T>
)
Ответ 5
Если класс B наследует класс A, это не означает, что Optional наследует Optional. Необязательно другой класс.