Me deparei com código Java como este:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Qual é a diferença entre os três itens acima e como eles chamam esse tipo de declaração de classe ou interface em Java?
Me deparei com código Java como este:
public interface Foo<E> {}
public interface Bar<T> {}
public interface Zar<?> {}
Qual é a diferença entre os três itens acima e como eles chamam esse tipo de declaração de classe ou interface em Java?
Respostas:
Bem, não há diferença entre os dois primeiros - eles estão apenas usando nomes diferentes para o parâmetro type ( E
ou T
).
A terceira não é uma declaração válida - ?
é usada como curinga que é usada ao fornecer um argumento de tipo , por exemplo, List<?> foo = ...
significa que foo
se refere a uma lista de algum tipo, mas não sabemos o que.
Tudo isso é genérico , que é um tópico bastante grande. Você pode aprender sobre isso através dos seguintes recursos, embora haja mais disponíveis, é claro:
T
e E
- eles são apenas identificadores. Você poderia escrever KeyValuePair<K, V>
por exemplo. ?
tem um significado especial.
É mais convenção do que qualquer outra coisa.
T
deve ser um tipo E
deve ser um elemento ( List<E>
: uma lista de elementos) K
é a chave (em a Map<K,V>
) V
é Value (como valor de retorno ou valor mapeado) Eles são totalmente intercambiáveis (não obstante os conflitos na mesma declaração).
As respostas anteriores explicam os parâmetros de tipo (T, E etc.), mas não explicam o caractere curinga "?" Ou as diferenças entre eles; portanto, abordarei isso.
Primeiro, só para esclarecer: os parâmetros curinga e tipo não são os mesmos. Onde os parâmetros de tipo definem um tipo de variável (por exemplo, T) que representa o tipo de um escopo, o curinga não: o curinga apenas define um conjunto de tipos permitidos que você pode usar para um tipo genérico. Sem qualquer delimitação ( extends
ou super
), o curinga significa "use qualquer tipo aqui".
O curinga sempre se encontra entre colchetes angulares e só tem significado no contexto de um tipo genérico:
public void foo(List<?> listOfAnyType) {...} // pass a List of any type
Nunca
public <?> ? bar(? someType) {...} // error. Must use type params here
ou
public class MyGeneric ? { // error
public ? getFoo() { ... } // error
...
}
Fica mais confuso onde eles se sobrepõem. Por exemplo:
List<T> fooList; // A list which will be of type T, when T is chosen.
// Requires T was defined above in this scope
List<?> barList; // A list of some type, decided elsewhere. You can do
// this anywhere, no T required.
Há muita sobreposição no que é possível com as definições de método. A seguir, são funcionalmente idênticos:
public <T> void foo(List<T> listOfT) {...}
public void bar(List<?> listOfSomething) {...}
Então, se houver sobreposição, por que usar um ou outro? Às vezes, é honestamente apenas estilo: algumas pessoas dizem que se você não precisar de um parâmetro de tipo, você deve usar um curinga apenas para tornar o código mais simples / mais legível. Uma diferença principal que expliquei acima: parâmetros de tipo definem uma variável de tipo (por exemplo, T) que você pode usar em outro lugar no escopo; o curinga não. Caso contrário, existem duas grandes diferenças entre os parâmetros de tipo e o curinga:
Parâmetros de tipo podem ter várias classes delimitadoras; o curinga não pode:
public class Foo <T extends Comparable<T> & Cloneable> {...}
O curinga pode ter limites mais baixos; params de tipo não podem:
public void bar(List<? super Integer> list) {...}
No exemplo acima, List<? super Integer>
define Integer
como um limite inferior no curinga, o que significa que o tipo de Lista deve ser Inteiro ou um supertipo de Inteiro. O limite de tipo genérico está além do que quero abordar em detalhes. Em resumo, permite definir quais tipos podem ser de um tipo genérico. Isso torna possível o tratamento de genéricos polimorficamente. Por exemplo, com:
public void foo(List<? extends Number> numbers) {...}
Você pode passar um List<Integer>
, List<Float>
, List<Byte>
, etc para numbers
. Sem delimitação de tipos, isso não funcionará - é assim que os genéricos são.
Finalmente, aqui está uma definição de método que usa o curinga para fazer algo que eu acho que você não pode fazer de outra maneira:
public static <T extends Number> void adder(T elem, List<? super Number> numberSuper) {
numberSuper.add(elem);
}
numberSuper
pode ser uma lista de números ou qualquer supertipo de número (por exemplo, List<Object>
) e elem
deve ser um número ou qualquer subtipo. Com todas as delimitações, o compilador pode ter certeza de que .add()
é tipicamente seguro .
Uma variável de tipo, <T>, pode ser qualquer tipo não primitivo que você especificar: qualquer tipo de classe, qualquer tipo de interface, qualquer tipo de matriz ou mesmo outra variável de tipo.
Os nomes dos parâmetros de tipo mais usados são:
No Java 7, é permitido instanciar assim:
Foo<String, Integer> foo = new Foo<>(); // Java 7
Foo<String, Integer> foo = new Foo<String, Integer>(); // Java 6
Os nomes dos parâmetros de tipo mais usados são:
E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
Você verá esses nomes usados em toda a API do Java SE
O compilador fará uma captura para cada curinga (por exemplo, ponto de interrogação na Lista) quando compuser uma função como:
foo(List<?> list) {
list.put(list.get()) // ERROR: capture and Object are not identical type.
}
No entanto, um tipo genérico como V ficaria bem e o tornaria um método genérico :
<V>void foo(List<V> list) {
list.put(list.get())
}