1. Todas as classes herdamObject

Todas as classes em Java herdam implicitamente a Objectclasse.

Analisaremos o que é herança e como ela funciona em Java na missão Java Core. Por enquanto, vamos considerar um fato simples que decorre disso:

Um objeto de qualquer classe pode ser atribuído a uma Objectvariável. Exemplo:

Código Observação
Object o = new Scanner(System.in);
A ovariável armazena uma referência a um Scannerobjeto
Object o = new String();
A ovariável armazena uma referência a um Stringobjeto
Object o = new Integer(15);
A ovariável armazena uma referência a um Integerobjeto
Object o = "Hello";
A ovariável armazena uma referência a um Stringobjeto

É aqui que as boas notícias terminam. O compilador não rastreia o tipo original de objeto salvo em uma Objectvariável, portanto, você não poderá chamar métodos no objeto salvo que não sejam os métodos da Objectclasse.

Se você precisar chamar os métodos associados ao tipo original do objeto, precisará primeiro salvar uma referência a ele em uma variável do tipo correto e, em seguida, chamar os métodos nessa variável:

Código Observação
Object o = new Scanner(System.in);
int x = o.nextInt();
O programa não irá compilar. A Objectclasse não tem nextInt()método.
Object o = new Scanner(System.in);

Scanner console = (Scanner) o;

int x = console.nextInt();
Isso vai funcionar.

Aqui salvamos uma referência a um Scannerobjeto em uma Scannervariável usando um operador typecast .

Você não pode simplesmente atribuir uma Objectvariável a uma variável Scanner, mesmo que a Objectvariável armazene uma referência a um Scannerobjeto. Mas você pode fazer isso se usar o typecast operator , que você já conhece. Esta é a sua aparência geral:

Type name1 = (Type) name2;

Onde name1é o nome de uma Typevariável e name2é o nome de uma Objectvariável que armazena uma referência a um Typeobjeto.

Datilografia

Se o tipo da variável e o tipo do objeto não corresponderem, ClassCastExceptionserá lançado um. Exemplo:

Código Observação
Object o = new Integer(5);
String s = (String) o;
Ocorrerá um erro no tempo de execução:
um ClassCastExceptionserá lançado aqui

Existe uma maneira de evitar esse erro em Java: fazemos isso verificando o tipo do objeto armazenado em uma variável :

name instanceof Type

O instanceofoperador verifica se a namevariável é um Typeobjeto.

Como exemplo, vamos encontrar uma string em um array de diversos objetos:

Código Observação
Object[] objects = {10, "Hello", 3.14};

for (int i = 0; i < objects.length; i++)
{
   if (objects[i] instanceof String)
   {
      String s = (String) objects[i];
      System.out.println(s);
   }
}
O Autoboxing converterá esses valores em Integer, Stringe Double, respectivamente.

Percorrer a matriz de objetos

Se o objeto for String

Salve-o em uma Stringvariável
Exiba a variável na tela.


2. Por que surgiram os genéricos — coleções

Voltemos às coleções.

Assim que os desenvolvedores Java criaram a ArrayListclasse, eles quiseram torná-la universal, para que pudesse armazenar qualquer tipo de objeto. Então eles usaram um array de Objects para armazenar os elementos.

A força dessa abordagem é que você pode adicionar um objeto de qualquer tipo à coleção.

Claro, existem vários pontos fracos.

Desvantagem 1.

Sempre foi necessário escrever um operador de conversão de tipo ao recuperar elementos de uma coleção:

Código Observação
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 10);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Crie uma coleção para armazenar referências a Objectobjetos

Preencha a coleção com números 10, 20, ... 100;



Somar os elementos da coleção


Typecasting é necessário

Desvantagem 2.

Não havia garantia de que uma coleção contém um tipo específico de elemento

Código Observação
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 2.5);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Crie uma coleção para armazenar referências a Objectobjetos

Nós preenchemos a coleção com números representados como Doubleobjetos:
0.0, 2.5, 5.0, ...


Somar os elementos da coleção


Ocorrerá um erro: a Doublenão pode ser convertido em umInteger

Os dados podem ser colocados na coleção em qualquer lugar:

  • em outro método
  • em outro programa
  • de um arquivo
  • pela rede

Desvantagem 3.

Os dados na coleção podem ser alterados acidentalmente.

Você pode passar uma coleção preenchida com seus dados para algum método. Esse método, escrito por um programador diferente, adiciona seus dados à sua coleção.

O nome da coleção não indica claramente quais tipos de dados podem ser armazenados nela. E mesmo se você der um nome claro à sua variável, uma referência a ela pode ser passada para uma dúzia de métodos, e esses métodos definitivamente não saberão nada sobre o nome original da variável.


