简体   繁体   English

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

[英]Vavr with generics gives incompatible types

Could anyone please explain why this code: 任何人都可以解释为什么这个代码:

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

fails to compile if I uncomment onFailure statement? 如果我取消注释onFailure语句,则无法编译? No idea what happens here. 不知道这里发生了什么。 How to improve it? 怎么改进呢?

You can call Try.of() with explicit generic type returned to satisfy compiler checks. 您可以调用Try.of()并返回显式泛型类型以满足编译器检查。 Something like: 就像是:

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

Try.of() returns type Try<T> where T is the type returned by the supplier. Try.of()返回类型Try<T> ,其中T是供应商返回的类型。 And because List.of(T t...) returns List<T> , then the final type seen by the compiler is Try<List<Integer> , which is not what the method returned type defined. 并且因为List.of(T t...)返回List<T> ,所以编译器看到的最终类型是Try<List<Integer> ,这不是返回类型定义的方法。 Java generics with specific type are invariant and they don't support covariant or contravariant substitutions, so List<Integer> != List<Number> . 具有特定类型的Java泛型是不变的,它们不支持协变或逆变换,因此List<Integer> != List<Number>

Working example: 工作范例:

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

Output: 输出:

Success(List(1, 2, 3))

Generic example type inference problem 通用示例类型推断问题

Further investigation shown that this is most probably a generic compiler problem. 进一步的调查表明,这很可能是一个通用的编译器问题。 Take a look at following plain Java example: 看看下面的普通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"));
    }
}

This code compiles without any issue and compiler infers type returned by Arrays.asList() from the expected type on the left side: 此代码编译没有任何问题,编译器推断Arrays.asList()从左侧的预期类型返回的类型:

在此输入图像描述

Now, if I call this Some<T>.shout() method, which does nothing and returns Some<T> , compiler infers the type not from the expected variable type, but from the last returned type: 现在,如果我调用Some<T>.shout()方法,它什么都不做并返回Some<T> ,编译器推断的类型不是来自预期的变量类型,而是来自最后返回的类型:

在此输入图像描述

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

在此输入图像描述

Specifying explicit type of Some<T>.of() solves the problem as in the Try.of() example: 指定Some<T>.of() Try.of()显式类型可以解决问题,如Try.of()示例中所示:

在此输入图像描述

I was searching Oracle documentation on type inference and there is this explanation: 我正在搜索关于类型推断的Oracle文档,并且有这样的解释:

The Java compiler takes advantage of target typing to infer the type parameters of a generic method invocation. Java编译器利用目标类型来推断泛型方法调用的类型参数。 The target type of an expression is the data type that the Java compiler expects depending on where the expression appears. 表达式的目标类型是Java编译器所期望的数据类型,具体取决于表达式的显示位置。

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

It looks like this "depending on where the expression appears" in this case means inferred type from the previously returned exact type. 看起来这“取决于表达式出现的位置”在这种情况下意味着来自先前返回的确切类型的推断类型。 It would explain why skipping shout() method makes compiler aware, that we expect Some<List<CharSequence>> and when we add shout() method it starts returning Some<List<String>> , because this is what shout() method sees from the returned type of Some.of() method. 这将解释为什么跳过shout()方法使编译器意识到,我们期望Some<List<CharSequence>>并且当我们添加shout()方法时它开始返回Some<List<String>> ,因为这就是shout()方法从返回的Some.of()方法类型中看到。 Hope it helps. 希望能帮助到你。

TL;DR TL; DR

The answer to your question is related to Java's type inference in conjunction with type variance (covariance in our case). 您的问题的答案与Java的类型推断以及类型方差(在我们的案例中为协方差)有关。 It has nothing to do with Vavr in particular. 它与Vavr无关。

  1. Try<List<Integer>> is a subtype of Try<? extends Seq<? extends Number>> Try<List<Integer>>Try<? extends Seq<? extends Number>>的子类型 Try<? extends Seq<? extends Number>> Try<? extends Seq<? extends Number>> . Try<? extends Seq<? extends Number>>
  2. But Try<List<Integer>> is not a subtype of Try<Seq<? extends Number>> 但是Try<List<Integer>>不是Try<Seq<? extends Number>>的子类型Try<Seq<? extends Number>> Try<Seq<? extends Number>> . Try<Seq<? extends Number>>

Change the return type of the lol() method(s) to the Try<? extends Seq<? extends Number>> lol()方法的返回类型更改为Try<? extends Seq<? extends Number>> Try<? extends Seq<? extends Number>> Try<? extends Seq<? extends Number>> and all will compile fine. Try<? extends Seq<? extends Number>> ,所有将编译正常。


Let us take a detailed look. 让我们详细看看。

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

The lol() method does return a value of type Try<Seq<? extends Number>> lol()方法确实返回Try<Seq<? extends Number>>类型的值Try<Seq<? extends Number>> Try<Seq<? extends Number>> (see line 1). Try<Seq<? extends Number>> (见第1行)。

The return statement in line 2 returns an instance of Try that is constructed using the factory method Try.of(...) . 第2行中的return语句返回使用工厂方法Try.of(...)构造的Try实例。 In Vavr 0.9.x, it is defined the following way: 在Vavr 0.9.x中,它定义如下:

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

The compiler infers: 编译器推断:

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

because it needs to match both, the return type of the method lol() and the CheckedFunction0 signature of the factory method Try.of . 因为它需要匹配两者,方法lol()的返回类型和工厂方法Try.ofCheckedFunction0签名。

This compiles fine, because the supplier function returns a value of type ? extends T 编译很好,因为supplier函数返回的类型值? extends T ? extends T , which is ? extends Seq<? extends Number> ? extends T ,这是? extends Seq<? extends Number> ? extends Seq<? extends Number> ? extends Seq<? extends Number> , which is compatible with the actual return type List<Integer> (see TL;DR section above). ? extends Seq<? extends Number> ,它与实际返回类型List<Integer>兼容(参见上面的TL; DR部分)。

If we now uncomment the .onFailure part (line 3), then the generic type argument T of the factory method Try.of does not have the scope of the return type of lol() anymore. 如果我们现在取消注释.onFailure部分(第3行),那么工厂方法Try.of的泛型类型参数T不再具有返回类型lol()的范围。 The compiler infers T to be List<Integer> because it always tries to find the most specific type that is applicable. 编译器推断TList<Integer>因为它总是试图找到适用的最具体的类型。

.onFailure returns a value of the type List<Integer> because it returns exactly the same type if its instance. .onFailure返回List<Integer>类型的值,因为如果它的实例返回完全相同的类型。 But Try<List<Integer>> is not a subtype of Try<Seq<? extends Number>> 但是Try<List<Integer>>不是Try<Seq<? extends Number>>的子类型Try<Seq<? extends Number>> Try<Seq<? extends Number>> (see TL;DR section above), so the code does not compile anymore. Try<Seq<? extends Number>> (参见上面的TL; DR部分),因此代码不再编译。

Making the lol() method covariant in its return type will satisfy the compiler: 使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
}

Btw, to define the correct generic variance throughout the type hierarchy of Vavr, especially for the collections, was one of the hard parts when creating Vavr. 顺便说一下,在整个Vavr的类型层次结构中定义正确的通用方差,特别是对于集合,是创建Vavr时的难点之一。 Java's type system is not perfect, there are still several things we can't express with Java's generics. Java的类型系统并不完美,还有一些我们用Java的泛型无法表达的东西。 See also my blog post "Declaration-Site Variance in a Future Java" 另请参阅我的博客文章“声明 - 未来Java中的站点差异”

Disclaimer: I'm the creator of Vavr (formerly Javaslang) 免责声明:我是Vavr(前身为Javaslang)的创始人

Seems like a Java compiler can not infer a correct type for you, in this case you need to provide the additional type information needed to proceed, for example: 看起来像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);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM