具有泛型的Vavr提供了不兼容的类型

时间:2018-10-26 12:18:28

标签: java generics vavr

任何人都可以解释为什么这样的代码:

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 个答案:

答案 0 :(得分:5)

您可以使用返回的显式泛型调用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()返回的类型:

enter image description here

现在,如果我调用此Some<T>.shout()方法,该方法不执行任何操作并返回Some<T>,则编译器不是根据预期的变量类型而是根据最后返回的类型来推断类型:

enter image description here

当然,Arrays.asList("a","b","c")返回List<String>,而this is the type shout()`方法推断并返回:

enter image description here

指定Some<T>.of()的显式类型可以解决Try.of()示例中的问题:

enter image description here

我正在搜索有关类型推断的Oracle文档,并且有以下解释:

  

Java编译器利用目标类型来推断通用方法调用的类型参数。表达式的目标类型是Java编译器期望的数据类型,具体取决于表达式出现的位置。

     

来源:https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html#target_types

看起来像这样的“取决于表达式出现的位置” 在这种情况下是指从先前返回的精确类型中推断出的类型。它可以解释为什么跳过shout()方法会使编译器知道我们期望Some<List<CharSequence>>的原因,而当我们添加shout()方法时它会开始返回Some<List<String>>,因为这就是{{1} }方法从返回的shout()方法的类型中查看。希望对您有所帮助。

答案 1 :(得分:4)

TL; DR

您的问题的答案与Java的类型推断以及类型差异(本例中为协方差)有关。与Vavr无关。

  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行)。

第2行中的return语句返回使用工厂方法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的{​​{1}}签名。

这可以很好地编译,因为Try.of函数返回类型为supplier的值? extends T,它与实际的返回类型? extends Seq<? extends Number>兼容(请参阅TL ; DR上面的部分)。

如果现在取消注释List<Integer>部分(第3行),则工厂方法.onFailure的泛型类型参数T的返回类型范围不为{{ 1}}。编译器推断Try.oflol(),因为它总是尝试查找最合适的类型。

T返回类型为List<Integer>的值,因为它的实例返回完全相同的类型。但是.onFailure不是List<Integer>的子类型(请参见上面的TL; DR部分),因此代码不再编译。

在返回类型中使用Try<List<Integer>>方法 covariant 将使编译器满意:

Try<Seq<? extends Number>>

顺便说一句,要在整个Vavr的类型层次结构中定义正确的通用方差,尤其是对于集合,是创建Vavr的难点之一。 Java的类型系统并不完美,Java的泛型还有很多我们无法表达的东西。另请参阅我的博客文章"Declaration-Site Variance in a Future Java"

免责声明:我是Vavr(以前称为Javaslang)的创建者

答案 2 :(得分:2)

似乎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);
    }
}