6

谁能解释一下为什么这段代码:

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语句,编译失败?不知道这里会发生什么。如何改进它?

4

3 回答 3

5

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

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

Try.of()返回类型Try<T>其中T是供应商返回的类型。并且因为List.of(T t...)return 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")returnsList<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()。希望能帮助到你。

于 2018-10-26T12:42:27.650 回答
5

TL;博士

您的问题的答案与 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签名Try.of

这编译得很好,因为supplier函数返回一个 type 的值? extends T,即? extends Seq<? extends Number>,它与实际的返回类型兼容List<Integer>(参见上面的 TL;DR 部分)。

如果我们现在取消注释该部分(第 3 行),则工厂方法.onFailure的泛型类型参数不再具有返回类型的范围。编译器推断是因为它总是试图找到最具体的适用类型。TTry.oflol()TList<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)的创建者

于 2018-11-07T01:32:05.107 回答
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);
    }
}
于 2018-10-26T12:43:53.460 回答