Compreensão dos Tipos de Dados em Java

O autor selecionou o Fundo Livre e de Código Aberto para receber uma doação como parte do programa Escreva por Doações.

Introdução

O Java é uma linguagem de programação com tipagem estática. Isso significa que ao criar uma variável, você também deve especificar seu tipo de dados, que é o tipo de informação que ela armazena. Isso contrasta com as linguagens de tipagem dinâmica, como o PHP. Com linguagens de tipagem dinâmica, você não precisa especificar o tipo de dados de uma variável, o que pode parecer um alívio.

No entanto, conhecer os tipos de dados e usá-los apropriadamente permite aos desenvolvedores otimizar seu código, pois cada tipo de dados tem requisitos de recursos específicos. Além disso, se você especificar um tipo de dados e tentar armazenar um tipo diferente, como por engano, não conseguirá compilar o código. Assim, com linguagens de tipagem estática, você pode detectar erros mesmo antes de qualquer teste.

Java possui dois tipos de dados: primitivos e de referência (também conhecidos como não primitivos). Neste tutorial, você usará variáveis para armazenar e usar informações em um programa Java, aprendendo sobre alguns dos tipos de dados comumente utilizados. Este não é um guia abrangente de todos os tipos de dados, mas ajudará você a se familiarizar com as opções disponíveis em Java.

Pré-requisitos

Para seguir este tutorial, você precisará:

Tipos Primitivos

Os tipos primitivos em Java são os tipos de dados mais simples e básicos. Eles representam valores brutos, como números e caracteres. Os tipos de dados primitivos mais frequentemente utilizados são int (inteiros), boolean (valores booleanos) e char (caracteres). Você pode encontrar o restante no documento oficial sobre tipos de dados em Java.

Inteiros

Os inteiros podem ser tanto números inteiros negativos quanto positivos. Em Java, você usará int para armazená-los. O int pode acomodar números suficientemente grandes para a maioria dos propósitos: de -2.147.483.648 a 2.147.483.647.

Vamos ver como o int é utilizado em um exemplo:

int theAnswer = 42;

Os tipos primitivos sempre começam com uma letra minúscula (int). As regras de sintaxe em Java exigem que você primeiro especifique o tipo de dado (int) e, em seguida, seu nome (theAnswer). Depois disso, você atribui o valor 42 à variável com o sinal de igual (=).

Independentemente do tipo de dados, você usa uma variável especificando diretamente seu nome sem adicionar nenhum caractere especial. Isso ocorre porque o Java pode reconhecê-lo como uma variável.

Nota: O nome da variável theAnswer e todas as outras variáveis neste tutorial são escritos em Camel case. Mesmo que não haja uma exigência rigorosa para usá-lo, esta é a convenção de nomenclatura aceita no Java.

Depois de declarar a variável, você pode usá-la referenciando-a em um método da seguinte forma:

int theAnswer = 42;
System.out.println("The answer to all questions is " + theAnswer);

Na segunda linha, você imprime theAnswer no console usando o método integrado println do pacote System.out. Esta é a maneira mais simples de testar uma variável para garantir que ela seja declarada conforme o esperado.

Para ver este código em ação, use a ferramenta Java Shell. Após instalar o Java, abra um terminal ou prompt de comando no seu computador local e digite jshell:

  1. jshell

Sua saída será semelhante ao seguinte:

Output
| Welcome to JShell -- Version 11.0.16 | For an introduction type: /help intro jshell>

Você pode colar os exemplos de código deste tutorial no console. Quando terminar, você pode sair do jshell digitando /exit.

Para declarar e usar int, cole as seguintes linhas no console do jshell:

  1. int theAnswer = 42;
  2. System.out.println("The answer to all questions is " + theAnswer);

Você verá a seguinte saída:

Output
theAnswer ==> 42 The answer to all questions is 42

Esta saída confirma que você definiu corretamente a variável int theAnswer para 42 (theAnswer ==> 42). Você também usou com sucesso theAnswer passando-o para um método, e o método produziu o valor esperado da variável.

Boolean

Boolean valores são true ou false. Em Java, você usará boolean para armazená-los. Por exemplo, vamos criar uma variável boolean definindo se Java é divertido ou não:

boolean isJavaFun = true;

Você define a variável isJavaFun como true. O valor alternativo de boolean é false.

Usando a variável acima, você pode imprimir a frase Java é divertido: true assim:

  1. boolean isJavaFun = true;
  2. System.out.println("Java is fun: " + isJavaFun);

Executando essas linhas no jshell produzirá a seguinte saída:

Output
isJavaFun ==> true Java is fun: true

Semelhante ao exemplo de int, o método println imprimirá o argumento fornecido entre parênteses. O sinal de mais (+) concatena ou une a string “Java é divertido: ” com a variável isJavaFun, de modo que na realidade, é apenas um argumento — a string, Java é divertido: true.

Personagens

Para armazenar um único caractere alfanumérico, você usará char. Por exemplo:

char firstLetter = 'a';

Observe que a letra a está entre aspas simples. Aspas simples são usadas apenas para valores de char. Aspas duplas são usadas para strings, como você aprenderá mais tarde.

char não parece ser um tipo particularmente útil, pois é pouco provável que você precise de uma variável atribuída a um único caractere. No entanto, char é utilizado como o bloco de construção para classes de cadeias de caracteres como String, que são basicamente uma coleção de valores char.

Como visto nesta seção, a declaração e o uso de variáveis de tipo primitivo são diretos, pois representam valores simples como inteiros. Esses valores estão prontos para serem usados e não requerem operações adicionais, como criar objetos, invocar métodos, e assim por diante.

Tipos de Referência

No primeiro tutorial desta série, Como Escrever Seu Primeiro Programa em Java, você aprendeu que o código Java é organizado em classes e que essas classes são usadas como modelos para criar objetos. Quando esses objetos são atribuídos a variáveis, você está apontando ou se referindo a esses objetos. Nesses casos, as variáveis são classificadas como tipos de referência. Essas variáveis também são conhecidas como não primitivas, porque variáveis de tipo primitivo não podem apontar para objetos.

Objetos são poderosos porque possuem propriedades avançadas e podem agir quando você aciona seus métodos. No entanto, sem variáveis apontando para eles, esses objetos são inacessíveis e praticamente inutilizáveis. Por isso, variáveis do tipo referência são essenciais para o Java e a programação orientada a objetos como um todo.

Nota: Tipos de referência apontam para objetos criados a partir de classes. Para evitar confusão, o tipo de referência e o objeto criado serão da mesma classe nos exemplos a seguir.

No entanto, em programas complexos, isso raramente é o caso. Em Java, uma interface é um grupo de requisitos para um comportamento específico, e esses requisitos podem ser atendidos por uma ou mais classes. Uma classe que atende aos requisitos de uma interface é considerada como implementando essa interface. Assim, em programas complexos, é comum declarar uma variável com o tipo de referência de uma interface. Dessa forma, você especifica o comportamento que sua variável deve exibir sem vinculá-la a uma implementação concreta desse comportamento. Isso permite que você altere facilmente para qual implementação sua variável aponta sem precisar alterar a forma como a variável é usada. Esse conceito complexo faz parte de um tópico mais avançado sobre herança e polimorfismo, que será um tutorial separado em nossa série Java.

Embora existam apenas alguns tipos primitivos, os tipos de referência são praticamente ilimitados porque não há limite para o número de classes (e interfaces), e cada classe representa um tipo de referência. Existem muitas classes integradas em Java que fornecem funcionalidades essenciais. Os mais frequentemente usados são encontrados no pacote principal java.lang. Você irá revisar alguns deles nesta seção.

A Classe String

A classe String representa uma combinação de caracteres que formam uma string. Para declarar uma String, ou qualquer outra variável de tipo de referência, você primeiro especifica seu tipo seguido pelo seu nome. Depois disso, você atribui um valor a ela com o sinal de igual. Até agora, é semelhante ao trabalho com tipos primitivos. No entanto, os tipos de referência apontam para objetos, então você precisa criar um objeto se ainda não houver sido criado. Aqui está um exemplo:

String hello = new String("Hello");

hello é o nome da variável com tipo de referência String. Você a atribui a um novo objeto String. O novo objeto String é criado com a palavra-chave new juntamente com o nome da classe — String neste caso. A classe String começa com uma letra maiúscula. Por convenção, todas as classes e, portanto, tipos de referência começam com uma letra maiúscula.

Cada classe possui um método especial chamado construtor que é utilizado para criar novos objetos. Você pode invocar este construtor adicionando parênteses (()) no final do nome da classe. O construtor pode aceitar parâmetros, como no exemplo acima, onde o parâmetro "Hello" é aplicado ao construtor de String.

Para confirmar que a variável hello se comporta conforme o esperado, passe-a novamente para o método println desta forma:

  1. String hello = new String("Hello");
  2. System.out.println(hello);

Executar essas linhas no jshell resultará na seguinte saída:

Output
hello ==> "Hello" Hello

Desta vez, a saída confirma que a variável hello está definida como Hello. Depois disso, o mesmo Hello é impresso em uma nova linha, confirmando que o método println() o processou.

Classes de Invólucro

Na seção anterior, você trabalhou com o tipo de referência String, que é frequentemente utilizado. Outros tipos de referência populares são os chamados invólucros para tipos primitivos. Uma classe de invólucro envolve ou contém dados primitivos, daí o seu nome. Todos os tipos primitivos têm equivalentes de invólucro e aqui estão alguns exemplos:

  • Integer: Para envolver valores int.
  • Character: Para envolver valores char.
  • Boolean: Para envolver valores boolean.

Estes invólucros existem para que você possa atualizar um valor primitivo simples para um objeto poderoso. Cada invólucro tem métodos prontos para uso relacionados aos valores que se destinam a armazenar.

Como exemplo, você explorará Integer. Na seção anterior, você criou um objeto String com a palavra-chave new. No entanto, algumas classes fornecem e até incentivam o uso de métodos especiais para adquirir objetos delas, e Integer é uma delas. No caso de Integer, usar um método especial é principalmente sobre otimização de recursos, mas em outros casos, poderia ser sobre simplificar a construção de objetos complexos.

No exemplo a seguir, você cria uma variável Integer chamada theAnswer com o valor 42 usando o método valueOf:

  1. Integer theAnswer = Integer.valueOf(42);
  2. System.out.println(theAnswer);

No jshell, você obterá a seguinte saída:

Output
theAnswer ==> 42 42

Ao invocar o método valueOf(42) da classe Integer, você instrui o Java a lhe fornecer um objeto com este valor. Nos bastidores, o Java verificará se já existe um objeto com tal valor em seu cache. Se houver, o objeto será vinculado à variável theAnswer. Se não houver, um novo objeto será criado para a variável theAnswer.

Muitas classes integradas fornecem tais métodos por razões de desempenho, e seu uso é recomendado, se não obrigatório. No caso de Integer, você ainda poderia criar um objeto com a palavra-chave new, mas você receberá um aviso de depreciação.

Além de String e wrappers, também existem outros tipos de referência integrados úteis, que você pode encontrar no resumo do pacote java.lang. Para entender completamente alguns desses tipos de referência mais avançados, é necessária uma explicação adicional ou conhecimento prévio. É por isso que vamos abordar alguns deles em nossos próximos tutoriais da série Java.

Literais

Os literais representam valores fixos que podem ser usados diretamente no código e, portanto, podem ser atribuídos tanto a tipos primitivos quanto a tipos de referência. Existem alguns tipos de literais, que podem ser categorizados da seguinte forma.

Literais de Tipo Primitivo

Você já usou alguns literais na seção sobre tipos primitivos. Para cada tipo primitivo, há um literal, como os exemplos: 42, 'a' e true. Inteiros como 42 são literais inteiros. Da mesma forma, caracteres como 'a' são literais de caractere, e true e false são literais booleanos.

Tipos primitivos literais também podem ser usados para criar valores para tipos de referência. O literal int foi utilizado na criação de um objeto Integer com o código Integer.valueOf(42). Existe também uma forma abreviada para isso, e você pode atribuir o valor diretamente assim:

Integer theAnswer = 42;

