Stream.findFirst отличается от Optional.of? - PullRequest
0 голосов
/ 21 февраля 2019

Допустим, у меня есть два класса и два метода:

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.

Ответы [ 5 ]

0 голосов
/ 21 февраля 2019

Optional<B> не является подтипом Optional<A>.В отличие от других языков программирования, система универсальных типов Java не знает «типы только для чтения» или «параметры типа вывода», поэтому она не понимает, что Optional<B> предоставляет только экземпляр B и может работать в местах, где *Требуется 1005 *.

Когда мы пишем оператор, такой как

Optional<A> o = Optional.of(new B());

Вывод типа Java использует целевой тип, чтобы определить, что мы хотим, чтобы

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> (а универсальные шаблоны Java не позволяют объявлять переменную типа, такую ​​как <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.

0 голосов
/ 21 февраля 2019

Посмотрите на этот похожий пример:

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's not a generic method parameter
Optional<T> findFirst(); 

В этом случае, однако, return items.stream().map(s -> new B()).findFirst(); не печатает результат .findFirst() на основе объявленного типа возвращаемого значения getItems (T строго основан на аргументе типаиз Stream<T>)

0 голосов
/ 21 февраля 2019

У вас проблема с наследованием для дженериков.Необязательный не расширяет Необязательный , поэтому он не может быть возвращен как таковой.

Я думаю, что-то вроде этого:

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();
}

Будет работать нормально, в зависимости от ваших потребностей.

Редактировать: экранирование некоторых символов

0 голосов
/ 21 февраля 2019

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)).

0 голосов
/ 21 февраля 2019

Если класс B наследует класс A, это не значит, что Optional наследует Optional.Необязательный другой класс.

...