簡體   English   中英

關於Java泛型和java.util.function.Function設計的問題

[英]Question about Java generics and design of java.util.function.Function

  1. 關於通配符的問題

示例: Student extends Person

    Person person = new Person();
    Student student = new Student();

    List<? super Student> list = new ArrayList<>();
    list.add(student); // success
    list.add(person); // compile error

    List<? extends Person> list2 = new ArrayList<>();
    list2.add(person); // compile error
    list2.add(student);// compile error

我已閱讀以下問題的答案“ capture#1-of ? extends Object is not 適用

您正在使用通用通配符。 您不能執行添加操作,因為類類型不確定。 您不能添加/放置任何內容(空值除外)-- Aniket Thakur

官方文檔:通配符永遠不會用作泛型方法調用、泛型類實例創建或超類型的類型參數

但是為什么list.add(student)編譯成功呢?

  1. java.util.function.Function設計
public interface Function<T, R>{

    //...

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
}

為什么before被設計為Function<? super V, ? extends T> Function<? super V, ? extends T> Function<? super V, ? extends T>而不是Function<V,T>當返回的類型是Function<V,R>和輸入的類型是V (它還可以通過編譯靈活使用)

要理解這些問題,您必須了解泛型如何與subtyping (在 Java 中使用extends關鍵字明確表示)一起工作。 Andreas 提到了PECS規則,這是它們在 Java 中的表示。

首先,我想指出,上面的代碼可以通過簡單的轉換來糾正

ArrayList<? super Student> list = new ArrayList<>();
list.add(new Student());
ArrayList<Person> a = (ArrayList<Person>) list; // a covariance
a.add(new Person());

並且編譯和運行良好(而不是引發任何異常)

原因很簡單,當我們有一個consumer (它接受一些對象並消費它們,例如add方法)時,我們期望它接受類型no more than (超類)我們指定的類型T ,因為過程消費可能需要它想要的類型的任何成員(變量、方法等),我們希望確保類型T滿足消費者需要的所有成員。

與此相反,一個producer ,這對於我們產生對象(如get方法),具有供應類型的對象no less than指定類型T使我們可以訪問任何構件T具有所產生的對象上。

這兩個與稱為covariancecontravariance子類型化形式密切相關

至於第二個問題,你也可以參考Consumer<T>的實現(稍微簡單一些):

default Consumer<T> andThen(Consumer<? super T> after) {
  Objects.requireNonNull(after);
  return (T t) -> { accept(t); after.accept(t); };
}

我們需要這個的原因是? super T ? super T是:當我們使用andThen方法組合兩個Consumer ,假設前一個Consumer接受一個類型為T的對象,我們期望后者接受一個類型no more than T的對象,因此它不會嘗試訪問T沒有的任何成員。

因此,而不是簡單地將Consumer<T> afterConsumer<T> afterConsumer<? super T> after Consumer<? super T> after ,我們允許前一個消費者(類型T )與一個消費者結合,該消費者接受的對象不完全是T類型,但可能比T小,這是為了方便covariance 這使得以下代碼聽起來:

Consumer<Student> stu = (student) -> {};
Consumer<Person> per = (person) -> {};
stu.andThen(per);

compose同樣的考慮, Function類型的compose方法也適用。

IMO 這可能是 vanilla Java 中最復雜的概念。 所以讓我們把它分解一下。 我將從你的第二個問題開始。

Function<T, R>接受類型為T的實例t並返回類型為R的實例r 有了繼承這意味着,你可以提供一個實例foo類型的Foo ,如果Foo extends T ,同樣返回bar類型的Bar ,如果Bar extends R

作為一個想要編寫靈活的泛型方法的庫維護者,很難而且實際上不可能提前知道可能與此方法一起使用的所有類,這些類擴展了TR 那么我們將如何編寫一個處理它們的方法呢? 此外,這些實例具有擴展基類的類型這一事實與我們無關。

這就是通配符的用武之地。在方法調用期間,我們說您可以使用滿足所需類的信封的任何類。 對於所討論的方法,我們有兩個不同的通配符使用上限和下限泛型類型參數:

public interface Function<T, R>{
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before)

現在讓我們說我們想利用這個方法......對於這個例子,讓我們定義一些基本類:

class Animal{} 
class Dog extends Animal{} 
class Fruit{} 
class Apple extends Fruit{} 
class Fish{} 
class Tuna extends Fish{} 

想象一下我們的函數和轉換定義如下:

Function<Animal, Apple> base = ...;
Function<Fish, Animal> transformation = ...;

我們可以使用compose組合這些函數來創建一個新函數:

Function<Fish, Apple> composed = base.compose(transformation);

這一切都很好,但現在想象一下,在所需的輸出函數中,我們實際上只想使用Tuna作為輸入。 如果我們不使用下界? super V ? super V作為我們傳遞給composeFunction的輸入類型參數,那么我們會得到一個編譯器錯誤:

default <V> Function<V, R> compose(Function<V, ? extends T> before)
...
Function<Tuna, Apple> composed = base.compose(transformation);
> Incompatible types: 
> Found: Function<Fish, Apple>, required: Function<Tuna, Apple>

發生這種情況是因為compose調用的返回類型將V指定為Tuna而另一方面, transformation將其“ V ”指定為Fish 所以現在當我們嘗試通過transformationcompose ,編譯器需要transformation來接受一個Tuna作為它的V ,當然Tuna並不完全匹配Fish

另一方面,代碼的原始版本( ? super V )允許我們將V視為下限(即它允許V “逆變”與“不變”)。 編譯器能夠成功應用下限檢查,而不是遇到TunaFish之間的不匹配? super V 計算為Fish super Tuna ? super V ,這是真的,因為Tuna extends Fish

對於另一種情況,假設我們的調用定義為:

Function<Animal, Apple> base = ...;
Function<Fish, Dog> transformation = ...;
Function<Fish, Apple> composed = base.compose(transformation);

如果我們沒有通配符? extends T ? extends T那么我們會得到另一個錯誤:

default <V> Function<V, R> compose(Function<? super V, T> before)
Function<Fish, Apple> composed = base.compose(transformation);
// error converting transformation from
//    Function<Fish, Dog> to Function<Fish, Animal>

通配符? extends T ? extends T允許這樣做,因為T解析為Animal並且通配符解析為Dog ,這可以滿足約束Dog extends Animal

對於你的第一個問題; 這些邊界實際上只在方法調用的上下文中起作用。 在該方法的過程中,通配符將被解析為實際類型,就像? super V ? super V被解析到Fish? extends T ? extends T被解析為Dog 如果沒有來自泛型簽名的信息,我們將無法讓編譯器知道可以在類型的方法上使用什么類,因此不允許使用任何類。

暫無
暫無

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

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