歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

新JEP將簡化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中執行。

查看英文原文:New JEP Would Simplify Java Type Variance

Copyright © Linux教程網 All Rights Reserved