Compreendendo os 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 Write for DOnations.

Introdução

Java é uma linguagem de programação estaticamente tipada. Isso significa que, ao criar uma variável, você também deve especificar o seu tipo de dado, que é o tipo de informação que ela armazena. Isso é em contraste com linguagens dinamicamente tipadas, como PHP. Com linguagens dinamicamente tipadas, você não precisa especificar o tipo de dado de uma variável, o que pode parecer um alívio.

No entanto, conhecer os tipos de dados e usá-los apropriadamente permite que os desenvolvedores otimizem seu código, pois cada tipo de dado possui requisitos específicos de recursos. Além disso, se você especificar um tipo de dado e tentar armazenar um tipo diferente, como por engano, você não conseguirá compilar o código. Assim, com linguagens estaticamente tipadas, é possível detectar erros mesmo antes de qualquer teste.

Java tem dois tipos de 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 para aprender sobre alguns dos tipos de dados comumente usados em Java. Este não é uma visão abrangente de todos os tipos de dados, mas este guia ajudará você a se familiarizar com as opções disponíveis para você em Java.

Pré-requisitos

Para seguir este tutorial, você precisará:

Tipos Primitivos

Os tipos primitivos do Java são os tipos de dados mais simples e básicos em Java. Eles representam valores brutos como números e caracteres. Os tipos de dados primitivos mais frequentemente usados são int (inteiros), boolean (valores booleanos) e char (caracteres). Você pode encontrar o restante na documentação oficial de tipos de dados do 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 grandes o suficiente para a maioria dos propósitos: de -2.147.483.648 a 2.147.483.647.

Vamos ver como o int é usado em um exemplo:

int theAnswer = 42;

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

Independentemente do tipo de dados, você utiliza 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 de todas as outras variáveis neste tutorial estão escritos em Camel case. Mesmo que não haja uma exigência rigorosa para utilizá-lo, essa é a convenção de nomenclatura aceita no Java.

Uma vez que você tenha declarado a variável, pode usá-la referenciando-a em um método da seguinte maneira:

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, utilize a ferramenta Java Shell. Após instalar o Java, abra um terminal ou prompt de comando em seu computador local e digite jshell:

  1. jshell

Seu resultado 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, 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á o seguinte resultado:

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

Este resultado confirma que você definiu corretamente a variável int theAnswer como 42 (theAnswer ==> 42). Você também usou com sucesso theAnswer ao passá-lo para um método, e o método produziu o valor da variável esperado.

Booleano

Booleano 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);

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

Output
isJavaFun ==> true Java is fun: true

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

Caracteres

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

char firstLetter = 'a';

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

char não parece ser um tipo particularmente útil, porque é pouco provável que você precise de uma variável atribuída a um único caractere. No entanto, char é utilizado como 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 do tipo primitivo são diretos, pois representam valores simples, como inteiros. Esses valores estão prontos para serem usados e não exigem operações adicionais, como a criação de objetos, invocação de métodos, etc.

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 referenciando 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 do tipo primitivo não podem apontar para objetos.

Objetos são poderosos porque possuem propriedades avançadas e conseguem 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 que variáveis do tipo referência são essenciais para o Java e 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 maneira 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. As mais utilizadas são encontradas no pacote central java.lang. Você revisará algumas delas 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 do tipo de referência, você primeiro especifica seu tipo seguido pelo 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 um. 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 junto 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 de construtor que é utilizado para criar novos objetos. Você pode invocar este construtor adicionando parênteses (()) ao final do nome da classe. O construtor pode aceitar parâmetros, como no exemplo acima, onde o parâmetro "Hello" é aplicado ao construtor para String.

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

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

Executar estas linhas no jshell produzirá a seguinte saída:

Output
hello ==> "Hello" Hello

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

Classes Wrapper

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

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

Esses invólucros existem para que você possa elevar um valor primitivo simples a um objeto poderoso. Cada invólucro possui métodos prontos para uso relacionados aos valores que ele foi projetado para 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, o uso de um método especial é principalmente sobre otimização de recursos, mas em outros casos, pode 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 fornecer um objeto com esse valor. Nos bastidores, o Java verificará se já existe um objeto com esse 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, ainda é possível criar um objeto com a palavra-chave new, mas você receberá um aviso de depreciação.

Além de String e wrappers, existem outros tipos de referência incorporados úteis, que você pode encontrar no resumo do pacote java.lang. Para entender completamente alguns desses tipos de referência mais avançados, é necessário uma explicação adicional ou conhecimento prévio. É por isso que abordaremos alguns deles nos próximos tutoriais da nossa 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, existe um literal, como os exemplos que usamos: 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 usado para criar um objeto Integer com o código Integer.valueOf(42). Também há uma forma mais simplificada 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 precisar de instruções adicionais. É comum ver um Integer declarado dessa forma porque é conveniente.

Essa abordagem simplificada 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. Também há um literal false, que pode ser atribuído da mesma forma.

O Literal de String

Também há 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!";

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

O Literal Null

Existe 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 o aponte 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.

Existe 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 de tipo de referência com valor null, ocorrerá 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 da versão do Java, a saída pode ser diferente.

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

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

Para corrigir o erro, é necessário reatribuir o valor de initiallyNullString desta forma:

  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, depois 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 é o pacote. Finalmente, uma mensagem completa e significativa é impressa: "O nome da classe é: class java.lang.String".

Declarações de valores null como essa são mais comuns em código legado. Elas foram usadas para criar uma variável primeiro e, em seguida, atribuir seu valor real posteriormente, geralmente passando por alguma lógica que determina este último. No entanto, desde a versão 8 do Java, existe um novo tipo de referência chamado Optional, que é mais adequado para casos em que o null era utilizado anteriormente.

Inferência de Tipo de Variável Local

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

De qualquer forma, a inferência de tipo não pode substituir completamente o uso de tipos de dados, porque funciona apenas com variáveis locais, que são variáveis dentro de um método. Vamos ver 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 seu tipo de dados. Depois disso, você a imprime no console da maneira usual para confirmar que funciona conforme o esperado:

Ouput
hello ==> "Hello" Hello

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

O processo de inferência de tipo ocorre durante o processo de compilação — ou seja, quando você compila o código. O processo de compilação transforma 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 com tipo inferido. Assim, o código de máquina que você executa após compilar 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. Variáveis locais são definidas dentro de métodos e são acessíveis apenas dentro dos métodos, é por isso que são chamadas de “locais”.

Para mostrar que var só pode ser usado para variáveis locais, tente colocá-lo 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. }

Quando você 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 aqui 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

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

  1. int new = 1;

Se você tentar este exemplo, você receberá 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 nessa 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 você usa a palavra-chave new, o Java espera que uma classe siga. Neste ponto, o Java não é capaz de interpretar a declaração e os demais erros seguem.

As demais 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 você não se lembre de todas as palavras-chave reservadas, você pode usar erros de compilação para identificar o problema.

Conclusão

Neste tutorial, você aprendeu sobre tipos de dados primitivos e de referência em Java, que é um tópico complexo, mas essencial. Dedique um tempo para praticá-lo e passe pelos exemplos mais de uma vez. Experimente alterar alguns dos tipos de dados e valores. Preste atenção em quando os erros são lançados e quando não são para desenvolver um senso de execução de código bem-sucedida.

Para mais sobre Java, confira nossa série Como Programar em Java.

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