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. Необязательно другой класс.