Perché un parametro di tipo Java non può avere un limite inferiore?

Considero che non è ansible associare un parametro di tipo generici Java a un limite inferiore (ovvero utilizzando la parola chiave super ). Stavo leggendo cosa aveva da dire le FAQ di Angelika Langer Generics sull’argomento . Dicono che fondamentalmente si riduce ad un limite inferiore che è inutile (“non ha senso”).

Non sono convinto. Posso immaginare un uso per loro per aiutarti a essere più flessibile per i chiamanti di un metodo di libreria che produce un risultato tipizzato. Immagina un metodo che ha creato un elenco di array di una dimensione specificata dall’utente e lo ha riempito con la stringa vuota. Una semplice dichiarazione sarebbe

 public static ArrayList createArrayListFullOfEmptyStrings(int i); 

Ma ciò è inutilmente restrittivo per i tuoi clienti. Perché non possono invocare il tuo metodo in questo modo:

 //should compile List l1 = createArrayListFullOfEmptyStrings(5); List l2 = createArrayListFullOfEmptyStrings(5); List l3 = createArrayListFullOfEmptyStrings(5); //shouldn't compile List l4 = createArrayListFullOfEmptyStrings(5); 

A questo punto sarei tentato di provare la seguente definizione:

 public static  List createArrayListFullOfEmptyStrings(int size) { List list = new ArrayList(size); for(int i = 0; i < size; i++) { list.add(""); } return list; } 

Ma non verrà compilato; la parola chiave super è illegale in questo contesto.

    Il mio esempio è un cattivo esempio (ignorando ciò che dico di seguito)? Perché qui non è utile un limite inferiore? E se fosse utile, qual è il vero motivo per cui non è permesso in Java?

    PS

    So che un’organizzazione migliore potrebbe essere qualcosa del genere:

     public static void populateListWithEmptyStrings(List list, int size); List list = new ArrayList(); populateListWithEmptyStrings(list, 5); 

    Possiamo, ai fini di questa domanda, pretendere che, a causa di un requisito, dobbiamo eseguire entrambe le operazioni in una chiamata al metodo?

    modificare

    @ Tom G (giustamente) chiede quale vantaggio abbia una List su una List . Per uno, nessuno ha detto che la lista restituita è immutabile, quindi ecco un vantaggio:

     List l2 = createArrayListFullOfEmptyStrings(5); l2.add(new StringBuilder("foo").append("bar")); 

    Fondamentalmente, non è abbastanza utile.

    Penso che il tuo esempio indichi l’unico vantaggio di un limite inferiore, una funzione che le FAQ chiamano Restricted Instantiation :

    La linea di fondo è: tutto ciò che un “super” legato ti comprerebbe è la restrizione che solo i supertipi di Numero possono essere usati come argomenti tipo . ….

    Ma come sottolineano gli altri post, l’utilità di questa funzionalità può essere limitata.

    A causa della natura del polimorfismo e della specializzazione, i limiti superiori sono molto più utili dei limiti inferiori come descritto nelle FAQ ( Accesso ai membri non statici e Cancellazione dei tipi ). Sospetto che la complessità introdotta dai limiti inferiori non valga il suo valore limitato.


    OP: Voglio aggiungere Penso che tu abbia dimostrato che è utile, ma non abbastanza utile. Vieni con gli inconfutabili casi d’uso dell’assassino e sosterrò la JSR. 🙂

    le specifiche parlano dei limiti inferiori dei parametri di tipo, ad esempio

    4.10.2

    una variabile di tipo è un supertipo diretto del suo limite inferiore.

    5.1.10

    una nuova variabile di tipo … il cui limite inferiore

    Sembra che una variabile di tipo abbia solo un limite inferiore (non nullo) se è sintetico come risultato della cattura di caratteri jolly. Cosa succede se la lingua consente limiti inferiori su tutti i parametri di tipo? Probabilmente non causa molti problemi, ed è escluso solo per mantenere i generici più semplici (beh …). L’ aggiornamento si dice che l’indagine teorica sui parametri del tipo con limiti inferiori non è completamente condotta.

    Aggiornamento: un documento che afferma che i limiti inferiori sono ok: “Inferferenza di tipo Java è rotto: possiamo aggiustarlo” di Daniel Smith

    RITRATTO: il seguente argomento è sbagliato. L’esempio di OP è legittimo.

    Il tuo esempio particolare non è molto convincente. Innanzitutto non è sicuro. L’elenco restituito è effettivamente un List , non è sicuro visualizzarlo come un altro tipo. Supponiamo che il tuo codice compili:

      List l2 = createArrayListFullOfEmptyStrings(5); 

    quindi possiamo aggiungere non-String ad esso, che è sbagliato

      CharSequence chars = new StringBuilder(); l2.add(chars); 

    Beh, una List non lo è, ma un po ‘come un elenco di CharSequence. Il tuo bisogno può essere risolto utilizzando il carattere jolly:

     public static List createArrayListFullOfEmptyStrings(int size) // a list of some specific subtype of CharSequence List l2 = createArrayListFullOfEmptyStrings(5); // legal. can retrieve elements as CharSequence CharSequence chars = l2.get(0); // illegal, won't compile. cannot insert elements as CharSequence l2.add(new StringBuilder()); 

    Più che una risposta, questo è un altro caso d’uso (forse assassino?). Ho un aiutante ModelDecorator. Voglio che abbia la seguente API pubblica

     class ModelDecorator{ public static  ModelDecorator create(Class clazz); public  T from(SUPER fromInstance); } 

    Quindi, date le classi A, B estende A, può essere usato in questo modo:

     A a = new A(); B b = ModelDecorator.create(B.class).from(a); 

    Ma voglio avere limiti su T e SUPER, quindi mi assicuro che solo le sottoclassi possano essere istanziate usando l’API. In questo momento, posso fare:

     C c = new C(); B b = ModelDecorator.create(B.class).from(c); 

    Dove B NON eredita da C.

    Ovviamente, se potessi fare:

      public  T from(SUPER fromInstance); 

    Questo risolverebbe il mio problema.

    Che vantaggio ti offre la digitazione della lista in quel punto? Quando si esegue l’iterazione sulla raccolta restituita, si dovrebbe comunque essere in grado di eseguire quanto segue:

     for(String s : returnedList) { CharSequence cs = s; //do something with your CharSequence } 

    Hmm, ok – lavoriamo con questo. Definisci un metodo:

    public static List createArrayListFullOfEmptyStrings(int size) {

    Cosa significa? Significa che se chiamo il tuo metodo, allora torno all’elenco di alcune superclass di String. Forse restituisce un elenco di String. Forse restituisce una lista di oggetti. Non lo so.

    Freddo.

    Listl1 = createArrayListFullOfEmptyStrings(5);

    Secondo te, questo dovrebbe essere compilato. Ma non è giusto! Posso mettere un intero in una lista di Object – l1.add(3) . Ma se stai restituendo una lista di String, allora farlo dovrebbe essere illegale.

    List l3 = createArrayListFullOfEmptyStrings(5);

    Secondo te, questo dovrebbe essere compilato. Ma non è giusto! l3.get(1) dovrebbe sempre restituire una stringa … ma quel metodo potrebbe aver restituito una lista di oggetti, il che significa che l3.get (1) potrebbe essere in teoria un intero.

    L’unica cosa che funziona è

    List l5 = createArrayListFullOfEmptyStrings(5);

    Tutto quello che so è che posso tranquillamente chiamare l4.put("foo") , e posso tranquillamente ottenere Object o = l4.get(2) .