JavaRush /Blogue Java /Random-PT /Genéricos para gatos
Viacheslav
Nível 3

Genéricos para gatos

Publicado no grupo Random-PT
Genéricos para gatos - 1

Introdução

Hoje é um ótimo dia para relembrar o que sabemos sobre Java. De acordo com o documento mais importante, ou seja, Especificação de Linguagem Java (JLS - Java Language Specifiaction), Java é uma linguagem fortemente tipada, conforme descrito no capítulo " Capítulo 4. Tipos, Valores e Variáveis ". O que isto significa? Digamos que temos um método principal:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
A digitação forte garante que quando este código for compilado, o compilador verificará se especificamos o tipo da variável de texto como String, então não estamos tentando usá-lo em nenhum lugar como uma variável de outro tipo (por exemplo, como um número inteiro) . Por exemplo, se tentarmos salvar um valor em vez de texto 2L(ou seja, longo em vez de String), obteremos um erro em tempo de compilação:

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
Aqueles. A digitação forte permite garantir que as operações em objetos sejam executadas somente quando essas operações forem legais para esses objetos. Isso também é chamado de segurança de tipo. Conforme declarado no JLS, existem duas categorias de tipos em Java: tipos primitivos e tipos de referência. Você pode se lembrar dos tipos primitivos no artigo de revisão: “ Tipos primitivos em Java: eles não são tão primitivos ”. Os tipos de referência podem ser representados por uma classe, interface ou array. E hoje estaremos interessados ​​em tipos de referência. E vamos começar com matrizes:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
Este código é executado sem erros. Como sabemos (por exemplo, em " Tutorial Oracle Java: Arrays "), um array é um contêiner que armazena dados de apenas um tipo. Neste caso - apenas linhas. Vamos tentar adicionar long ao array em vez de String:
text[1] = 4L;
Vamos executar este código (por exemplo, no Repl.it Online Java Compiler ) e obter um erro:
error: incompatible types: long cannot be converted to String
O array e a segurança de tipo da linguagem não nos permitiam salvar em um array o que não se enquadrava no tipo. Esta é uma manifestação de segurança de tipo. Disseram-nos: “Corrija o erro, mas até então não irei compilar o código”. E o mais importante é que isso acontece no momento da compilação, e não no lançamento do programa. Ou seja, vemos erros imediatamente, e não “algum dia”. E já que lembramos dos arrays, vamos lembrar também do Java Collections Framework . Tínhamos estruturas diferentes lá. Por exemplo, listas. Vamos reescrever o exemplo:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List text = new ArrayList(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(0);
  }
}
Ao compilá-lo, receberemos testum erro na linha de inicialização da variável:
incompatible types: Object cannot be converted to String
No nosso caso, List pode armazenar qualquer objeto (ou seja, um objeto do tipo Object). Portanto, o compilador diz que não pode assumir tal peso de responsabilidade. Portanto, precisamos especificar explicitamente o tipo que obteremos da lista:
String test = (String) text.get(0);
Essa indicação é chamada de conversão de tipo ou conversão de tipo. E tudo funcionará bem agora até tentarmos obter o elemento no índice 1, porque é do tipo Longo. E obteremos um erro justo, mas já durante a execução do programa (em Runtime):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
Como podemos ver, existem várias desvantagens importantes aqui. Primeiramente, somos forçados a “converter” o valor obtido da lista para a classe String. Concordo, isso é feio. Em segundo lugar, em caso de erro, só o veremos quando o programa for executado. Se nosso código fosse mais complexo, talvez não detectássemos esse erro imediatamente. E os desenvolvedores começaram a pensar em como tornar o trabalho nessas situações mais fácil e o código mais claro. E eles nasceram - Genéricos.
Genéricos para gatos - 2

Genéricos

