Olá! Hoje falaremos sobre genéricos. Devo dizer que você aprenderá muitas coisas novas! Não só isso, mas também as próximas palestras serão dedicadas aos genéricos. Portanto, se este tema é do seu interesse, você tem sorte: hoje você aprenderá muito sobre as características dos genéricos. Bem, se não, acalme-se e relaxe! :) Este é um tema muito importante e você precisa conhecê-lo. Vamos começar com um simples: “o quê” e “por quê”. O que são genéricos? Genéricos são tipos com um parâmetro. Ao criar um genérico, você especifica não apenas seu tipo, mas também o tipo de dados com os quais ele deve trabalhar. Acho que o exemplo mais óbvio já veio à sua mente - este é ArrayList! Veja como normalmente o criamos no programa:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> myList1 = new ArrayList<>();
myList1.add("Test String 1");
myList1.add("Test String 2");
}
}
Como você pode imaginar, a peculiaridade da lista é que não será possível “enfiar” tudo nela: ela funciona exclusivamente com objetos String
. Agora vamos fazer uma breve excursão pela história do Java e tentar responder à pergunta: “por quê?” Para fazer isso, nós mesmos escreveremos uma versão simplificada da classe ArrayList. Nossa lista só pode adicionar dados ao array interno e receber estes dados:
public class MyListClass {
private Object[] data;
private int count;
public MyListClass() {
this.data = new Object[10];
this.count = 0;
}
public void add(Object o) {
this.data[count] = o;
count++;
}
public Object[] getData() {
return data;
}
}
Digamos que queremos que nossa lista armazene apenas números Integer
. Não temos genéricos. Não podemos especificar explicitamente a instância o de check Integer
no arquivo add()
. Então toda a nossa classe será adequada apenas para Integer
, e teremos que escrever a mesma classe para todos os tipos de dados existentes no mundo! Decidimos contar com nossos programadores e simplesmente deixar um comentário no código para que não acrescentem nada desnecessário:
//use it ONLY with Integer data type
public void add(Object o) {
this.data[count] = o;
count++;
}
Um dos programadores não percebeu este comentário e inadvertidamente tentou colocar números misturados com strings na lista e depois calcular sua soma:
public class Main {
public static void main(String[] args) {
MyListClass list = new MyListClass();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
System.out.println(sum1);
Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
System.out.println(sum2);
}
}
Saída do console: 300 Exceção no thread "main" java.lang.ClassCastException: java.lang.String não pode ser convertido em java.lang.Integer em Main.main(Main.java:14) Qual é o pior nesta situação? Longe de ser a desatenção de um programador. O pior é que o código errado acabou em um lugar importante do nosso programa e foi compilado com sucesso . Agora veremos o erro não na fase de codificação, mas apenas na fase de teste (e isso é na melhor das hipóteses!). Corrigir bugs posteriormente no desenvolvimento custa muito mais – tanto dinheiro quanto tempo. Esta é precisamente a vantagem dos genéricos: uma classe genérica permitirá que um programador azarado detecte um erro imediatamente. O código simplesmente não compila!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> myList1 = new ArrayList<>();
myList1.add(100);
myList1.add(100);
myList1.add("Lolkek");//error!
myList1.add("Shalala");//error!
}
}
O programador imediatamente “cairá em si” e se corrigirá instantaneamente. Aliás, não precisamos criar nossa própria classe List
para ver esse tipo de erro. Simplesmente remova os colchetes de tipo ( <Integer>
) de um ArrayList normal!
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add(100);
list.add(200);
list.add("Lolkek");
list.add("Shalala");
System.out.println((Integer) list.get(0) + (Integer) list.get(1));
System.out.println((Integer) list.get(2) + (Integer) list.get(3));
}
}
Saída do console: 300 Exceção no thread "main" java.lang.ClassCastException: java.lang.String não pode ser convertido para java.lang.Integer em Main.main(Main.java:16) Ou seja, mesmo usando ferramentas “nativas” Java, você pode cometer esse erro e criar uma coleção insegura. No entanto, se colarmos este código no IDEa, veremos um aviso: “ Chamada não verificada para add(E) como membro do tipo bruto de java.util.List ” Isso nos diz que algo pode dar errado ao adicionar um elemento a um coleção sem genéricos não é assim. Mas o que significa a frase “tipo bruto”? A tradução literal será bastante precisa - “ tipo bruto ” ou “ tipo sujo ”. Raw type
é uma classe genérica da qual seu tipo foi removido. Em outras palavras, List myList1
isso é Raw type
. O oposto raw type
é generic type
uma classe genérica (também conhecida como class parameterized type
), criada corretamente, com uma especificação de tipo. Por exemplo, List<String> myList1
. Você pode ter uma pergunta: por que é permitido usar raw types
? A razão é simples. Os criadores do Java deixaram o suporte na linguagem raw types
para não criar problemas de compatibilidade. Quando o Java 5.0 foi lançado (os genéricos apareceram pela primeira vez nesta versão), muito código já havia sido escrito usando raw types
. Portanto, essa possibilidade ainda existe hoje. Já mencionamos o livro clássico de Joshua Bloch, “Java Eficaz”, mais de uma vez em palestras. Como um dos criadores da linguagem, ele não ignorou o tema do uso raw types
e no livro generic types
. O capítulo 23 deste livro tem um título muito eloqüente: “Não use tipos brutos em código novo”. Isso é algo que você precisa lembrar. Ao usar classes genéricas, nunca as transforme generic type
em arquivos raw type
.
Métodos digitados
Java permite digitar métodos individuais, criando os chamados métodos genéricos. Por que esses métodos são convenientes? Em primeiro lugar, porque permitem trabalhar com diversos tipos de parâmetros. Se a mesma lógica puder ser aplicada com segurança a tipos diferentes, um método genérico será uma ótima solução. Vejamos um exemplo. Digamos que temos algum tipo de listamyList1
. Queremos remover todos os valores dele e preencher todos os espaços livres com um novo valor. Esta é a aparência de nossa classe com um método genérico:
public class TestClass {
public static <T> void fill(List<T> list, T val) {
for (int i = 0; i < list.size(); i++)
list.set(i, val);
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("Старая строка 1");
strings.add("Старая строка 2");
strings.add("Старая строка 3");
fill(strings, "Новая строка");
System.out.println(strings);
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
fill(numbers, 888);
System.out.println(numbers);
}
}
Preste atenção na sintaxe, parece um pouco incomum:
public static <T> void fill(List<T> list, T val)
O tipo de retorno é precedido por <T>, que indica um método genérico. Neste caso, o método recebe 2 parâmetros como entrada: uma lista de objetos T e outro objeto separado T. Ao usar <T>, a digitação do método é alcançada: não podemos passar ali uma lista de strings e um número. Uma lista de strings e uma string, uma lista de números e um número, uma lista de nossos objetos Cat
e outro objeto Cat
- essa é a única maneira. O método main()
demonstra claramente que fill()
funciona facilmente com diferentes tipos de dados. Primeiro, ele recebe como entrada uma lista de strings e uma string e, em seguida, uma lista de números e um número. Saída do console: [Newline, Newline, Newline] [888, 888, 888] Imagine se fill()
precisássemos de lógica de método para 30 classes diferentes e não tivéssemos métodos genéricos. Seríamos forçados a escrever o mesmo método 30 vezes, apenas para tipos de dados diferentes! Mas graças aos métodos genéricos, podemos reutilizar o nosso código! :)
Aulas digitadas
Você pode não apenas usar as classes genéricas fornecidas em Java, mas também criar as suas próprias! Aqui está um exemplo simples:public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Старая строка");
System.out.println(stringBox.get());
stringBox.set("Новая строка");
System.out.println(stringBox.get());
stringBox.set(12345);//ошибка компиляции!
}
}
Nossa classe Box<T>
(“caixa”) está digitada. Tendo atribuído um tipo de dados ( ) a ele durante a criação <T>
, não poderemos mais colocar objetos de outros tipos nele. Isso pode ser visto no exemplo. Ao criar, especificamos que nosso objeto funcionará com strings:
Box<String> stringBox = new Box<>();
E quando na última linha do código tentamos colocar o número 12345 dentro da caixa, obtemos um erro de compilação! Simples assim, criamos nossa própria classe genérica! :) Isso conclui nossa palestra de hoje. Mas não estamos dizendo adeus aos genéricos! Nas próximas palestras falaremos sobre recursos mais avançados, então não diga adeus! ) Boa sorte em seus estudos! :)
GO TO FULL VERSION