O que é legal sobre os genéricos, por que usá-los?

87

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.

MrBoJangles
fonte
1
Duplicado de stackoverflow.com/questions/77632/… . Mas uma resposta simples, mesmo que as coleções não façam parte de sua API, não gosto de cast desnecessário, mesmo na implementação interna.
Matthew Flaschen
A pergunta é bastante semelhante, mas não acho que a resposta aceita responda a esta.
Tom Hawtin - tackline em
1
Verifique também stackoverflow.com/questions/520527
Clint Miller
4
@MatthewFlaschen estranho, sua duplicata direciona para esta questão ... falha na matriz!
jcollum
@jcollum, acho que a pergunta original sobre a qual postei esse comentário foi mesclada com esta pergunta.
Matthew Flaschen

Respostas:

126
  • Permite que você escreva código / use métodos de biblioteca que são seguros para tipos, ou seja, um List <string> é garantidamente uma lista de strings.
  • Como resultado do uso de genéricos, o compilador pode realizar verificações de tempo de compilação no código para segurança de tipo, ou seja, você está tentando colocar um int nessa lista de strings? Usar um ArrayList faria com que fosse um erro de tempo de execução menos transparente.
  • Mais rápido do que usar objetos, pois evita boxing / unboxing (onde .net tem que converter tipos de valor em tipos de referência ou vice-versa ) ou conversão de objetos para o tipo de referência necessário.
  • Permite que você escreva um código que é aplicável a muitos tipos com o mesmo comportamento subjacente, ou seja, um Dicionário <string, int> usa o mesmo código subjacente que um Dicionário <DateTime, double>; usando genéricos, a equipe do framework precisava escrever apenas um trecho de código para obter os dois resultados com as vantagens mencionadas.
ljs
fonte
9
Ah, muito bom, e eu gosto de listas com marcadores. Acho que esta é a resposta mais completa, porém sucinta até agora.
MrBoJangles
toda a explicação está no contexto de "coleções". estou procurando uma visão mais ampla. Alguma ideia?
jungle_mole
boa resposta, eu só quero adicionar 2 outras vantagens, a restrição genérica tem 2 benefícios além de 1- Você pode usar as propriedades do tipo restrito no tipo genérico (por exemplo, onde T: IComparable fornece o uso de CompareTo) 2- A pessoa que escreverá o código depois que você saberá o que fará
Hamit YILDIRIM
52

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.