Então, genéricos. O que é? Um genérico é uma forma especial de descrever os tipos usados, que o compilador de código pode usar em seu trabalho para garantir a segurança do tipo. Parece algo assim:
Genéricos para gatos - 3
Aqui está um breve exemplo e explicação:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> text = new ArrayList<String>(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(1);
  }
}
Neste exemplo, dizemos que não temos apenas List, mas List, que SÓ funciona com objetos do tipo String. E nenhum outro. O que está indicado entre colchetes, podemos armazená-lo. Esses "colchetes" são chamados de "colchetes angulares", ou seja, colchetes angulares. O compilador irá gentilmente verificar se cometemos algum erro ao trabalhar com uma lista de strings (a lista é chamada de texto). O compilador verá que estamos tentando descaradamente colocar Long na lista String. E na hora da compilação vai dar um erro:
error: no suitable method found for add(long)
Você deve ter se lembrado que String é descendente de CharSequence. E decida fazer algo como:
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
Mas isso não é possível e teremos o erro: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> Parece estranho, porque. a linha CharSequence sec = "test";não contém erros. Vamos descobrir. Dizem sobre esse comportamento: “Os genéricos são invariantes”. O que é um “invariante”? Gosto de como é dito sobre isso na Wikipedia no artigo “ Covariância e contravariância ”:
Genéricos para gatos - 4
Assim, Invariância é a ausência de herança entre tipos derivados. Se Cat for um subtipo de Animals, então Set<Cats> não é um subtipo de Set<Animals> e Set<Animals> não é um subtipo de Set<Cats>. Aliás, vale dizer que a partir do Java SE 7 surgiu o chamado “ Operador Diamante ”. Porque os dois colchetes angulares <> são como um diamante. Isso nos permite usar genéricos como este:
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
Com base neste código, o compilador entende que se indicamos no lado esquerdo que ele Listconterá objetos do tipo String, então no lado direito queremos dizer que queremos salvar linesum novo ArrayList em uma variável, que também armazenará um objeto do tipo especificado no lado esquerdo. Portanto, o compilador do lado esquerdo entende ou infere o tipo do lado direito. É por isso que esse comportamento é chamado de inferência de tipo ou "Inferência de tipo" em inglês. Outra coisa interessante que vale a pena notar são os RAW Types ou “tipos brutos”. Porque Os genéricos nem sempre existiram e o Java tenta manter a compatibilidade com versões anteriores sempre que possível; então, os genéricos são forçados a trabalhar de alguma forma com código onde nenhum genérico é especificado. Vejamos um exemplo:
List<CharSequence> lines = new ArrayList<String>();
Como lembramos, tal linha não será compilada devido à invariância dos genéricos.
List<Object> lines = new ArrayList<String>();
E este também não compila, pelo mesmo motivo.
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
Essas linhas serão compiladas e funcionarão. É neles que os Raw Types são usados, ou seja, tipos não especificados. Mais uma vez, vale ressaltar que Raw Types NÃO DEVEM ser usados ​​em código moderno.
Genéricos para gatos – 5

Aulas digitadas

Então, aulas digitadas. Vamos ver como podemos escrever nossa própria classe digitada. Por exemplo, temos uma hierarquia de classes:
public static abstract class Animal {
  public abstract void voice();
}

public static class Cat extends Animal {
  public void voice(){
    System.out.println("Meow meow");
  }
}

