Pensei em oferecer este softball para quem quisesse jogar fora do parque. O que são genéricos, quais são as vantagens dos genéricos, por que, onde, como devo usá-los? Por favor, mantenha-o bem básico. Obrigado.
Pensei em oferecer este softball para quem quisesse jogar fora do parque. O que são genéricos, quais são as vantagens dos genéricos, por que, onde, como devo usá-los? Por favor, mantenha-o bem básico. Obrigado.
Respostas:
Eu realmente odeio me repetir. Eu odeio digitar a mesma coisa com mais frequência do que o necessário. Não gosto de repetir as coisas várias vezes com pequenas diferenças.
Em vez de criar:
class MyObjectList {
MyObject get(int index) {...}
}
class MyOtherObjectList {
MyOtherObject get(int index) {...}
}
class AnotherObjectList {
AnotherObject get(int index) {...}
}
Posso construir uma classe reutilizável ... (no caso de você não querer usar a coleção bruta por algum motivo)
class MyList<T> {
T get(int index) { ... }
}
Agora estou 3x mais eficiente e só preciso manter uma cópia. Por que você NÃO deseja manter menos código?
Isso também é verdadeiro para classes que não são de coleção, como a Callable<T>
ou a, Reference<T>
que precisam interagir com outras classes. Você realmente deseja estender Callable<T>
e Future<T>
todas as outras classes associadas para criar versões de tipo seguro?
Eu não.
Não precisar fazer o typecast é uma das maiores vantagens dos genéricos Java , pois ele executa a verificação de tipo em tempo de compilação. Isso reduzirá a possibilidade de ClassCastException
s que podem ser lançados no tempo de execução e podem levar a um código mais robusto.
Mas suspeito que você esteja totalmente ciente disso.
Cada vez que vejo os Genéricos, fico com dor de cabeça. Acho que a melhor parte do Java é sua simplicidade e sintaxe mínima e genéricos não são simples e adicionam uma quantidade significativa de nova sintaxe.
No início, também não vi o benefício dos genéricos. Comecei a aprender Java a partir da sintaxe 1.4 (embora Java 5 estivesse lançado na época) e quando encontrei genéricos, senti que era mais código para escrever e realmente não entendi os benefícios.
IDEs modernos facilitam a escrita de código com genéricos.
A maioria dos IDEs modernos e decentes são inteligentes o suficiente para ajudar a escrever código com genéricos, especialmente com o auto-completar de código.
Aqui está um exemplo de como fazer um Map<String, Integer>
com a HashMap
. O código que eu teria que digitar é:
Map<String, Integer> m = new HashMap<String, Integer>();
E, de fato, é muito digitar apenas para fazer um novo HashMap
. No entanto, na realidade, eu só tive que digitar isso antes que o Eclipse soubesse o que eu precisava:
Map<String, Integer> m = new Ha
Ctrl+Space
Verdade, eu precisava selecionar HashMap
em uma lista de candidatos, mas basicamente o IDE sabia o que adicionar, incluindo os tipos genéricos. Com as ferramentas certas, usar genéricos não é tão ruim.
Além disso, como os tipos são conhecidos, ao recuperar elementos da coleção genérica, o IDE agirá como se aquele objeto já fosse um objeto de seu tipo declarado - não há necessidade de cast para o IDE saber qual o tipo do objeto é.
Uma vantagem importante dos genéricos vem da maneira como ele funciona bem com os novos recursos do Java 5. Aqui está um exemplo de como jogar números inteiros em a Set
e calcular seu total:
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);
int total = 0;
for (int i : set) {
total += i;
}
Nesse trecho de código, há três novos recursos Java 5 presentes:
Primeiro, os genéricos e autoboxing de primitivos permitem as seguintes linhas:
set.add(10);
set.add(42);
O inteiro 10
é autoboxed em um Integer
com o valor de 10
. (E o mesmo para 42
). Então isso Integer
é jogado no Set
que é conhecido por conter Integer
s. Tentar lançar um String
causaria um erro de compilação.
Em seguida, o loop for for each leva todos os três:
for (int i : set) {
total += i;
}
Primeiro, os Set
contendo Integer
s são usados em um loop for-each. Cada elemento é declarado como um int
e isso é permitido quando o Integer
é desempacotado de volta para o primitivo int
. E o fato de que esse desembalagem ocorre é conhecido porque genéricos foram usados para especificar que havia Integer
s retidos no Set
.
Os genéricos podem ser a cola que reúne os novos recursos introduzidos no Java 5 e simplesmente tornam a codificação mais simples e segura. E na maioria das vezes os IDEs são inteligentes o suficiente para ajudá-lo com boas sugestões, então geralmente, não será muito mais difícil digitar.
E, francamente, como pode ser visto no Set
exemplo, acho que utilizar os recursos do Java 5 pode tornar o código mais conciso e robusto.
Editar - Um exemplo sem genéricos
A seguir está uma ilustração do Set
exemplo acima sem o uso de genéricos. É possível, mas não é exatamente agradável:
Set set = new HashSet();
set.add(10);
set.add(42);
int total = 0;
for (Object o : set) {
total += (Integer)o;
}
(Observação: o código acima irá gerar um aviso de conversão não verificada em tempo de compilação.)
Ao usar coleções não genéricas, os tipos inseridos na coleção são objetos do tipo Object
. Portanto, neste exemplo, a Object
é o que está sendo add
inserido no conjunto.
set.add(10);
set.add(42);
Nas linhas acima, o autoboxing está em jogo - o int
valor primitivo 10
e 42
está sendo autoboxing em Integer
objetos, que estão sendo adicionados ao Set
. No entanto, lembre-se de que os Integer
objetos estão sendo tratados como Object
s, pois não há informações de tipo para ajudar o compilador a saber que tipo ele Set
deve esperar.
for (Object o : set) {
Essa é a parte crucial. O motivo pelo qual o loop for-each funciona é porque o Set
implementa a Iterable
interface, que retorna uma Iterator
informação do tipo with, se presente. ( Iterator<T>
isto é.)
No entanto, uma vez que não existe informação de tipo, o Set
retornará uma Iterator
que irá retornar os valores no Set
como Object
s, e é por isso que o elemento a ser recuperado na para-cada ciclo tem de ser do tipo Object
.
Agora que o Object
foi recuperado de Set
, ele precisa ser convertido em um Integer
manualmente para realizar a adição:
total += (Integer)o;
Aqui, um typecast é executado de um Object
para um Integer
. Nesse caso, sabemos que isso sempre funcionará, mas a conversão manual de tipos sempre me faz sentir que é um código frágil que pode ser danificado se uma pequena alteração for feita em outro local. (Eu sinto que cada typecast está ClassCastException
esperando para acontecer, mas estou divagando ...)
O Integer
agora está desempacotado em um int
e tem permissão para realizar a adição na int
variável total
.
Espero poder ilustrar que os novos recursos do Java 5 podem ser usados com código não genérico, mas não é tão simples e direto quanto escrever código com genéricos. E, na minha opinião, para tirar o máximo proveito dos novos recursos do Java 5, deve-se olhar para os genéricos, se pelo menos permitirem verificações de tempo de compilação para evitar que as previsões de tipos inválidas gerem exceções em tempo de execução.
Se você pesquisar o banco de dados de bugs do Java pouco antes do lançamento do 1.5, encontrará sete vezes mais bugs com do NullPointerException
que ClassCastException
. Portanto, não parece um ótimo recurso encontrar bugs, ou pelo menos bugs que persistem após um pequeno teste de fumaça.
Para mim, a grande vantagem dos genéricos é que eles documentam em código informações de tipo importantes. Se eu não quisesse que as informações de tipo fossem documentadas em código, usaria uma linguagem digitada dinamicamente ou, pelo menos, uma linguagem com inferência de tipo mais implícita.
Manter as coleções de um objeto para si mesmo não é um estilo ruim (mas o estilo comum é ignorar efetivamente o encapsulamento). Em vez disso, depende do que você está fazendo. Passar coleções para "algoritmos" é um pouco mais fácil de verificar (durante ou antes do tempo de compilação) com os genéricos.
Os genéricos em Java facilitam o polimorfismo paramétrico . Por meio de parâmetros de tipo, você pode passar argumentos para tipos. Assim como um método como String foo(String s)
modela algum comportamento, não apenas para uma string específica, mas para qualquer string s
, um tipo como List<T>
modela algum comportamento, não apenas para um tipo específico, mas para qualquer tipo . List<T>
diz que para qualquer tipo T
, há um tipo de List
cujos elementos são T
s . Portanto, List
é realmente um construtor de tipo . Ele pega um tipo como argumento e constrói outro tipo como resultado.
Aqui estão alguns exemplos de tipos genéricos que uso todos os dias. Primeiro, uma interface genérica muito útil:
public interface F<A, B> {
public B f(A a);
}
Essa interface diz que, para alguns tipos, A
e B
, há uma função (chamada f
) que recebe um A
e retorna um B
. Quando você implementa esta interface, A
e B
pode ser qualquer tipo que você quiser, desde que você forneça uma função f
que leva a primeira e retorna a última. Aqui está um exemplo de implementação da interface:
F<Integer, String> intToString = new F<Integer, String>() {
public String f(int i) {
return String.valueOf(i);
}
}
Antes dos genéricos, o polimorfismo era obtido pela subclasse usando a extends
palavra - chave. Com os genéricos, podemos realmente eliminar a subclasse e usar o polimorfismo paramétrico. Por exemplo, considere uma classe parametrizada (genérica) usada para calcular códigos hash para qualquer tipo. Em vez de substituir Object.hashCode (), usaríamos uma classe genérica como esta:
public final class Hash<A> {
private final F<A, Integer> hashFunction;
public Hash(final F<A, Integer> f) {
this.hashFunction = f;
}
public int hash(A a) {
return hashFunction.f(a);
}
}
Isso é muito mais flexível do que usar herança, porque podemos ficar com o tema de usar composição e polimorfismo paramétrico sem bloquear hierarquias frágeis.
Os genéricos do Java não são perfeitos. Você pode abstrair sobre os tipos, mas não pode abstrair sobre os construtores de tipo, por exemplo. Ou seja, você pode dizer "para qualquer tipo T", mas não pode dizer "para qualquer tipo T que receba um parâmetro de tipo A".
Escrevi um artigo sobre esses limites dos genéricos Java, aqui.
Uma grande vitória dos genéricos é que eles permitem que você evite a criação de subclasses. A criação de subclasses tende a resultar em hierarquias de classes frágeis que são difíceis de estender e classes que são difíceis de entender individualmente sem olhar para toda a hierarquia.
Enquanto o teor antes de genéricos você pode ter aulas de como Widget
prorrogado por FooWidget
, BarWidget
e BazWidget
, com os genéricos você pode ter uma única classe genérica Widget<A>
que leva um Foo
, Bar
ou Baz
em seu construtor para lhe dar Widget<Foo>
, Widget<Bar>
e Widget<Baz>
.
Os genéricos evitam o impacto no desempenho do boxing e unboxing. Basicamente, olhe para ArrayList vs List <T>. Ambos fazem as mesmas coisas básicas, mas List <T> será muito mais rápido porque você não precisa encaixotar de / para o objeto.
O melhor benefício para os Genéricos é a reutilização de código. Vamos dizer que você tem muitos objetos de negócios e vai escrever um código MUITO semelhante para cada entidade para executar as mesmas ações. (IE Linq para operações SQL).
Com os genéricos, você pode criar uma classe que será capaz de operar com qualquer um dos tipos que herdam de uma determinada classe base ou implementar uma determinada interface da seguinte forma:
public interface IEntity
{
}
public class Employee : IEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}
public class Company : IEntity
{
public string Name { get; set; }
public string TaxID { get; set }
}
public class DataService<ENTITY, DATACONTEXT>
where ENTITY : class, IEntity, new()
where DATACONTEXT : DataContext, new()
{
public void Create(List<ENTITY> entities)
{
using (DATACONTEXT db = new DATACONTEXT())
{
Table<ENTITY> table = db.GetTable<ENTITY>();
foreach (ENTITY entity in entities)
table.InsertOnSubmit (entity);
db.SubmitChanges();
}
}
}
public class MyTest
{
public void DoSomething()
{
var dataService = new DataService<Employee, MyDataContext>();
dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
var otherDataService = new DataService<Company, MyDataContext>();
otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });
}
}
Observe a reutilização do mesmo serviço, dados os diferentes tipos no método DoSomething acima. Verdadeiramente elegante!
Existem muitos outros excelentes motivos para usar genéricos em seu trabalho, este é o meu favorito.
Eu simplesmente gosto deles porque fornecem uma maneira rápida de definir um tipo personalizado (já que eu os uso de qualquer maneira).
Então, por exemplo, em vez de definir uma estrutura que consiste em uma string e um inteiro, e então ter que implementar um conjunto completo de objetos e métodos sobre como acessar um array dessas estruturas e assim por diante, você pode apenas fazer um Dicionário
Dictionary<int, string> dictionary = new Dictionary<int, string>();
E o compilador / IDE faz o resto do trabalho pesado. Um Dicionário em particular permite que você use o primeiro tipo como uma chave (sem valores repetidos).
Coleções digitadas - mesmo se você não quiser usá-las, provavelmente terá que lidar com elas de outras bibliotecas, de outras fontes.
Digitação genérica na criação de classe:
public class Foo <T> {public T get () ...
Evitar o elenco - sempre não gostei de coisas como
new Comparator {public int compareTo (Object o) {if (o instanceof classIcareAbout) ...
Onde você está essencialmente verificando uma condição que deveria existir apenas porque a interface é expressa em termos de objetos.
Minha reação inicial aos genéricos foi semelhante à sua - "muito confuso, muito complicado". Minha experiência é que depois de usá-los um pouco, você se acostuma com eles, e o código sem eles parece menos claramente especificado e apenas menos confortável. Além disso, o resto do mundo java os usa, então você terá que entrar no programa eventualmente, certo?
Para dar um bom exemplo. Imagine que você tem uma classe chamada Foo
public class Foo
{
public string Bar() { return "Bar"; }
}
Exemplo 1 Agora você deseja ter uma coleção de objetos Foo. Você tem duas opções, LIst ou ArrayList, e ambas funcionam de maneira semelhante.
Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();
//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());
No código acima, se eu tentar adicionar uma classe de FireTruck em vez de Foo, o ArrayList irá adicioná-lo, mas a Lista Genérica de Foo fará com que uma exceção seja lançada.
Exemplo dois.
Agora você tem suas duas listas de arrays e deseja chamar a função Bar () em cada uma. Como o ArrayList é preenchido com objetos, você deve lançá-los antes de poder chamar a barra. Mas, como a Lista Genérica de Foo pode conter apenas Foos, você pode chamar Bar () diretamente nesses.
foreach(object o in al)
{
Foo f = (Foo)o;
f.Bar();
}
foreach(Foo f in fl)
{
f.Bar();
}
Você nunca escreveu um método (ou uma classe) em que o conceito-chave do método / classe não estivesse fortemente vinculado a um tipo de dados específico dos parâmetros / variáveis de instância (pense em lista vinculada, funções máx. / Mín., Pesquisa binária , etc.).
Você nunca desejou poder reutilizar o algorthm / código sem recorrer à reutilização cut-n-paste ou comprometer a digitação forte (por exemplo, quero um List
de Strings, não uma List
das coisas que espero que sejam strings!)?
É por isso que você deve querem usar os genéricos (ou algo melhor).
Não se esqueça de que os genéricos não são usados apenas por classes, eles também podem ser usados por métodos. Por exemplo, pegue o seguinte snippet:
private <T extends Throwable> T logAndReturn(T t) {
logThrowable(t); // some logging method that takes a Throwable
return t;
}
É simples, mas pode ser usado com muita elegância. O bom é que o método retorna tudo o que foi fornecido. Isso ajuda quando você está lidando com exceções que precisam ser devolvidas ao chamador:
...
} catch (MyException e) {
throw logAndReturn(e);
}
A questão é que você não perde o tipo passando-o por um método. Você pode lançar o tipo correto de exceção em vez de apenas um Throwable
, o que seria tudo o que você poderia fazer sem os genéricos.
Este é apenas um exemplo simples de um uso para métodos genéricos. Existem algumas outras coisas legais que você pode fazer com métodos genéricos. O mais legal, na minha opinião, é tipo inferir com genéricos. Veja o seguinte exemplo (retirado do livro Effective Java 2nd Edition de Josh Bloch):
...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
return new HashMap<K, V>();
}
Isso não faz muito, mas elimina alguma confusão quando os tipos genéricos são longos (ou aninhados; ou seja, Map<String, List<String>>
).
Throwable
texto simples de dentro de um corpo de método que tenha exceções declaradas específicas. A alternativa é escrever métodos separados para retornar cada tipo de exceção ou fazer a conversão você mesmo com um método não genérico que retorna um Throwable
. O primeiro é muito prolixo e inútil e o último não terá nenhuma ajuda do compilador. Ao usar genéricos, o compilador irá inserir automaticamente o elenco correto para você. Portanto, a questão é: isso vale a pena a complexidade?
A principal vantagem, como Mitchel aponta, é a digitação forte sem a necessidade de definir várias classes.
Dessa forma, você pode fazer coisas como:
List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();
Sem os genéricos, você teria que lançar blah [0] para o tipo correto para acessar suas funções.
o jvm casts de qualquer maneira ... cria implicitamente o código que trata o tipo genérico como "Objeto" e cria casts para a instanciação desejada. Os genéricos Java são apenas açúcar sintático.
Eu sei que esta é uma pergunta C #, mas genéricos são usados em outras linguagens também e seu uso / objetivos são bastante semelhantes.
As coleções Java usam genéricos desde Java 1.5. Portanto, um bom lugar para usá-los é quando você estiver criando seu próprio objeto semelhante a uma coleção.
Um exemplo que vejo em quase todos os lugares é uma classe Pair, que contém dois objetos, mas precisa lidar com esses objetos de uma maneira genérica.
class Pair<F, S> {
public final F first;
public final S second;
public Pair(F f, S s)
{
first = f;
second = s;
}
}
Sempre que você usar esta classe Pair, você pode especificar com que tipo de objetos deseja lidar e qualquer tipo de problema de conversão aparecerá em tempo de compilação, em vez de tempo de execução.
Os genéricos também podem ter seus limites definidos com as palavras-chave 'super' e 'extends'. Por exemplo, se você quiser lidar com um tipo genérico, mas quiser ter certeza de que ele estende uma classe chamada Foo (que tem um método setTitle):
public class FooManager <F extends Foo>{
public void setTitle(F foo, String title) {
foo.setTitle(title);
}
}
Embora não seja muito interessante por si só, é útil saber que sempre que você lida com um FooManager, você sabe que ele manipulará tipos MyClass e que MyClass estende Foo.
Da documentação do Sun Java, em resposta a "por que devo usar genéricos?":
"Os genéricos fornecem uma maneira de comunicar o tipo de uma coleção ao compilador, para que possa ser verificado. Uma vez que o compilador conhece o tipo de elemento da coleção, o compilador pode verificar se você usou a coleção de forma consistente e pode inserir as projeções corretas em valores sendo retirados da coleção ... O código usando genéricos é mais claro e seguro .... o compilador pode verificar em tempo de compilação que as restrições de tipo não são violadas em tempo de execução [ênfase minha]. o programa compila sem avisos, podemos afirmar com certeza que ele não lançará uma ClassCastException em tempo de execução. O efeito líquido do uso de genéricos, especialmente em programas grandes, é maior legibilidade e robustez . [ênfase minha] "
Os genéricos permitem que você crie objetos fortemente tipados, mas você não precisa definir o tipo específico. Acho que o melhor exemplo útil é a lista e classes semelhantes.
Usando a lista genérica, você pode ter uma List List List o que quiser e você sempre pode fazer referência à tipagem forte, você não precisa converter ou qualquer coisa como faria com um Array ou List padrão.
Os genéricos permitem que você use tipagem forte para objetos e estruturas de dados que devem ser capazes de conter qualquer objeto. Ele também elimina typecasts tediosos e caros ao recuperar objetos de estruturas genéricas (boxing / unboxing).
Um exemplo que usa ambos é uma lista vinculada. De que serviria uma classe de lista encadeada se pudesse usar apenas o objeto Foo? Para implementar uma lista vinculada que pode lidar com qualquer tipo de objeto, a lista vinculada e os nós em uma classe interna de nó hipotético devem ser genéricos se você quiser que a lista contenha apenas um tipo de objeto.
Outra vantagem de usar Genéricos (especialmente com Coleções / Listas) é obter a Verificação de Tipo de Tempo de Compilação. Isso é muito útil ao usar uma lista genérica em vez de uma lista de objetos.
A única razão é que eles fornecem segurança de tipo
List<Customer> custCollection = new List<Customer>;
em oposição a,
object[] custCollection = new object[] { cust1, cust2 };
como um exemplo simples.
Em resumo, os genéricos permitem que você especifique com mais precisão o que você pretende fazer (digitação mais forte).
Isso tem vários benefícios para você:
Como o compilador sabe mais sobre o que você deseja fazer, ele permite que você omita muitos tipos de conversão, pois já sabe que o tipo será compatível.
Isso também fornece feedback anterior sobre a correção de seu programa. Coisas que anteriormente teriam falhado em tempo de execução (por exemplo, porque um objeto não pode ser lançado no tipo desejado), agora falham em tempo de compilação e você pode consertar o erro antes que seu departamento de teste arquive um relatório de bug críptico.
O compilador pode fazer mais otimizações, como evitar boxing, etc.
Algumas coisas para adicionar / expandir (falando do ponto de vista do .NET):
Os tipos genéricos permitem criar classes e interfaces baseadas em funções. Isso já foi dito em termos mais básicos, mas acho que você começa a projetar seu código com classes que são implementadas de uma maneira agnóstica de tipo - o que resulta em um código altamente reutilizável.
Argumentos genéricos sobre métodos podem fazer a mesma coisa, mas também ajudam a aplicar o princípio "Diga, não pergunte" ao casting, ou seja, "dê-me o que eu quero e, se não puder, diga por quê".
Eu os uso, por exemplo, em um GenericDao implementado com SpringORM e Hibernate que se parece com este
public abstract class GenericDaoHibernateImpl<T>
extends HibernateDaoSupport {
private Class<T> type;
public GenericDaoHibernateImpl(Class<T> clazz) {
type = clazz;
}
public void update(T object) {
getHibernateTemplate().update(object);
}
@SuppressWarnings("unchecked")
public Integer count() {
return ((Integer) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session session) {
// Code in Hibernate for getting the count
}
}));
}
.
.
.
}
Usando genéricos, minhas implementações deste DAOs forçam o desenvolvedor a transmiti-los apenas as entidades para as quais foram projetados, apenas criando uma subclasse do GenericDao
public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
public UserDaoHibernateImpl() {
super(User.class); // This is for giving Hibernate a .class
// work with, as generics disappear at runtime
}
// Entity specific methods here
}
Minha pequena estrutura é mais robusta (tem coisas como filtragem, carregamento lento, pesquisa). Eu apenas simplifiquei aqui para dar um exemplo
Eu, como Steve e você, disse no início "Muito confuso e complicado", mas agora vejo suas vantagens
Benefícios óbvios como "segurança de tipo" e "sem fundição" já foram mencionados, então talvez eu possa falar sobre alguns outros "benefícios" que espero ajudar.
Em primeiro lugar, os genéricos são um conceito independente da linguagem e, IMO, pode fazer mais sentido se você pensar em polimorfismo regular (tempo de execução) ao mesmo tempo.
Por exemplo, o polimorfismo como sabemos do design orientado a objetos tem uma noção de tempo de execução em que o objeto chamador é descoberto em tempo de execução conforme a execução do programa prossegue e o método relevante é chamado de acordo, dependendo do tipo de tempo de execução. Nos genéricos, a ideia é um pouco semelhante, mas tudo acontece em tempo de compilação. O que isso significa e como você o usa?
(Vamos ficar com métodos genéricos para mantê-lo compacto) Isso significa que você ainda pode ter o mesmo método em classes separadas (como você fez anteriormente em classes polimórficas), mas desta vez eles são gerados automaticamente pelo compilador dependendo dos tipos definidos em tempo de compilação. Você parametriza seus métodos de acordo com o tipo fornecido em tempo de compilação. Então, em vez de escrever os métodos do zero para cada tipo você tem, como faz no polimorfismo de tempo de execução (substituição de método), você deixa os compiladores fazerem o trabalho durante a compilação. Isso tem uma vantagem óbvia, pois você não precisa inferir todos os tipos possíveis que podem ser usados em seu sistema, o que o torna muito mais escalonável sem uma alteração de código.
As aulas funcionam praticamente da mesma maneira. Você parametriza o tipo e o código é gerado pelo compilador.
Depois de ter a ideia de "tempo de compilação", você pode fazer uso de tipos "limitados" e restringir o que pode ser passado como um tipo parametrizado por meio de classes / métodos. Portanto, você pode controlar o que deve ser transmitido, o que é uma coisa poderosa, especialmente se você tiver uma estrutura sendo consumida por outras pessoas.
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
Ninguém pode definir nada além de MyObject agora.
Além disso, você pode "impor" restrições de tipo aos argumentos de seu método, o que significa que você pode ter certeza de que ambos os argumentos de seu método dependem do mesmo tipo.
public <T extends MyObject> foo(T t1, T t2){
...
}
Espero que tudo isso faça sentido.
Certa vez, dei uma palestra sobre esse assunto. Você pode encontrar meus slides, código e gravação de áudio em http://www.adventuresinsoftware.com/generics/ .
Usar genéricos para coleções é simples e limpo. Mesmo se você apostar em todos os outros lugares, o ganho com as coleções é uma vitória para mim.
List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
stuff.do();
}
vs
List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
Stuff stuff = (Stuff)i.next();
stuff.do();
}
ou
List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
Stuff stuff = (Stuff)stuffList.get(i);
stuff.do();
}
Só isso já vale o "custo" marginal dos genéricos, e você não precisa ser um Guru genérico para usar isso e obter valor.
Os genéricos também oferecem a capacidade de criar objetos / métodos mais reutilizáveis e, ao mesmo tempo, fornecer suporte específico para o tipo. Você também ganha muito desempenho em alguns casos. Não sei as especificações completas do Java Generics, mas no .NET posso especificar restrições no parâmetro Type, como Implementa uma Interface, Construtor e Derivação.
Permitindo que os programadores implementem algoritmos genéricos - Usando genéricos, os programadores podem implementar algoritmos genéricos que funcionam em coleções de diferentes tipos, podem ser personalizados e são seguros para tipos e mais fáceis de ler.
Verificações de tipo mais fortes em tempo de compilação - Um compilador Java aplica a verificação de tipo forte ao código genérico e emite erros se o código violar a segurança de tipo. Corrigir erros de tempo de compilação é mais fácil do que corrigir erros de tempo de execução, que podem ser difíceis de encontrar.
Eliminação de moldes.