James Schek
fonte
1
Eu não tenho certeza se entendi. Não estou sugerindo que você faça invólucros "MyObject", isso seria horrível. Estou sugerindo que, se sua coleção de objetos for uma coleção de Carros, você deve escrever um objeto Garagem. Não teria get (car), teria métodos como buyFuel (100), que compraria $ 100 de combustível e distribuiria entre os carros que mais precisassem. Estou falando sobre aulas de negócios reais, não apenas "invólucros". Por exemplo, você quase nunca deve pegar (carro) ou passar por cima deles fora da coleção - você não pede a um objeto por seus membros, em vez disso, você pede a ele para fazer uma operação para você.
Bill K
Mesmo dentro de sua garagem, você deve ter segurança de tipo. Ou você tem List <Cars>, ListOfCars ou você lança em todos os lugares. Não sinto necessidade de indicar o tipo mais do que os tempos mínimos necessários.
James Schek
Eu concordo que o tipo de segurança seria bom, mas é muito menos útil, uma vez que está confinado à classe de garagem. Ele é encapsulado e facilmente controlado, portanto, meu ponto é que ele oferece essa pequena vantagem ao custo de uma nova sintaxe. É como modificar a constituição dos EUA para tornar ilegal o sinal vermelho - sim, sempre deveria ser ilegal, mas isso justifica uma emenda? Eu também acho que incentiva as pessoas a NÃO usarem encapsulamento neste caso, o que na verdade é comparativamente prejudicial.
Bill K
Bill - você pode ter um ponto sobre não usar encapsulamento, mas o resto do seu argumento é uma comparação pobre. O custo de aprender a nova sintaxe é mínimo neste caso (compare com lambdas em C #); comparar isso com emendar a Constituição não faz sentido. A Constituição não tem a intenção de especificar o código de tráfego. É mais como mudar para uma versão impressa com Serif-Font em uma cartolina em vez de uma cópia escrita à mão em um pergaminho. Tem o mesmo significado, mas é muito mais claro e fácil de ler.
James Schek de
O exemplo torna o conceito mais prático. Obrigado por isso.
RayLoveless
22

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 ClassCastExceptions 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 HashMapem 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 Sete 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 Integercom o valor de 10. (E o mesmo para 42). Então isso Integeré jogado no Setque é conhecido por conter Integers. Tentar lançar um Stringcausaria 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 Setcontendo Integers são usados ​​em um loop for-each. Cada elemento é declarado como um inte 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 Integers 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 Setexemplo, 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 Setexemplo 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 addinserido no conjunto.

set.add(10);
set.add(42);

Nas linhas acima, o autoboxing está em jogo - o intvalor primitivo 10e 42está sendo autoboxing em Integerobjetos, que estão sendo adicionados ao Set. No entanto, lembre-se de que os Integerobjetos estão sendo tratados como Objects, pois não há informações de tipo para ajudar o compilador a saber que tipo ele Setdeve esperar.

for (Object o : set) {

Essa é a parte crucial. O motivo pelo qual o loop for-each funciona é porque o Setimplementa a Iterableinterface, que retorna uma Iteratorinformação do tipo with, se presente. ( Iterator<T>isto é.)

No entanto, uma vez que não existe informação de tipo, o Setretornará uma Iteratorque irá retornar os valores no Setcomo Objects, e é por isso que o elemento a ser recuperado na para-cada ciclo tem de ser do tipo Object.

Agora que o Objectfoi recuperado de Set, ele precisa ser convertido em um Integermanualmente para realizar a adição:

  total += (Integer)o;

Aqui, um typecast é executado de um Objectpara 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á ClassCastExceptionesperando para acontecer, mas estou divagando ...)

O Integeragora está desempacotado em um inte tem permissão para realizar a adição na intvariá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.

coobird
fonte
A integração com o loop for aprimorado é muito bom (embora eu ache que o maldito loop está quebrado porque não posso usar exatamente a mesma sintaxe com uma coleção não genérica - deve apenas usar o cast / autobox para int, tem todas as informações de que precisa!), mas você está certo, a ajuda no IDE provavelmente é boa. Ainda não sei se é o suficiente para justificar a sintaxe adicional, mas pelo menos isso é algo de que posso me beneficiar (o que responde à minha pergunta).
Bill K
@Bill K: Eu adicionei um exemplo de uso de recursos Java 5 sem o uso de genéricos.
coobird
Esse é um bom ponto, eu não tinha usado isso (eu mencionei que estou preso, no 1.4 e no início, há anos). Eu concordo que há vantagens, mas as conclusões que estou chegando são que A) eles tendem a ser bastante vantajosos para pessoas que não usam OO corretamente e são menos úteis para aqueles que usam, e B) As vantagens para você ' Listamos são vantagens, mas dificilmente valem o suficiente para justificar até mesmo uma pequena mudança de sintaxe, muito menos as grandes mudanças necessárias para realmente aproveitar os genéricos implementados em Java. Mas pelo menos existem vantagens.
Bill K
15

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 NullPointerExceptionque 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.

Tom Hawtin - tackline
fonte
6
Não é apenas documentado (o que por si só é uma grande vitória), mas também é reforçado pelo compilador.
James Schek
Bom ponto, mas isso também não é conseguido encerrando a coleção em uma classe que define "uma coleção desse tipo de objeto"?
Bill K
1
James: Sim, esse é o ponto de estar documentado em código .
Tom Hawtin - tackline
Eu não acho que conheço boas bibliotecas que usam coleções para "Algoritmos" (possivelmente implicando que você está falando sobre "Funções", o que me leva a acreditar que elas deveriam ser um método no objeto da coleção). Acho que o JDK quase sempre extrai um array bom e limpo que é uma cópia dos dados para que não possa ser alterado - pelo menos na maioria das vezes.
Bill K
Além disso, ter um objeto descrevendo exatamente como a coleção é usada e limitando seu uso não é uma forma MUITO melhor de documentação no código? Acho as informações fornecidas em genéricos extremamente redundantes em um bom código.
Bill K
11

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 Listcujos elementos são Ts . 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, Ae B, há uma função (chamada f) que recebe um Ae retorna um B. Quando você implementa esta interface, Ae Bpode ser qualquer tipo que você quiser, desde que você forneça uma função fque 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 extendspalavra - 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 Widgetprorrogado por FooWidget, BarWidgete BazWidget, com os genéricos você pode ter uma única classe genérica Widget<A>que leva um Foo, Barou Bazem seu construtor para lhe dar Widget<Foo>, Widget<Bar>e Widget<Baz>.