3. Genéricos

Genéricos em Java

Em Java, todos esses problemas são eliminados por essa coisa legal chamada genéricos.

Em Java, genéricos significam a capacidade de adicionar parâmetros de tipo a tipos. O resultado é um tipo composto complexo. A visão geral de tal tipo composto é esta:

ClassName<TypeParameter>

Esta é uma classe genérica. E pode ser usado onde quer que você normalmente use as aulas.

Código Descrição
ArrayList<Integer> list;
Criando variáveis
list = new ArrayList<Integer> ();
Criando objetos
ArrayList<Integer>[] array;
Criando matrizes

Somente Integervariáveis ​​podem ser armazenadas em tal coleção:

Código Descrição
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(new Integer(1));
list.add(2);
list.add("Hello");
ArrayListcoleção com Integerelementos
Isso é permitido
E isso também funcionará
Autoboxing

Mas isso não é permitido: erro de compilação

Você aprenderá como criar suas próprias classes com parâmetros de tipo na missão Java Collections. Por enquanto, veremos como usá-los e como eles funcionam.


4. Como funcionam os genéricos

Na verdade, os genéricos são terrivelmente primitivos.

O compilador simplesmente substitui tipos genéricos por tipos comuns. Mas quando métodos de um tipo genérico são usados, o compilador adiciona um operador typecast para converter parâmetros para os parâmetros de tipo:

Código O que o compilador faz
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList list = new ArrayList();
list.add(1);
list.add( (Integer) 1 );
int x = list.get(0);
int x = (Integer) list.get(0);
list.set(0, 10);
list.set(0, (Integer) 10);

Suponha que temos um método que soma os números em uma coleção de inteiros:

Código O que o compilador faz
public int sum(ArrayList<Integer> numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + numbers.get(i);

   return result;
}
public int sum(ArrayList numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + (Integer) numbers.get(i);

   return result;
}

Em outras palavras, os genéricos são uma espécie de açúcar sintático, assim como o autoboxing, mas um pouco mais. Com o autoboxing, o compilador adiciona métodos para converter um intem um Integere vice-versa, e para genéricos adiciona operadores typecast.

Depois que o compilador compila suas classes genéricas com parâmetros de tipo, elas são simplesmente convertidas em classes comuns e operadores typecast. As informações sobre os argumentos de tipo passados ​​para variáveis ​​de tipos genéricos são perdidas. Esse efeito também é chamado de apagamento de tipo .

Às vezes, os programadores que escrevem classes genéricas (classes com parâmetros de tipo) realmente precisam das informações sobre os tipos passados ​​como argumentos. Na missão Java Collections, você aprenderá como lidar com isso e o que isso implica.



5. Alguns fatos sobre genéricos

Aqui estão alguns fatos mais interessantes sobre genéricos.

As classes podem ter vários parâmetros de tipo. Parece algo assim:

ClassName<TypeParameter1, TypeParameter2, TypeParameter3>

Na verdade, isso não é realmente surpreendente. Em qualquer lugar que o compilador possa adicionar um operador para converter em um tipo, ele pode adicionar vários operadores typecast.

Exemplos:

Código Observação
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(7, "Hello");
map.put(-15, "Hello");
O putprimeiro parâmetro do método é um Integer, e o segundo é umString

Tipos genéricos também podem ser usados ​​como parâmetros . Parece algo assim:

ClassName<TypeParameter<TypeParameterParameter>>

Suponha que queremos criar uma lista que armazenará listas de strings. Neste caso, teremos algo assim:

// List of greetings
ArrayList<String> listHello = new ArrayList<String>();
listHello.add ("Hello");
listHello.add ("Hi");

// List of goodbyes
ArrayList<String> listBye = new ArrayList<String>();
listBye.add("Bye");
listBye.add ("Goodbye");

// List of lists
ArrayList<ArrayList<String>> lists = new ArrayList<ArrayList<String>>();
lists.add(listHello);
lists.add(listBye);

Tipos genéricos (tipos com parâmetros de tipo) também podem ser usados ​​como tipos de matriz. Parece algo assim:

ClassName<TypeParameter>[] array = new ClassName<TypeParameter>[size];

Não há nada de mágico acontecendo aqui: os colchetes indicam apenas o nome do tipo:

Código Contraparte não genérica
ArrayList<String>[] list = new ArrayList<String>[10];
StringArrayList[] list = new StringArrayList[10];
ArrayList<Integer>[] list = new ArrayList<Integer>[10];
IntegerArrayList[] list = new IntegerArrayList[10];
ArrayList<Scanner>[] list = new ArrayList<Scanner>[10];
ScannerArrayList[] list = new ScannerArrayList[10];