public static class Dog extends Animal {
  public void voice(){
    System.out.println("Woof woof");
  }
}
Queremos criar uma classe que implemente um contêiner para animais. Seria possível escrever uma classe que contivesse qualquer arquivo Animal. Isso é simples, compreensível, MAS... misturar cachorro e gato é ruim, eles não são amigos. Além disso, se alguém receber tal recipiente, ele pode erroneamente jogar gatos do recipiente em uma matilha de cães... e isso não levará a nada de bom. E aqui os genéricos nos ajudarão. Por exemplo, vamos escrever a implementação assim:
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
Nossa classe trabalhará com objetos do tipo especificado por um genérico chamado T. Este é um tipo de alias. Porque O genérico é especificado no nome da classe, então o receberemos ao declarar a classe:
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
Como podemos ver, indicamos que temos Box, que só funciona com Cat. O compilador percebeu que catBoxem vez de um genérico, Tvocê precisa substituir o tipo Catsempre que o nome do genérico for especificado T:
Genéricos para gatos – 6
Aqueles. é graças ao Box<Cat>compilador que ele entende o que slotsrealmente deveria ser List<Cat>. Pois Box<Dog>dentro haverá slots, contendo List<Dog>. Pode haver vários genéricos em uma declaração de tipo, por exemplo:
public static class Box<T, V> {
O nome do genérico pode ser qualquer coisa, embora seja recomendado seguir algumas regras tácitas - "Convenções de nomenclatura de parâmetro de tipo": Tipo de elemento - E, tipo de chave - K, tipo de número - N, T - para tipo, V - para tipo de valor. A propósito, lembre-se que dissemos que os genéricos são invariantes, ou seja, não preserve a hierarquia de herança. Na verdade, podemos influenciar isso. Ou seja, temos a oportunidade de fazer genéricos COvariant, ou seja, mantendo as heranças na mesma ordem. Este comportamento é chamado de "Tipo Limitado", ou seja, tipos limitados. Por exemplo, nossa classe Boxpoderia conter todos os animais, então declararíamos um genérico como este:
public static class Box<T extends Animal> {
Ou seja, definimos o limite superior para a classe Animal. Também podemos especificar vários tipos após a palavra-chave extends. Isso significará que o tipo com o qual trabalharemos deve ser descendente de alguma classe e ao mesmo tempo implementar alguma interface. Por exemplo:
public static class Box<T extends Animal & Comparable> {
Nesse caso, se tentarmos colocar Boxalgo que não seja um herdeiro Animale não implemente Comparable, durante a compilação receberemos um erro:
error: type argument Cat is not within bounds of type-variable T
Genéricos para gatos – 7

Métodos de digitação

Os genéricos são usados ​​não apenas em tipos, mas também em métodos individuais. A aplicação dos métodos pode ser conferida no tutorial oficial: “ Métodos Genéricos ”.

Fundo:

Genéricos para gatos – 8
Vejamos esta foto. Como você pode ver, o compilador analisa a assinatura do método e vê que estamos pegando alguma classe indefinida como entrada. Não determina pela assinatura que estamos retornando algum tipo de objeto, ou seja, Objeto. Portanto, se quisermos criar, digamos, um ArrayList, precisamos fazer o seguinte:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
Você deve escrever explicitamente que a saída será um ArrayList, o que é feio e aumenta a chance de cometer um erro. Por exemplo, podemos escrever essas bobagens e ele irá compilar:
ArrayList object = (ArrayList) createObject(LinkedList.class);
Podemos ajudar o compilador? Sim, os genéricos nos permitem fazer isso. Vejamos o mesmo exemplo:
Genéricos para gatos – 9
Então, podemos criar um objeto simplesmente assim:
ArrayList<String> object = createObject(ArrayList.class);
Genéricos para gatos – 10

Curinga

De acordo com o Tutorial sobre Genéricos da Oracle, especificamente a seção “ Wildcards ”, podemos descrever um “tipo desconhecido” com um ponto de interrogação. Wildcard é uma ferramenta útil para mitigar algumas das limitações dos genéricos. Por exemplo, como discutimos anteriormente, os genéricos são invariantes. Isso significa que embora todas as classes sejam descendentes (subtipos) do tipo Object, List<любой тип>não é um subtipo List<Object>. MAS, List<любой тип>é um subtipo List<?>. Então podemos escrever o seguinte código:
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
Assim como os genéricos regulares (ou seja, sem o uso de curingas), os genéricos com curingas podem ser limitados. O curinga com limite superior parece familiar:
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
Mas você também pode limitá-lo pelo curinga Limite inferior:
public static void printCatList(List<? super Cat> list) {
Assim, o método passará a aceitar todos os gatos, bem como os superiores na hierarquia (até Object).
Genéricos para gatos – 11

Tipo de apagamento

Falando em genéricos, vale a pena conhecer o “Type Erasing”. Na verdade, o apagamento de tipo tem a ver com o fato de que os genéricos são informações para o compilador. Durante a execução do programa não há mais informações sobre genéricos, isso é chamado de “apagamento”. Esse apagamento faz com que o tipo genérico seja substituído pelo tipo específico. Se o genérico não tiver limite, o tipo Object será substituído. Se a borda foi especificada (por exemplo <T extends Comparable>), ela será substituída. Aqui está um exemplo do Tutorial da Oracle: " Apagamento de Tipos Genéricos ":
Genéricos para gatos – 12
Como foi dito acima, neste exemplo o genérico Té apagado até a borda, ou seja, antes Comparable.
Genéricos para gatos – 13

Conclusão

Os genéricos são um tema muito interessante. Espero que este tema seja do seu interesse. Resumindo, os genéricos são uma ótima ferramenta que os desenvolvedores receberam para fornecer informações adicionais ao compilador para garantir a segurança do tipo, por um lado, e a flexibilidade, por outro. E se você tiver interesse, sugiro que dê uma olhada nos recursos que gostei: #Viacheslav
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION