簡體   English   中英

帶有泛型的Vavr提供了不兼容的類型

[英]Vavr with generics gives incompatible types

任何人都可以解釋為什么這個代碼:

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語句,則無法編譯? 不知道這里發生了什么。 怎么改進呢?

您可以調用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()顯式類型可以解決問題,如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()方法類型中看到。 希望能幫助到你。

TL; DR

您的問題的答案與Java的類型推斷以及類型方差(在我們的案例中為協方差)有關。 它與Vavr無關。

  1. Try<List<Integer>>Try<? extends Seq<? extends Number>>的子類型 Try<? extends Seq<? extends Number>> Try<? extends Seq<? extends Number>>
  2. 但是Try<List<Integer>>不是Try<Seq<? extends Number>>的子類型Try<Seq<? extends Number>> Try<Seq<? extends Number>>

lol()方法的返回類型更改為Try<? extends Seq<? extends Number>> Try<? extends Seq<? extends Number>> 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>>類型的值Try<Seq<? extends Number>> Try<Seq<? extends Number>> (見第1行)。

第2行中的return語句返回使用工廠方法Try.of(...)構造的Try實例。 在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()的返回類型和工廠方法Try.ofCheckedFunction0簽名。

編譯很好,因為supplier函數返回的類型值? extends T ? extends T ,這是? extends Seq<? extends Number> ? extends Seq<? extends Number> ? extends Seq<? extends Number> ,它與實際返回類型List<Integer>兼容(參見上面的TL; DR部分)。

如果我們現在取消注釋.onFailure部分(第3行),那么工廠方法Try.of的泛型類型參數T不再具有返回類型lol()的范圍。 編譯器推斷TList<Integer>因為它總是試圖找到適用的最具體的類型。

.onFailure返回List<Integer>類型的值,因為如果它的實例返回完全相同的類型。 但是Try<List<Integer>>不是Try<Seq<? extends Number>>的子類型Try<Seq<? extends Number>> 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);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM