Carattere jolly (Java) - Wildcard (Java)

Il carattere jolly ? in Java è un tipo speciale di argomento di tipo che controlla la sicurezza del tipo dell'uso di tipi generici (parametrizzati). Può essere utilizzato nelle dichiarazioni e istanze di variabili, nonché nelle definizioni dei metodi, ma non nella definizione di un tipo generico. Questa è una forma di annotazione della varianza del sito di utilizzo , in contrasto con le annotazioni della varianza del sito di definizione trovate in C # e Scala .

Covarianza per tipi generici

A differenza degli array (che sono covarianti in Java), le diverse istanze di un tipo generico non sono compatibili tra loro, nemmeno esplicitamente: con la dichiarazione Generic<Supertype> superGeneric; Generic<Subtype> subGeneric; il compilatore segnalerebbe un errore di conversione sia per i cast (Generic<Subtype>)superGeneric che per (Generic<Supertype>)subGeneric .

Questa incompatibilità può essere attenuata dal carattere jolly se ? viene utilizzato come parametro di tipo effettivo: Generic<?> è un supertipo di tutte le parametrizzazioni di tipo generico Generic . Ciò consente agli oggetti di tipo Generic<Supertype> e Generic<Subtype> di essere assegnati in modo sicuro a una variabile oa un parametro di metodo di tipo Generic<?> . L'utilizzo Generic<? extends Supertype> consente lo stesso, limitando la compatibilità a Supertype e ai suoi figli. Un'altra possibilità è Generic<? super Subtype> , che accetta anche entrambi gli oggetti e limita la compatibilità a Subtype e tutti i suoi genitori.

Carattere jolly come tipo di parametro

Nel corpo di un'unità generica, il parametro di tipo (formale) viene gestito come il suo limite superiore (espresso con extends ; Object se non vincolato). Se il tipo di ritorno di un metodo è il parametro di tipo, il risultato (ad esempio di tipo ? ) può essere referenziato da una variabile del tipo del limite superiore (o Object ). Nella direzione opposta, il carattere jolly non si adatta a nessun altro tipo, nemmeno Object : se ? è stato applicato come parametro di tipo formale di un metodo, non è possibile passargli alcun parametro effettivo. Tuttavia, gli oggetti di tipo sconosciuto possono essere letti dall'oggetto generico e assegnati a una variabile di un supertipo di upperbound.

class Generic <T extends UpperBound> {
    private T t;
    void write(T t) {
        this.t = t;
    }
    T read() {
        return t;
    }
}
...
Generic<UpperBound> concreteTypeReference = new Generic<UpperBound>();
Generic<?> wildcardReference = concreteTypeReference;
UpperBound ub = wildcardReference.read(); // Object would also be OK
wildcardReference.write(new Object()); // type error
wildcardReference.write(new UpperBound()); // type error
concreteTypeReference.write(new UpperBound()); // OK

Caratteri jolly limitati

Un carattere jolly limitato è uno con un vincolo di ereditarietà superiore o inferiore . Il limite di un carattere jolly può essere un tipo di classe, un tipo di interfaccia , un tipo di matrice o una variabile di tipo. I limiti superiori vengono espressi utilizzando la parola chiave extends e i limiti inferiori utilizzando la parola chiave super . I caratteri jolly possono indicare un limite superiore o un limite inferiore, ma non entrambi.

Limiti superiori

Un limite superiore su un carattere jolly deve essere un sottotipo del limite superiore del parametro di tipo corrispondente dichiarato nel tipo generico corrispondente. Un esempio di carattere jolly che indica esplicitamente un limite superiore è:

Generic<? extends SubtypeOfUpperBound> referenceConstrainedFromAbove;

Questo riferimento può contenere qualsiasi parametrizzazione il Generic cui argomento di tipo è un sottotipo di SubtypeOfUpperBound . Un carattere jolly che non indica esplicitamente un limite superiore è effettivamente lo stesso di uno che ha il vincolo extends Object , poiché tutti i tipi di riferimento in Java sono sottotipi di Object.

Limiti inferiori

Un carattere jolly con un limite inferiore, come

Generic<? super SubtypeOfUpperBound> referenceConstrainedFromBelow;

può contenere qualsiasi parametrizzazione il Generic cui argomento any type è sia un sottotipo del limite superiore del parametro di tipo corrispondente sia un supertipo di SubtypeOfUpperBound .

Creazione di oggetti con caratteri jolly

Nessun oggetto può essere creato con un argomento di tipo jolly: ad esempio, new Generic<?>() è vietato. In pratica, questo non è necessario perché se si volesse creare un oggetto che fosse assegnabile a una variabile di tipo Generic<?> , si potrebbe semplicemente utilizzare qualsiasi tipo arbitrario (che rientra nei vincoli del carattere jolly, se presente) come argomento di tipo.

Tuttavia, new ArrayList<Generic<?>>() è consentito, perché il carattere jolly non è un parametro per il tipo istanziato ArrayList . Lo stesso vale per new ArrayList<List<?>>() .

In un'espressione di creazione di un array, il tipo di componente dell'array deve essere modificabile come definito dalla specifica del linguaggio Java, Sezione 4.7. Ciò implica che, se il tipo di componente dell'array ha argomenti di tipo, devono essere tutti caratteri jolly illimitati (caratteri jolly costituiti solo da un ? ). Ad esempio, new Generic<?>[20] è corretto, mentre new Generic<SomeType>[20] non lo è.

Per entrambi i casi, l'utilizzo di nessun parametro è un'altra opzione. Questo genererà un avviso poiché è meno sicuro per i tipi (vedi Tipo Raw ).

Esempio: elenchi

In Java Collections Framework, la classe List<MyClass> rappresenta una raccolta ordinata di oggetti di tipo MyClass . I limiti superiori sono specificati usando extends : A List<? extends MyClass> è un elenco di oggetti di qualche sottoclasse di MyClass , cioè ogni oggetto nell'elenco è garantito essere di tipo MyClass , quindi si può iterare su di esso usando una variabile di tipo MyClass

public void doSomething(List<? extends MyClass> list) {
    for (MyClass object : list) { // OK
        // do something
    }
}

Tuttavia, non è garantito che si possa aggiungere qualsiasi oggetto di tipo MyClass a tale elenco:

public void doSomething(List<? extends MyClass> list) {
    MyClass m = new MyClass();
    list.add(m); // Compile error
}

Il contrario è vero per i limiti inferiori, che vengono specificati utilizzando super : A List<? super MyClass> è un elenco di oggetti di qualche superclasse di MyClass , cioè è garantito che l'elenco sia in grado di contenere qualsiasi oggetto di tipo MyClass , quindi si può aggiungere qualsiasi oggetto di tipo MyClass :

public void doSomething(List<? super MyClass> list) {
    MyClass m = new MyClass();
    list.add(m); // OK
}

Tuttavia, non è garantito che si possa iterare su tale elenco utilizzando una variabile di tipo MyClass :

public void doSomething(List<? super MyClass> list) {
    for (MyClass object : list) { // Compile error
        // do something
    }
}

Per poter fare entrambe le cose, aggiungere oggetti di tipo MyClass alla lista e iterare su di essa usando una variabile di tipo MyClass , List<MyClass> è necessario a, che è l'unico tipo List sia List<? extends MyClass> e List<? super MyClass> .

Il mnemonico PECS (Producer Extends, Consumer Super) del libro Effective Java di Joshua Bloch offre un modo semplice per ricordare quando utilizzare i caratteri jolly (corrispondenti a Covariance e Contravariance) in Java.

Guarda anche

Riferimenti