42 é um literal inteiro, assim como qualquer número inteiro, e você pode atribuí-lo diretamente à variável theAnswer sem a necessidade de declarações adicionais. É comum ver um Integer declarado dessa forma porque é conveniente.

Esta abordagem abreviada também funciona para outros tipos primitivos literais e seus tipos de referência correspondentes, como Boolean, por exemplo:

Boolean isFun = true;

true é o literal, que é atribuído diretamente à variável isFun do tipo Boolean. Existe também um literal false, que você pode atribuir da mesma maneira.

O Literal de String

Existe também um literal especial para o tipo de referência String, e ele é reconhecido pelas aspas duplas que cercam seu valor. Neste exemplo, é "Olá, Mundo!":

String helloWorld = "Hello, World!";

O uso de literais é mais simples e mais curto, e é por isso que muitos programadores o preferem. No entanto, ainda é possível declarar uma variável String com um novo objeto String, como já foi feito na seção de tipos de referência.

O Literal Null

Há mais um literal importante: null, que representa a ausência de um valor ou a não existência de um objeto. Null permite que você crie um tipo de referência e aponte-o para null em vez de apontá-lo para um objeto. null pode ser usado para todos os tipos de referência, mas não para nenhum tipo primitivo.

Há uma ressalva com o literal null: você pode declarar variáveis com ele, mas não pode usar essas variáveis até atribuir um valor adequado e não nulo. Se você tentar usar uma variável do tipo de referência com um valor null, receberá um erro. Aqui está um exemplo:

  1. String initiallyNullString = null;
  2. System.out.println("The class name is: " + initiallyNullString.getClass());

Ao tentar executar este código no jshell, você verá um erro semelhante ao seguinte:

