2015-08-03 8 views
9

Come discusso in Bounding generics with 'super' keyword, il sistema di tipo Java è rotto/incompleto quando si tratta di limiti inferiori sui generici del metodo. Dato che Optional fa ora parte del JDK, sto iniziando a usarlo di più e i problemi che Guava ha riscontrato con i suoi Opzionali stanno iniziando a diventare un problema per me. Ho trovato un buon lavoro, ma non sono sicuro che sia sicuro. In primo luogo, mi impostazione lascia l'esempio:Soluzione sicura per i limiti controvarianti in Java?

public class A {} 
public class B extends A {} 

Vorrei essere in grado di dichiarare un metodo come:

public class Cache { 
    private final Map<String, B> cache; 
    public <T super B> Optional<T> find(String s) { 
     return Optional<T>.ofNullable(cache.get(s)); 
    } 
} 

in modo che entrambe i seguenti lavori:

A a = cache.find("A").orElse(new A()) 
B b = cache.find("B").orElse(new B()) 

Come una soluzione alternativa, ho un metodo di utilità statica come segue:

public static <S, T extends S> Optional<S> convertOptional(Optional<T> optional) { 
    return (Optional<S>)optional; 
} 

Quindi, la mia ultima domanda, è sicura dal punto di vista del codice "ideale" sopra?

A a = OptionalUtil.<A,B>convertOptional(cache.find("A")).orElse(new A()); 

risposta

5

si sta effettivamente cercando di visualizzare l'Optional<B> restituito come Optional<A> senza cambiare il tipo di ritorno (dal momento che non è possibile utilizzare la super). Vorrei solo map il identity function.

A a = cache.find("A").map(Function.<A> identity()).orElse(new A()); 
// or shorter 
A a = cache.find("A").<A> map(x -> x).orElse(new A()); 

Non vedo nulla di sbagliato nel vostro approccio.

2

Sì, il codice è di tipo sicuro, perché si sta casting Optional<T>-Optional<S>, e T è sempre un S. È possibile indicarlo al compilatore utilizzando il @SuppressWarnings("unchecked") annotazioni sul vostro metodo di utilità convertOptional.

Così come l'altra eccellente risposta, è possibile farlo in questo modo. Nota la mancanza di generici rispetto alla tua prima versione.

package com.company; 

import java.util.HashMap; 
import java.util.Map; 
import java.util.Optional; 
import java.util.function.Function; 

public class Cache { 
    private static final Function<A,A> CAST_TO_A = Function.<A>identity(); 
    private final Map<String, B> cache = new HashMap<>(); 

    public Optional<B> find(String s) { 
     return Optional.ofNullable(cache.get(s)); 
    } 

    public static void main(String[] args) { 
     Cache cache = new Cache(); 

     Optional<B> potentialA = cache.find("A"); 

     A a = potentialA.isPresent() ? potentialA.get() : new A(); 

     A anotherWay = cache.find("A").map(CAST_TO_A).orElse(new A()); 

     B b = cache.find("B").orElse(new B()); 
    } 

    public static class A {} 
    public static class B extends A {} 
} 
+1

L'uso di un ternario è un po 'meno elegante, specialmente nelle espressioni lunghe – Dici

+0

L'ineleganza di un uomo è la chiarezza di un altro uomo. L'eleganza di un uomo è il lambda non necessario di un altro uomo. Personalmente, mentre preferisco lambda per questo genere di cose, trovo la necessità di specificare per la mappa (x -> x) inelegante. (A proposito, mi piace la soluzione lambda, ed è per questo che l'ho descritta eccellente) – user1886323

+0

Non stavo confrontando con l'altra risposta, ma con la sintassi di un mondo perfetto. Vorremmo gestire tutti i casi allo stesso modo, o il più vicino possibile. Comunque, stavo solo commentando: p La tua risposta funziona almeno allo – Dici

1

In pratica, penso che sia type-safe, perché la classe Optional è immutabile e T è sottotipo di S.

Attenzione però che T is subtype of S fa NON significa che Optional<T> is subtype of Optional<S>, quindi teoricamente che il cast non è corretto. E in alcuni casi, non è sicuro fare questo tipo di cast e può essere problematico in fase di esecuzione (come accade con List).

Quindi il mio suggerimento è di evitare i cast ogni volta che possiamo, e preferirei definire un metodo come lo getOrElse. Inoltre, per motivi di completezza, i tipi generici del metodo convertOptional potrebbero essere semplificati come di seguito.

class OptionalUtil { 
    public static <S> S getOrElse(Optional<? extends S> optional, Supplier<? extends S> other) { 
     return optional.map(Function.<S>identity()).orElseGet(other); 
    } 

    public static <S> Optional<S> convertOptional(Optional<? extends S> optional) { 
     return (Optional<S>)optional; 
    } 
} 

E potrebbero essere utilizzare come che:

A a1 = OptionalUtil.getOrElse(cache.find("A"), A::new); 
A a2 = OptionalUtil.<A>convertOptional(cache.find("A")).orElseGet(A::new); 

[EDIT] ho sostituito il metodo orElse da orElseGet, perché con l'ex un nuovo oggetto A verrà creato anche se è presente lo Optional.

+0

È interessante notare che getOrElse è esattamente ciò che ho finito per fare. Anch'io odio i cast. – MattWallace

Problemi correlati