新JEP將簡化Java類型變異

jmcj9416 7年前發布 | 10K 次閱讀 Java Java開發

新的JEP Candidate 旨在簡化處理Java中復雜的類型變異的概念。這個新的JEP Candidate可能會在Java 10中推出,提供了在定義的泛型類型中指定目標對象默認變異的方法,而不是在泛型類型實例化時通過通配符指定。這個新方案并不會代替通配符,而是減少對通配符的需求。

類型變異這個概念對于很多開發人員來說仍然比較模糊,在Java中通過不太普及的通配符來解決這個問題并沒有很大幫助。因此,為了幫助我們的讀者能夠理解這款JEP的潛在影響力,在本文中我們將首先解釋什么是類型變異,目前Java中是怎么解決它的,之后將介紹這個新方案能實現什么。

變異、協變和逆變

以下的代碼屬于傳統的在線購物應用程序:

public class Product {
/* ... */
}

public class FrozenProduct extends Product {
    /* ... */
}

如果有個方法scan(Product product),我們調用它傳遞FrozenProduct對象,調用工作沒有問題,這是眾所周知的多態性的一般規則。但是當參數中包含泛型時,就不能使用相同的邏輯,以下的代碼將無法編譯:

private void addAllProducts(List<Product> products) {
/* Check product stock */
shoppingCart.addAll(products);
}

private void freezerSection() {
    List<FrozenProduct> frozenProducts = /* select frozen products */;
    addAllProducts(frozenProducts); // ERROR: List<Product> expected
                                    //        List<FrozenProduct> found
}

當在Java中使用泛型時,并沒有關于目標類型和其子類型或超類型之間兼容性的假設。換句話說,當使用泛型時,我們默認目標類型是不變的,它只接受明確的類型。

然而,在上面的例子中,我們可以看到addAllProducts方法可以處理List of Product或是其子類型。當泛型參數可以接受其目標類型或是它的任何子類型,我們就說這個類型是協變的,在Java中可以用extends表示:

private void addAllProducts(List<? extends Product> products) {
/* Check product stock */
shoppingCart.addAll(products);
}

private void freezerSection() {
    List<FrozenProduct> frozenProducts =  /* select frozen products */;
    addAllProducts(frozenProducts); // works with no problem
}

在這些例子中,接受的目標類型的變異是子類型。在一些其他例子中,目標類型的變異不是子類型,而是超類型。考慮以下的情況:

private boolean askQuestion(Predicate<String> p) {
return p.test("hello");
}

private void applyPredicate() {
    Predicate<Object> evenLength = o -> o.toString().length() % 2 == 0;
    askQuestion(evenLength); // ERROR: Predicate<String> expected
                             //        Predicate<Object> found
}

在這種情況下,我們可以看到使用string “hello”到lambda o -> o.toString().length() % 2 == 0中不會發生問題,然而,編譯器不允許我們這么做。askQuestion可以處理Predicate of String或其任意超類型:我們就說這種情況下的目標類型是逆變的,在Java中可以用super表示:

private boolean askQuestion(Predicate<? super String> p) {
return p.test("hello");
}

private void applyPredicate() {
    Predicate<Object> evenLength = o -> o.toString().length() % 2 == 0;
    askQuestion(evenLength); // works with no problem
}

通配符是創建類型變異的一種非常靈活的方法,因為它允許你在不同地方對同種類型定義不同的變異。比如說,在上面的例子里我們定義addAllProducts是協變的參數,但在其他地方根據我們的需求,可以定義它為逆變或是不變的。然而缺點是必須在每個地方根據需要明確指定變異,這樣會造成很多的冗余和混亂。所以新的方案應運而生。

在聲明時指定默認變異

通配符的最主要的問題是它們比開發人員通常需要的還要靈活。在 Predicate<String> 的例子中,我們理論上可以創建一個方法 Predicate<? extends String> ,然而,可以用到的用例有限(可能根本沒有)。在大量情況下,只有一個類型變異有意義,為了反映出這一點, JEP 300 提供了在聲明泛型類型時指定默認變異的方法,而不是在實例化時指定默認變異。比如說,用了這種方案,可以使用逆變的關鍵字 Predicate<contravariant T> 來重寫接口 Predicate<T> ,這就代表著任何時候開發人員寫 Predicate<String> 都會被隱含地理解為 Predicate<? super String> 。

這個新功能的語法尚未決定,但是已經有了一些備選項:使用新的顯式關鍵字,如 Function<contravariant T, covariant R> ,或遵循其他語言的先例,如Scala中的符號 (Function<-T, +R>) ,或是C#中的較短關鍵字 (Function<in T, out R>) 。在解決語法問題之前,還需要解決一些重要的技術問題,即默認變異和通配符之間的交互,默認變異對現有代碼產生的影響,以及變異類型兼容性檢查的實際機制。

最后值得提出的一點是,JEP 300僅會處理新的默認變異,但不會修改Java庫中可用的任何類和接口。如果之后JEP 300再發展可能會考慮處理這種情況,但也只是在其他版本的JEP中執行。

 

 

來自:http://www.infoq.com/cn/news/2017/02/java-default-type-variance

 

 本文由用戶 jmcj9416 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!