Output
initiallyNullString ==> null | Exception java.lang.NullPointerException | at (#4:1)

Dependendo do seu sistema operacional e versão do Java, sua saída pode ser diferente.

O erro java.lang.NullPointerException é lançado porque você está tentando invocar o método getClass() (que retorna o nome da classe) na variável initiallyNullString (que aponta para um objeto nulo).

Nota: Para simplicidade, estamos chamando java.lang.NullPointerException de erro, embora tecnicamente seja uma exceção. Para mais informações sobre exceções e erros, consulte o tutorial, Tratamento de Exceções em Java.

Para resolver o erro, você precisa reatribuir o valor initiallyNullString assim:

  1. String initiallyNullString = null;
  2. initiallyNullString = "not null any longer";
  3. System.out.println("The class name is: " + initiallyNullString.getClass());

O novo código corrigido imprimirá a seguinte saída:

Output
initiallyNullString ==> null initiallyNullString ==> "not null any longer" The class name is: class java.lang.String

A saída acima mostra como initiallyNullString é inicialmente null, e então se torna um novo objeto String contendo "not null any longer". Em seguida, quando o método getClass() é invocado no objeto instanciado, você obtém java.lang.String, onde String é o nome da classe e java.lang é seu pacote. Finalmente, uma mensagem completa e significativa é impressa: "The class name is: class java.lang.String".

Tais declarações de valores null são mais comuns em código legado. Elas foram usadas para criar uma variável primeiro e depois atribuir seu valor real, geralmente passando por alguma lógica que determina isso posteriormente. No entanto, desde a versão 8 do Java, há um novo tipo de referência chamado Optional, que é mais adequado para casos em que null foi usado anteriormente.

Inferência de Tipo de Variável Local

Até agora, você tem utilizado alguns dos tipos de dados comuns em Java para definir variáveis. No entanto, o Java 10 introduziu uma nova funcionalidade chamada inferência de tipo de variável local, que permite o uso da palavra-chave var na frente de uma nova variável. Com essa funcionalidade, o Java irá inferir (ou seja, adivinhar automaticamente) o tipo de dado a partir do contexto local. A inferência de tipo é controversa, pois contrasta com a verbosidade previamente explicada na definição de variáveis. As vantagens e desvantagens dessa funcionalidade são discutíveis, mas o fato é que outras linguagens tipadas estaticamente, como C++, também suportam a inferência de tipo.

Em todo caso, a inferência de tipo não pode substituir completamente o uso de tipos de dados, pois funciona apenas com variáveis locais, que são variáveis dentro de um método. Vamos dar uma olhada em um exemplo com var:

  1. var hello = "Hello";
  2. System.out.println(hello);

Você declara a variável hello com a palavra-chave var para instruir o Java a detectar o tipo de dado. Depois disso, você a imprime no console da maneira usual para confirmar que está funcionando como esperado:

Ouput
hello ==> "Hello" Hello

Este exemplo funcionará desde que sua instalação do Java (mais especificamente, o JDK) seja da versão 10 ou superior. A palavra-chave var não é suportada em versões mais antigas.

A inferência de tipo ocorre durante o processo de compilação — isto é, quando você compila o código. O processo de compilação converte o código-fonte em texto simples em código de máquina e aplica várias otimizações, incluindo a inferência de tipo. Isso garante que a quantidade correta de memória do sistema esteja disponível para as variáveis de tipo inferido. Assim, o código de máquina que você executa após a compilação está totalmente otimizado, como se você tivesse especificado manualmente todos os tipos de dados.

Neste exemplo, a palavra-chave var funciona porque a variável é local, e o tipo de dados var funciona apenas com variáveis locais. As variáveis locais são definidas dentro de métodos e são acessíveis apenas dentro dos métodos, razão pela qual são chamadas de “locais”.

Para mostrar que var só pode ser usada para variáveis locais, tente colocá-la fora do método principal, assim:

  1. public class Hello {
  2. var hello = "Hello";
  3. public static void main(String[] args) {
  4. // example code
  5. }
  6. }

Ao colar o código acima no jshell, você receberá o seguinte erro:

Output
| Error: | 'var' is not allowed here | var hello = "Hello"; | ^-^

var não é permitido lá porque hello está fora de um método e não é mais considerado local. Assim, a inferência de tipo não funciona para variáveis não locais porque o contexto não pode ser usado de forma confiável para detectar o tipo de dados.

Embora usar var possa ser desafiador e não seja obrigatório, você provavelmente encontrará isso, então é útil conhecê-lo.

Palavras-chave Reservadas

Quando declarando variáveis em Java, há mais uma regra importante a saber. Existem palavras-chave reservadas que não pode usar como nomes de variáveis. Por exemplo, não pode declarar um primitivo do tipo int e nomeá-lo new assim:

  1. int new = 1;

Se tentar este exemplo, terá erros de compilação porque new é uma palavra-chave reservada.

Output
| Error: | '.class' expected | int new = 1; | ^ | Error: | <identifier> expected | int new = 1; | ^ | Error: | '(' or '[' expected | int new = 1; | ^ | Error: | unexpected type | required: value | found: class | int new = 1; | ^--^ | Error: | missing return statement | int new = 1; | ^----------^

A palavra-chave new é usada para criar novos objetos e o Java não espera isso nesta posição. Na lista de erros na saída anterior, a primeira parte é a mais importante:

Output
| Error: | '.class' expected | int new = 1; | ^

O erro '.class' esperado significa que quando usa a palavra-chave new, o Java espera que uma classe siga. Neste ponto, o Java não consegue interpretar a declaração e os outros erros seguem.

O resto das palavras-chave reservadas, como abstract, continue, default, for e break, também têm significados específicos em Java e não podem ser usadas como nomes de variáveis. A lista completa das palavras-chave reservadas pode ser encontrada na página de Palavras-chave da Linguagem Java. Mesmo que não se lembre de todas as palavras-chave reservadas, pode usar erros de compilação para identificar o problema.

Conclusão

No tutorial, aprendeu sobre tipos de dados primitivos e de referência em Java, que é um tópico complexo, mas essencial. Dedique tempo para praticar e revisar os exemplos mais de uma vez. Experimente alterar alguns dos tipos de dados e valores. Preste atenção em quando os erros são gerados e quando não são, a fim de desenvolver um senso para a execução bem-sucedida do código.

Para mais informações sobre Java, confira nossa série Como Programar em Java.

Source:
https://www.digitalocean.com/community/tutorials/understanding-data-types-in-java