JavaRush /Blogue Java /Random-PT /Apagando tipos

Apagando tipos

Publicado no grupo Random-PT
Olá! Continuamos nossa série de palestras sobre genéricos. Anteriormente , descobrimos em termos gerais o que é e por que é necessário. Hoje falaremos sobre alguns dos recursos dos genéricos e veremos algumas armadilhas ao trabalhar com eles. Ir! Tipos de apagamento - 1Na última palestra falamos sobre a diferença entre Generic Types e Raw Types . Caso você tenha esquecido, Raw Type é uma classe genérica da qual seu tipo foi removido.
List list = new ArrayList();
Aqui está um exemplo. Aqui não especificamos que tipo de objetos serão colocados em nosso arquivo List. Se tentarmos criar um Liste adicionar alguns objetos a ele, veremos um aviso no IDEa:

“Unchecked call to add(E) as a member of raw type of java.util.List”.
Mas também falamos sobre o fato de que os genéricos apareceram apenas na versão da linguagem Java 5. Na época em que foi lançado, os programadores já haviam escrito muito código usando Raw Types, e para que não parasse de funcionar, a capacidade de criar e trabalhar com tipos brutos em Java foi preservado. No entanto, esse problema acabou sendo muito mais amplo. O código Java, como você sabe, é convertido em bytecode especial, que é então executado pela máquina virtual Java. E se durante o processo de tradução colocássemos informações sobre os tipos de parâmetros no bytecode, isso quebraria todo o código escrito anteriormente, porque antes do Java 5 não existiam tipos de parâmetros! Ao trabalhar com genéricos, há um recurso muito importante que você precisa lembrar. É chamado de apagamento de tipo. Sua essência reside no fato de que nenhuma informação sobre seu tipo de parâmetro é armazenada dentro da classe. Esta informação está disponível apenas na fase de compilação e é apagada (torna-se inacessível) em tempo de execução. Se você tentar colocar um objeto do tipo errado em seu arquivo List<String>, o compilador gerará um erro. Foi exatamente isso que os criadores da linguagem conseguiram ao criar genéricos - verificações na fase de compilação. Mas quando todo o código Java que você escreve se transformar em bytecode, não haverá informações sobre os tipos de parâmetros. Dentro do bytecode, sua lista de List<Cat>gatos não será diferente das List<String>strings. Nada no bytecode dirá que catsesta é uma lista de objetos Cat. As informações sobre isso serão apagadas durante a compilação, e apenas as informações que você possui em uma determinada lista em seu programa entrarão no código de bytes List<Object> cats. Vamos ver como isso funciona:
public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
Criamos nossa própria classe genérica TestClass. É bastante simples: essencialmente é uma pequena “coleção” de 2 objetos, que são colocados ali imediatamente quando o objeto é criado. Possui 2 objetos como campos T. Quando o método é executado, createAndAdd2Values()os dois objetos passados ​​​​devem ser convertidos Object aem Object bnosso tipo T, após o que serão adicionados ao objeto TestClass. No método main()que criamos TestClass<Integer>, ou seja, na qualidade Tteremos Integer. Mas, ao mesmo tempo, createAndAdd2Values()passamos um número Doublee um objeto para o método String. Você acha que nosso programa funcionará? Afinal, especificamos como parâmetro o tipo Integer, mas Stringcertamente não pode ser convertido em Integer! Vamos executar o método main()e verificar. Saída do console: 22.111 Test String Resultado inesperado! Por quê isso aconteceu? Precisamente por causa do apagamento do tipo. Durante a compilação do código, as informações sobre o tipo de parâmetro Integerdo nosso objeto TestClass<Integer> testforam apagadas. Ele se transformou em TestClass<Object> test. Nossos parâmetros foram transformados sem problemas Double( e não em , como esperávamos!) e foram adicionados silenciosamente ao . Aqui está outro exemplo simples, mas muito ilustrativo, de apagamento de tipo: StringObjectIntegerTestClass
import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
Saída do console: true true Parece que criamos coleções com três tipos de parâmetros diferentes - String, Integer, e a classe que criamos Cat. Mas durante a conversão para bytecode, todas as três listas se transformaram em List<Object>, então, quando executado, o programa nos informa que nos três casos estamos usando a mesma classe.

Apagamento de tipo ao trabalhar com arrays e genéricos

Há um ponto muito importante que deve ser entendido claramente ao trabalhar com arrays e genéricos (por exemplo, List). Também vale a pena considerar ao escolher uma estrutura de dados para o seu programa. Os genéricos estão sujeitos a apagamento de tipo. As informações sobre o tipo de parâmetro não estão disponíveis durante a execução do programa. Por outro lado, os arrays conhecem e podem usar informações sobre seu tipo de dados durante a execução do programa. Tentar colocar um valor do tipo errado em um array gerará uma exceção:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Saída do console:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Como existe uma diferença tão grande entre arrays e genéricos, eles podem ter problemas de compatibilidade. Primeiro de tudo, você não pode criar um array de objetos genéricos ou mesmo apenas um array digitado. Parece um pouco confuso? Vamos olhar mais de perto. Por exemplo, você não pode fazer nada disso em Java:
new List<T>[]
new List<String>[]
new T[]
Se tentarmos criar um array de listas List<String>, obteremos um erro genérico de compilação de criação de array:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Mas por que isso foi feito? Por que a criação de tais matrizes é proibida? Isso tudo é para garantir a segurança do tipo. Se o compilador nos permitisse criar tais arrays a partir de objetos genéricos, poderíamos ter muitos problemas. Aqui está um exemplo simples do livro “Effective Java” de Joshua Bloch:
public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
Vamos imaginar que a criação de array List<String>[] stringListsseria permitida e o compilador não reclamaria. Aqui está o que poderíamos fazer neste caso: Na linha 1, criamos um array de folhas List<String>[] stringLists. Nosso array contém um arquivo List<String>. Na linha 2 criamos uma lista de números List<Integer>. Na linha 3 atribuímos nosso array List<String>[]a uma variável Object[] objects. A linguagem Java permite fazer isso: Xvocê pode colocar objetos Xe objetos de todas as classes filhas em um array de objetos Х. Conseqüentemente, Objectsvocê pode colocar qualquer coisa no array. Na linha 4, substituímos o único elemento do array objects (List<String>)por uma lista List<Integer>. Como resultado, colocamos List<Integer>em nosso array, que era destinado apenas ao armazenamento List<String>! Encontraremos um erro somente quando o código atingir a linha 5. Uma exceção será lançada durante a execução do programa ClassCastException. Portanto, a proibição de criar tais arrays foi introduzida na linguagem Java - isso nos permite evitar tais situações.

Como posso ignorar o apagamento de tipo?

Bem, aprendemos sobre apagamento de tipo. Vamos tentar enganar o sistema! :) Tarefa: Temos uma classe genérica TestClass<T>. Precisamos criar nele um método createNewT()que irá criar e retornar um novo objeto do tipo Т. Mas isso é impossível de fazer, certo? Todas as informações sobre o tipo Тserão apagadas durante a compilação e, enquanto o programa estiver em execução, não poderemos descobrir que tipo de objeto precisamos criar. Na verdade, existe uma maneira complicada. Você provavelmente se lembra que existe uma classe em Java Class. Usando-o, podemos obter a classe de qualquer um dos nossos objetos:
public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
Saída do console:

class java.lang.Integer
class java.lang.String
Mas aqui está um recurso sobre o qual não falamos. Na documentação do Oracle você verá que Class é uma classe genérica! Tipos de apagamento - 3A documentação diz: “T é o tipo de classe modelada por este objeto Class.” Se traduzirmos isso da linguagem de documentação para a linguagem humana, isso significa que a classe de um objeto Integer.classnão é apenas Class, mas Class<Integer>. O tipo de um objeto string.classnão é apenas Class, Class<String>, etc. Se ainda não estiver claro, tente adicionar um parâmetro de tipo ao exemplo anterior:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
E agora, usando esse conhecimento, podemos contornar o apagamento de tipo e resolver nosso problema! Vamos tentar obter informações sobre o tipo de parâmetro. Seu papel será desempenhado pela turma MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
Veja como usamos nossa solução na prática:
public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
Saída do console:

Объект секретного класса успешно создан!
Simplesmente passamos o parâmetro de classe necessário para o construtor de nossa classe genérica:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Graças a isso, salvamos informações sobre o tipo de parâmetro e evitamos que sejam apagadas. Como resultado, conseguimos criar um objeto T! :) Isso conclui a palestra de hoje. O apagamento de tipo é sempre algo a se ter em mente ao trabalhar com genéricos. Isso não parece muito conveniente, mas você precisa entender que os genéricos não faziam parte da linguagem Java quando ela foi criada. Este é um recurso adicionado posteriormente que nos ajuda a criar coleções digitadas e detectar erros no estágio de compilação. Algumas outras linguagens onde os genéricos existem desde a versão 1 não possuem apagamento de tipo (por exemplo, C#). No entanto, ainda não terminamos de estudar os genéricos! Na próxima palestra você conhecerá vários outros recursos para trabalhar com eles. Enquanto isso, seria bom resolver alguns problemas! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION