Вавр с дженериками выдает несовместимые типы

Может ли кто-нибудь объяснить, почему этот код:

interface Lol {
  default Try<Seq<? extends Number>> lol() {
    return Try.of(List::empty);
  }
}

class LolImpl implements Lol {
  @Override
  public Try<Seq<? extends Number>> lol() {
    return Try
      .of(() -> List.of(1, 2, 3))
      //.onFailure(Object::hashCode)
      ;
  }
}

не компилируется, если я раскомментирую onFailure заявление? Понятия не имею, что здесь происходит. Как это улучшить?

3 ответа

Решение

Ты можешь позвонить Try.of() с явным универсальным типом, возвращаемым для удовлетворения проверок компилятора. Что-то вроде:

Try.<Seq<? extends Number>of(() -> List.of(1,2,3))

Try.of() возвращает тип Try<T> где T тип, возвращаемый поставщиком. И потому что List.of(T t...) возвращается List<T>последний тип, видимый компилятором Try<List<Integer>, который не является тем, что метод возвратил определенный тип. Обобщения Java с определенным типом являются инвариантными и не поддерживают ковариантные или контравариантные замены, поэтому List<Integer> != List<Number>,

Рабочий пример:

import io.vavr.collection.List;
import io.vavr.collection.Seq;
import io.vavr.control.Try;

interface Lol {
    default Try<Seq<? extends Number>> lol() {
        return Try.of(List::empty);
    }
}

class LolImpl implements Lol {
    @Override
    public Try<Seq<? extends Number>> lol() {
        return Try
                .<Seq<? extends Number>>of(() -> List.of(1, 2, 3))
                .onFailure(t -> System.out.println(t.getMessage()));

    }

    public static void main(String[] args) {
        System.out.println(new LolImpl().lol());
    }
}

Выход:

Success(List(1, 2, 3))

Типовой пример вывода типа проблемы

Дальнейшие исследования показали, что это, скорее всего, общая проблема компилятора. Взгляните на следующий простой пример Java:

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;

interface Some<T> {
    static <T> Some<T> of(Supplier<T> supplier) {
        return new SomeImpl<>(supplier.get());
    }

    default Some<T> shout() {
        System.out.println(this);
        return this;
    }

    class SomeImpl<T> implements Some<T> {
        private final T value;

        public SomeImpl(T value) {
            this.value = value;
        }
    }

    static void main(String[] args) {
        final Some<List<CharSequence>> strings = Some.of(() -> Arrays.asList("a", "b", "c"));
    }
}

Этот код компилируется без проблем, и компилятор выводит тип, возвращаемый Arrays.asList() из ожидаемого типа на левой стороне:

Теперь, если я назову это Some<T>.shout() метод, который ничего не делает и возвращает Some<T>, компилятор выводит тип не из ожидаемого типа переменной, а из последнего возвращенного типа:

Конечно Arrays.asList("a","b","c") возвращается List<String> а также this is the typeМетод shout()`выводит и возвращает:

Указание явного типа Some<T>.of() решает проблему как в Try.of() пример:

Я искал документацию Oracle по выводу типа, и вот это объяснение:

Компилятор Java использует преимущества целевой типизации, чтобы вывести параметры типа общего вызова метода. Целевой тип выражения - это тип данных, который ожидает компилятор Java в зависимости от того, где находится выражение.

Источник: https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html

Похоже, это "в зависимости от того, где появляется выражение", в данном случае означает предполагаемый тип из ранее возвращенного точного типа. Это объяснило бы, почему пропуск shout() метод дает понять компилятору, что мы ожидаем Some<List<CharSequence>> и когда мы добавим shout() метод начинает возвращаться Some<List<String>>потому что это то, что shout() Метод видит из возвращенного типа Some.of() метод. Надеюсь, поможет.

TL;DR

Ответ на ваш вопрос связан с выводом типа Java в сочетании с дисперсией типа (в нашем случае ковариация). Это не имеет никакого отношения к Вавру в частности.

  1. Try<List<Integer>> это подтип Try<? extends Seq<? extends Number>>,
  2. Но Try<List<Integer>> не является подтипом Try<Seq<? extends Number>>,

Изменить тип возвращаемого значения lol() метод (ы) к Try<? extends Seq<? extends Number>> и все скомпилируется нормально.


Давайте подробно рассмотрим.

public Try<Seq<? extends Number>> lol() {  // line 1
    return Try.of(() -> List.of(1, 2, 3))  // line 2
        //.onFailure(Object::hashCode)     // line 3
    ;
}

lol() метод возвращает значение типа Try<Seq<? extends Number>> (см. строку 1).

Оператор return в строке 2 возвращает экземпляр Try который построен с использованием фабричного метода Try.of(...), В Vavr 0.9.x это определяется следующим образом:

static <T> Try<T> of(CheckedFunction0<? extends T> supplier) {
    // implementation omitted
}

Компилятор выводит:

// type T = Seq<? extends Number>
Try.of(() -> List.of(1, 2, 3))

потому что он должен соответствовать обоим, тип возвращаемого значения метода lol() и CheckedFunction0 подпись фабричного метода Try.of,

Это хорошо компилируется, потому что supplier функция возвращает значение типа ? extends T, который ? extends Seq<? extends Number>, который совместим с фактическим типом возврата List<Integer> (см. раздел TL;DR выше).

Если мы сейчас раскомментируем .onFailure часть (строка 3), затем аргумент универсального типа T заводского метода Try.of не имеет объема возвращаемого типа lol() больше. Компилятор выводит T быть List<Integer> потому что он всегда пытается найти наиболее конкретный тип, который применим.

.onFailure возвращает значение типа List<Integer> потому что он возвращает точно такой же тип, если его экземпляр. Но Try<List<Integer>> не является подтипом Try<Seq<? extends Number>> (см. раздел TL;DR выше), поэтому код больше не компилируется.

Делая lol() Метод Ковариант в своем типе возврата будет удовлетворять компилятор:

// before: Try<Seq<? extends Number>>
Try<? extends Seq<? extends Number>> lol() { // line 1
    return Try.of(() -> List.of(1, 2, 3))    // line 2
        .onFailure(Object::hashCode);        // line 3
}

Между прочим, определение правильной общей дисперсии во всей иерархии типов Vavr, особенно для коллекций, было одной из сложных частей при создании Vavr. Система типов Java не идеальна, есть еще несколько вещей, которые мы не можем выразить с помощью обобщений Java. Смотрите также мой пост в блоге "Декларация сайта Разница в будущей Java"

Отказ от ответственности: я создатель Vavr (ранее Javaslang)

Похоже, компилятор Java не может определить правильный тип для вас, в этом случае вам нужно предоставить дополнительную информацию о типе, необходимую для продолжения, например:

class LolImpl implements Lol {

    @Override
    public Try<Seq<? extends Number>> lol() {
        Try<Seq<? extends Number>> res = Try.of(() -> List.of(1, 2, 3));
        return res.onFailure(Object::hashCode);
    }
}
Другие вопросы по тегам