Apocalisp
fonte
Seria um bom ponto sobre a subclasse se Java não tivesse interfaces. Não seria correto ter FooWidget, BarWidtet e BazWidget implementando Widget? Eu posso estar errado sobre isso ... Mas este é um ponto muito bom se eu estiver errado.
Bill K
Por que você precisa de uma classe genérica dinâmica para calcular os valores de hash? Não seria uma função estática com parâmetros apropriados para o trabalho de forma mais eficiente?
Ant_222
8

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.

Darren Kopp
fonte
Essa é a essência, não é? Agradável.
MrBoJangles
Bem, não inteiramente; eles são úteis para segurança de tipos + evitando casts para tipos de referências também sem encaixar / desembalar.
ljs
Portanto, acho que a pergunta a seguir é: "Por que eu iria querer usar um ArrayList?" E, arriscando uma resposta, eu diria que ArrayLists está bem, contanto que você não espere encaixotar e desencaixotar seus valores muito. Comentários?
MrBoJangles
Não, eles estão extintos em .net 2; útil apenas para compatibilidade com versões anteriores. ArrayLists são mais lentos, não são seguros para o tipo e exigem boxing / unboxing ou casting.
ljs
Se você não está armazenando tipos de valores (por exemplo, ints ou structs), não há boxing ao usar ArrayList - as classes já são 'objeto'. Usar List <T> ainda é muito melhor por causa do tipo de segurança, e se você realmente deseja armazenar objetos, usar List <object> é melhor.
Wilka,
6

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.

Dean Poulin
fonte
5

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).

Tom Kidd
fonte
5
  • 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?


1
para ser preciso quanto a evitar a conversão: a conversão ocorre, de acordo com a documentação da Sun, mas é feita pelo compilador. Você apenas evita escrever conversões em seu código.
Demi

Talvez eu pareça estar preso em uma longa série de empresas que não querem ir acima de 1,4, então, quem sabe, posso me aposentar antes de chegar a 5. Também tenho pensado recentemente que bifurcar um "SimpleJava" pode ser um conceito interessante --Java vai continuar adicionando recursos e para muito do código que vi, não será uma coisa saudável. Já lido com pessoas que não conseguem codificar um loop - odiaria vê-los tentando injetar genéricos em seus loops desenrolados inutilmente.
Bill K

@Bill K: Poucas pessoas precisam usar todo o poder dos genéricos. Isso só é necessário quando você está escrevendo uma classe que é genérica.
Eddie

5

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();
}

Suponho que você respondeu antes de ler a pergunta. Esses são os dois casos que realmente não me ajudam muito (descritos na pergunta). Existem outros casos em que faz algo útil?
Bill K

4

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 Listde Strings, não uma Listdas coisas que espero que sejam strings!)?

É por isso que você deve querem usar os genéricos (ou algo melhor).


Nunca precisei copiar / colar e reutilizar para esse tipo de coisa (não tenho certeza de como isso aconteceria?) Você implementa uma interface que atende às suas necessidades e usa essa interface. O raro caso em que você não pode fazer isso é quando está escrevendo um utilitário puro (como as coleções) e, para eles (como descrevi na pergunta), nunca foi realmente um problema. Eu comprometo uma digitação forte - assim como você precisa se usar reflexão. Você se recusa absolutamente a usar reflexão porque não pode ter uma digitação forte? (Se eu tiver que usar reflexão, simplesmente encapsulo-a como uma coleção).
Bill K

3

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>>).


Não acho que isso seja verdade sobre exceções. Isso está me fazendo pensar, mas tenho 95% de certeza de que você obteria exatamente os mesmos resultados sem o uso de genéricos (e um código mais claro). As informações de tipo são parte do objeto, não para onde você as lança. Estou tentado a tentar - talvez eu faça amanhã no trabalho e retorno para você .. Vou te dizer uma coisa, vou tentar e se você estiver certo, vou passar por sua lista de postagens e vote em 5 de suas postagens :) Se não, você pode querer considerar que a simples disponibilidade de genéricos fez com que complicasse seu código sem nenhuma vantagem.
Bill K

É verdade que isso adiciona complexidade extra. No entanto, acho que tem alguma utilidade. O problema é que você não pode lançar um Throwabletexto 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?
jigawot

Oh, entendi. Portanto, evita que o elenco saia ... bom ponto. Eu te devo 5.
Bill K

2

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.


Se pudesse escolher, prefiro não escalar do que escalar.
MrBoJangles

A tipagem forte é facilmente o melhor aspecto do imho genérico, especialmente dada a verificação de tipo em tempo de compilação que permite.
ljs

2

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.


2

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.


2

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] "


Nunca encontrei um caso em que a digitação genérica de coleções pudesse ajudar meu código. Minhas coleções têm um escopo restrito. É por isso que formulei "Isso pode me ajudar?". Como uma pergunta lateral, você está dizendo que antes de começar a usar os genéricos isso acontecia com você ocasionalmente? (colocando o tipo de objeto errado em sua coleção). Parece-me uma solução sem problemas reais, mas talvez eu apenas tenha sorte.
Bill K

@Bill K: se o código for rigidamente controlado (ou seja, usado apenas por você), talvez você nunca considere isso um problema. Isso é ótimo! Os maiores riscos estão em grandes projetos nos quais várias pessoas trabalham, IMO. É uma rede de segurança e uma "melhor prática".
Demi

@Bill K: Aqui está um exemplo. Digamos que você tenha um método que retorna um ArrayList. Digamos que ArrayList não seja uma coleção genérica. O código de alguém pega este ArrayList e tenta realizar alguma operação em seus itens. O único problema é que você preencheu o ArrayList com BillTypes e o consumidor está tentando operar em ConsumerTypes. Isso compila, mas explode durante o tempo de execução. Se você usar coleções genéricas, terá erros do compilador, que são muito mais fáceis de lidar.
Demi

@Bill K: Trabalhei com pessoas com diversos níveis de habilidade. Não tenho o luxo de escolher com quem compartilho um projeto. Assim, a segurança de tipo é muito importante para mim. Sim, encontrei (e corrigi) bugs convertendo o código para usar genéricos.
Eddie

1

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.


1

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.


1

Se sua coleção contém tipos de valor, eles não precisam encaixotar / desencaixotar para objetos quando inseridos na coleção, portanto, seu desempenho aumenta drasticamente. Complementos legais como resharper podem gerar mais código para você, como loops foreach.


1

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.


1

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.


Segurança de tipo, uma discussão em si. Talvez alguém devesse fazer uma pergunta sobre isso.
MrBoJangles,

Sim, mas o que você está insinuando? O ponto principal do tipo de segurança?
Vin

1

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.


1

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ê".


1

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


1

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.



0

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.


1
Sem os genéricos, você pode fazer: List stuffList = getStuff (); for (Object stuff: stuffList) {((Stuff) stuff) .doStuff (); }, o que não é muito diferente.
Tom Hawtin - tackline

fonte
Sem genéricos, faço stuffList.doAll (); Eu disse que sempre tenho uma classe de wrapper que é exatamente o lugar para códigos como esse. Caso contrário, como você evita copiar este loop em outro lugar? Onde você o coloca para reutilização? A classe wrapper provavelmente terá um loop de algum tipo, mas nessa classe, uma vez que essa classe é sobre "As coisas", usar um loop for fundido como Tom sugerido acima é bastante claro / autodocumentado.
Bill K
@Jherico, isso é muito interessante. Eu me pergunto se há uma razão para você se sentir assim, e se é isso que eu não estou entendendo - que há algo na natureza das pessoas que acha qualquer elenco por qualquer motivo de alguma forma obsceno e está disposto a ir a comprimentos não naturais (como adicionar uma sintaxe muito mais complexa) para evitá-lo. Por que "se você tiver que lançar algo, você já perdeu"?
Bill K
@Jherico: de acordo com a documentação da Sun, os genéricos fornecem ao compilador o conhecimento de para onde lançar objetos. Visto que o compilador faz a conversão para genéricos, em vez do usuário, isso muda sua declaração?
Demi
Converti o código anterior ao Java 1.5 para genérico e, no processo, encontrei bugs em que o tipo de objeto errado foi colocado em uma coleção. Eu também caio no campo "se você tiver que lançar, você perdeu", porque se você tiver que lançar manualmente, você corre o risco de uma ClassCastException. Com o Generics, o compilador faz isso de forma invisível para você, mas a chance de uma ClassCastException é bem reduzida devido à segurança de tipo. Sim, sem Genéricos você pode usar Object, mas isso é um cheiro de código terrível para mim, o mesmo que "lança Exceção".
Eddie
0

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.

Eric Schneider
fonte
0
  1. 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.

  2. 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.

  3. Eliminação de moldes.

Cinza
fonte