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

Opa*_*pal 6 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)
      ;
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我取消注释onFailure语句,则无法编译?不知道这里发生了什么.怎么改进呢?

Szy*_*iak 5

您可以Try.of()使用返回的显式泛型类型调用以满足编译器检查.就像是:

Try.<Seq<? extends Number>of(() -> List.of(1,2,3))
Run Code Online (Sandbox Code Playgroud)

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

Success(List(1, 2, 3))
Run Code Online (Sandbox Code Playgroud)

通用示例类型推断问题

进一步的调查表明,这很可能是一个通用的编译器问题.看看下面的普通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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码编译时没有任何问题,编译器推断Arrays.asList()出左侧预期类型返回的类型:

在此输入图像描述

现在,如果我调用此Some<T>.shout()方法,它什么都不做并返回Some<T>,编译器推断出的类型不是来自预期的变量类型,而是来自最后返回的类型:

在此输入图像描述

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

在此输入图像描述

指定显式类型Some<T>.of()解决问题,如Try.of()示例中所示:

在此输入图像描述

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

Java编译器利用目标类型来推断泛型方法调用的类型参数.表达式的目标类型是Java编译器所期望的数据类型,具体取决于表达式的显示位置.

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

看起来这"取决于表达式出现的位置"在这种情况下意味着来自先前返回的确切类型的推断类型.它将解释为什么跳过shout()方法使编译器意识到,我们期望Some<List<CharSequence>>并且当我们添加shout()方法时它开始返回Some<List<String>>,因为这是shout()方法从返回的Some.of()方法类型中看到的.希望能帮助到你.


Dan*_*ich 5

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
    ;
}
Run Code Online (Sandbox Code Playgroud)

该lol()方法确实返回类型的值Try<Seq<? extends Number>>(请参见第1行)。

第2行中的return语句返回Try使用factory方法构造的实例Try.of(...)。在Vavr 0.9.x中,它的定义方式如下:

static <T> Try<T> of(CheckedFunction0<? extends T> supplier) {
    // implementation omitted
}
Run Code Online (Sandbox Code Playgroud)

编译器推断:

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

因为它需要同时匹配方法的返回类型lol()和CheckedFunction0工厂方法的签名Try.of。

这样编译就可以了,因为该supplier函数返回的type值? extends T是? extends Seq<? extends Number>,与实际的返回类型兼容List<Integer>(请参见上面的TL; DR部分)。

如果现在取消注释.onFailure零件(第3行),那么Tfactory方法的泛型类型参数将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
}
Run Code Online (Sandbox Code Playgroud)

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

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