Wildcard (Java) - Wildcard (Java)

O curinga ? em Java é um tipo especial de argumento de tipo que controla a segurança de tipo do uso de tipos genéricos (parametrizados). Ele pode ser usado em declarações e instanciações de variáveis, bem como em definições de métodos, mas não na definição de um tipo genérico. Esta é uma forma de anotação de variação de site de uso , em contraste com as anotações de variação de site de definição encontradas em C # e Scala .

Covariância para tipos genéricos

Ao contrário dos arrays (que são covariantes em Java), diferentes instanciações de um tipo genérico não são compatíveis entre si, nem mesmo explicitamente: com a declaração, Generic<Supertype> superGeneric; Generic<Subtype> subGeneric; o compilador relataria um erro de conversão para ambos os castings (Generic<Subtype>)superGeneric e (Generic<Supertype>)subGeneric .

Esta incompatibilidade pode ser atenuada pelo curinga se ? for usado como um parâmetro de tipo real: Generic<?> é um supertipo de todas as parametrizações do tipo genérico Generic . Isso permite que objetos do tipo Generic<Supertype> e Generic<Subtype> sejam atribuídos com segurança a uma variável ou parâmetro de método do tipo Generic<?> . O uso Generic<? extends Supertype> permite o mesmo, restringindo a compatibilidade para Supertype e seus filhos. Outra possibilidade é Generic<? super Subtype> , que também aceita ambos os objetos e restringe a compatibilidade para Subtype e todos os seus pais.

Curinga como tipo de parâmetro

No corpo de uma unidade genérica, o parâmetro de tipo (formal) é tratado como seu limite superior (expresso com extends ; Object se não for restrito). Se o tipo de retorno de um método é o parâmetro de tipo, o resultado (por exemplo, de tipo ? ) pode ser referenciado por uma variável do tipo do limite superior (ou Object ). Na outra direção, o curinga não se ajusta a nenhum outro tipo, nem mesmo Object : se ? foi aplicado como o parâmetro de tipo formal de um método, nenhum parâmetro real pode ser passado a ele. No entanto, objetos do tipo desconhecido podem ser lidos do objeto genérico e atribuídos a uma variável de um supertipo do limite superior.

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

Curingas delimitadas

Um curinga limitado é aquele com uma restrição de herança superior ou inferior . O limite de um curinga pode ser um tipo de classe, tipo de interface , tipo de matriz ou variável de tipo. Os limites superiores são expressos usando a palavra-chave extends e os limites inferiores usando a palavra - chave super . Os curingas podem indicar um limite superior ou inferior, mas não ambos.

Limites superiores

Um limite superior em um curinga deve ser um subtipo do limite superior do parâmetro de tipo correspondente declarado no tipo genérico correspondente. Um exemplo de curinga que afirma explicitamente um limite superior é:

Generic<? extends SubtypeOfUpperBound> referenceConstrainedFromAbove;

Esta referência pode conter qualquer parametrização de Generic cujo argumento de tipo seja um subtipo de SubtypeOfUpperBound . Um curinga que não declara explicitamente um limite superior é efetivamente igual àquele que possui a restrição extends Object , uma vez que todos os tipos de referência em Java são subtipos de Object.

Limites inferiores

Um curinga com um limite inferior, como

Generic<? super SubtypeOfUpperBound> referenceConstrainedFromBelow;

pode conter qualquer parametrização de Generic cujo qualquer argumento de tipo seja um subtipo do limite superior do parâmetro de tipo correspondente e um supertipo de SubtypeOfUpperBound .

Criação de objeto com curinga

Nenhum objeto pode ser criado com um argumento do tipo curinga: por exemplo, new Generic<?>() é proibido. Na prática, isso é desnecessário porque se alguém quisesse criar um objeto que pudesse ser atribuído a uma variável do tipo Generic<?> , poderia simplesmente usar qualquer tipo arbitrário (que se enquadrasse nas restrições do curinga, se houver) como o argumento de tipo.

No entanto, new ArrayList<Generic<?>>() é permitido, porque o curinga não é um parâmetro para o tipo instanciado ArrayList . O mesmo vale para new ArrayList<List<?>>() .

Em uma expressão de criação de matriz, o tipo de componente da matriz deve ser reificável conforme definido pela Especificação da linguagem Java, Seção 4.7. Isso implica que, se o tipo de componente da matriz tiver quaisquer argumentos de tipo, todos eles devem ser curingas ilimitadas (curingas consistindo apenas em a ? ). Por exemplo, new Generic<?>[20] está correto, enquanto new Generic<SomeType>[20] não.

Para ambos os casos, usar nenhum parâmetro é outra opção. Isso irá gerar um aviso, pois é menos seguro para o tipo (consulte Tipo bruto ).

Exemplo: Listas

No Java Collections Framework, a classe List<MyClass> representa uma coleção ordenada de objetos do tipo MyClass . Os limites superiores são especificados usando extends : A List<? extends MyClass> é uma lista de objetos de alguma subclasse de MyClass , ou seja, qualquer objeto na lista é garantido como sendo do tipo MyClass , portanto, pode-se iterar sobre ele usando uma variável do tipo MyClass

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

No entanto, não é garantido que se possa adicionar qualquer objeto do tipo MyClass a essa lista:

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

O inverso é verdadeiro para os limites inferiores, que são especificados usando super : A List<? super MyClass> é uma lista de objetos de alguma superclasse de MyClass , ou seja, a lista tem garantia de conter qualquer objeto do tipo MyClass , portanto, pode-se adicionar qualquer objeto do tipo MyClass :

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

No entanto, não é garantido que se possa iterar nessa lista usando uma variável do tipo MyClass :

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

Para poder adicionar objetos do tipo MyClass à lista e iterar sobre ela usando uma variável do tipo MyClass , List<MyClass> é necessário um, que é o único tipo List que é List<? extends MyClass> e List<? super MyClass> .

O mnemônico PECS (Producer Extends, Consumer Super) do livro Effective Java de Joshua Bloch fornece uma maneira fácil de lembrar quando usar curingas (correspondendo a Covariância e Contravariância) em Java.

Veja também

Referências