Comprendiendo los Tipos de Datos en Java

El autor seleccionó el Fondo para el Software Libre y de Código Abierto para recibir una donación como parte del programa Escribe para Donaciones.

Introducción

Java es un lenguaje de programación de tipado estático. Esto significa que cuando creas una variable, también debes especificar su tipo de dato, que es el tipo de información que almacena. Esto contrasta con los lenguajes de tipado dinámico, como PHP. Con los lenguajes de tipado dinámico, no tienes que especificar el tipo de dato de una variable, lo cual puede parecer un alivio.

Sin embargo, conocer los tipos de datos y usarlos adecuadamente permite a los desarrolladores optimizar su código porque cada tipo de dato tiene requisitos de recursos específicos. Además, si especificas un tipo de dato y tratas de almacenar un tipo diferente, como por error, no podrás compilar el código. Por lo tanto, con los lenguajes de tipado estático, puedes detectar errores incluso antes de realizar cualquier prueba.

Java tiene dos tipos de datos: primitivos y de referencia (también conocidos como no primitivos). En este tutorial, usarás variables para almacenar y utilizar información en un programa Java para aprender sobre algunos de los tipos de datos comúnmente utilizados en Java. Esta no es una visión general exhaustiva de todos los tipos de datos, pero esta guía te ayudará a familiarizarte con las opciones disponibles en Java.

Prerrequisitos

Para seguir este tutorial, necesitarás:

Tipos Primitivos

Los tipos primitivos de Java son los tipos de datos más simples y básicos en Java. Representan valores sin procesar como números y caracteres. Los tipos de datos primitivos más utilizados son int (enteros), boolean (valores booleanos) y char (caracteres). Puedes encontrar el resto en la documentación oficial de tipos de datos de Java.

Enteros

Los enteros pueden ser tanto números enteros negativos como positivos. En Java, se utiliza int para almacenarlos. int puede acomodar números lo suficientemente grandes para la mayoría de los propósitos: desde -2,147,483,648 hasta 2,147,483,647.

Veamos cómo se usa int en un ejemplo:

int theAnswer = 42;

Los tipos primitivos siempre comienzan con una letra minúscula (int). Las reglas de sintaxis de Java requieren que primero especifiques el tipo de datos (int) y luego su nombre (theAnswer). Después de eso, asignas el valor 42 con el signo igual (=) a la variable.

Independientemente de su tipo de datos, se utiliza una variable especificando directamente su nombre sin añadir ningún carácter especial. Esto se debe a que Java puede reconocerlo como una variable.

Nota: El nombre de la variable theAnswer y todas las demás variables en este tutorial están escritos en Camel case. Aunque no hay un requisito estricto para usarlo, esta es la convención de nomenclatura aceptada en Java.

Una vez que has declarado la variable, puedes usarla refiriéndote a ella en un método de la siguiente manera:

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

En la segunda línea, imprimes theAnswer en la consola utilizando el método incorporado println del paquete System.out. Esta es la forma más sencilla de probar una variable para asegurarte de que se haya declarado como se espera.

Para ver este código en acción, utiliza la herramienta Java Shell. Después de instalar Java, abre una terminal o símbolo del sistema en tu computadora local y escribe jshell:

  1. jshell

La salida se verá similar a lo siguiente:

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

Puedes pegar los ejemplos de código de este tutorial en la consola. Cuando hayas terminado, puedes salir de jshell escribiendo /exit.

Para declarar y usar int, pega las siguientes líneas en la consola de jshell:

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

Verás la siguiente salida:

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

Esta salida confirma que has configurado correctamente la variable int theAnswer a 42 (theAnswer ==> 42). También has utilizado con éxito theAnswer al pasarlo a un método, y el método produjo el valor de la variable esperado.

Los valores Boolean

Boolean son true o false. En Java, utilizarás boolean para almacenarlos. Por ejemplo, creemos una variable boolean que defina si Java es divertido o no:

boolean isJavaFun = true;

Defines la variable isJavaFun como true. El valor alternativo de boolean es false.

Usando la variable anterior, puedes imprimir la frase Java is fun: true de esta manera:

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

Ejecutar estas líneas en jshell producirá la siguiente salida:

Output
isJavaFun ==> true Java is fun: true

Similar al ejemplo de int, el método println imprimirá el argumento proporcionado entre paréntesis. El signo más (+) concatena o une la cadena “Java is fun: ” con la variable isJavaFun para que en realidad sea solo un argumento: la cadena, Java is fun: true.

Caracteres

Para almacenar un único carácter alfanumérico, usarás char. Por ejemplo:

char firstLetter = 'a';

Observa que la letra a está rodeada por comillas simples. Las comillas simples solo se pueden usar para valores char. Las comillas dobles se usan para cadenas, como aprenderás más adelante.

char no parece ser un tipo especialmente útil porque es poco probable que necesites una variable asignada a un solo carácter. Sin embargo, char se utiliza como el bloque de construcción para clases de cadenas de caracteres como String, que básicamente son una colección de valores char.

Como has visto en esta sección, la declaración y el uso de variables de tipo primitivo es directo porque representan valores simples como enteros. Estos valores están listos para ser usados y no requieren operaciones adicionales como la creación de objetos, la invocación de métodos, y así sucesivamente.

Tipos de Referencia

En el primer tutorial de esta serie, Cómo Escribir Tu Primer Programa en Java, aprendiste que el código Java está organizado en clases y que estas clases se utilizan como plantillas para crear objetos. Cuando dichos objetos se asignan a variables, estás apuntando o haciendo referencia a estos objetos. En estos casos, las variables se clasifican como tipos de referencia. Estas variables también son conocidas como no primitivas porque las variables de tipo primitivo no pueden apuntar a objetos.

Los objetos son poderosos porque tienen propiedades avanzadas y pueden actuar cuando activas sus métodos. Sin embargo, sin variables que apunten a ellos, estos objetos son inaccesibles y prácticamente inutilizables. Por eso, los variables de tipo referencia son esenciales para Java y la programación orientada a objetos en su conjunto.

Nota: Los tipos de referencia apuntan a objetos creados a partir de clases. Para evitar confusiones, el tipo de referencia y el objeto creado serán de la misma clase en los ejemplos siguientes.

Sin embargo, en programas complejos, rara vez ocurre esto. En Java, una interfaz es un conjunto de requisitos para un comportamiento específico, y estos requisitos pueden ser satisfechos por una o más clases. Una clase que satisface los requisitos de una interfaz se dice que implementa esta interfaz. Por lo tanto, en programas complejos, es común declarar una variable con el tipo de referencia de una interfaz. De esta manera, especificas el comportamiento que tu variable debe mostrar sin vincularlo a una implementación concreta de este comportamiento. Esto te permite cambiar fácilmente a qué implementación apunta tu variable sin tener que cambiar la forma en que se utiliza la variable. Este concepto complejo forma parte de un tema más avanzado sobre herencia y polimorfismo, que será un tutorial aparte en nuestra serie sobre Java.

Aunque solo hay unos pocos tipos primitivos, los tipos de referencia son prácticamente ilimitados porque no hay límite en el número de clases (y interfaces), y cada clase representa un tipo de referencia. Hay muchas clases integradas en Java que proporcionan funcionalidades esenciales. Las más utilizadas se encuentran en el paquete central java.lang. Revisarás algunas de ellas en esta sección.

La Clase String

La clase String representa una combinación de caracteres que forman una cadena. Para declarar un String, o cualquier otra variable de tipo referencia, primero especificas su tipo seguido de su nombre. Después, asignas un valor con el signo igual. Hasta aquí, es similar a trabajar con tipos primitivos. Sin embargo, los tipos de referencia apuntan a objetos, por lo que tienes que crear un objeto si aún no se ha creado uno. Aquí tienes un ejemplo:

String hello = new String("Hello");

hello es el nombre de la variable con tipo de referencia String. Lo asignas a un nuevo objeto String. El nuevo objeto String se crea con la palabra clave new junto con el nombre de la clase, que es String en este caso. La clase String comienza con una letra mayúscula. Por convención, todas las clases y, por lo tanto, los tipos de referencia comienzan con una letra mayúscula.

Cada clase tiene un método especial llamado constructor que se utiliza para crear nuevos objetos. Puedes invocar este constructor agregando paréntesis (()) al final del nombre de la clase. El constructor puede aceptar parámetros, como en el ejemplo anterior, donde el parámetro "Hola" se aplica al constructor de String.

Para confirmar que la variable hello se comporta como se espera, pásala nuevamente al método println de la siguiente manera:

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

Al ejecutar estas líneas en jshell, se producirá la siguiente salida:

Output
hello ==> "Hello" Hello

Esta vez, la salida confirma que la variable hello está establecida en Hola. Después de eso, el mismo Hola se imprime en una nueva línea, confirmando que el método println() lo ha procesado.

Clases Envolturas

En la sección anterior, trabajaste con el tipo de referencia String, que se usa con frecuencia. Otros tipos de referencia populares son las llamadas envolturas para tipos primitivos. Una clase envoltura envuelve o contiene datos primitivos, de ahí su nombre. Todos los tipos primitivos tienen contrapartes envolventes, y aquí hay algunos ejemplos:

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

Estos envoltorios existen para que puedas mejorar un valor primitivo simple a un objeto poderoso. Cada envoltorio tiene métodos listos para usar relacionados con los valores que están diseñados para almacenar.

Como ejemplo, explorarás Integer. En la sección anterior, creaste un objeto String con la palabra clave new. Sin embargo, algunas clases proporcionan, e incluso fomentan, el uso de métodos especiales para adquirir objetos de ellos, y Integer es uno de ellos. En el caso de Integer, el uso de un método especial se trata principalmente de optimización de recursos, pero en otros casos, podría tratarse de simplificar la construcción de objetos complejos.

En el siguiente ejemplo, creas una variable Integer llamada theAnswer con el valor 42 usando el método valueOf:

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

En jshell, obtendrás la siguiente salida:

Output
theAnswer ==> 42 42

Al invocar el método valueOf(42) de Integer, instruyes a Java para que te proporcione un objeto con este valor. Detrás de escena, Java verificará si ya hay un objeto con dicho valor en su caché. Si existe, el objeto se vinculará a la variable theAnswer. Si no existe, se creará un nuevo objeto para la variable theAnswer.

Muchas clases integradas proporcionan tales métodos por razones de rendimiento, y su uso es recomendado, si no obligatorio. En el caso de Integer, aún podrías crear un objeto con la palabra clave new, pero recibirás una advertencia de desaprobación.

Además de String y envoltorios, también hay otros tipos de referencia integrados útiles, que puedes encontrar en el resumen del paquete java.lang. Para entender completamente algunos de estos tipos de referencia más avanzados, se requiere una explicación adicional o conocimientos previos. Por eso, cubriremos algunos de ellos en nuestros próximos tutoriales de la serie Java.

Literales

Los literales representan valores fijos que se pueden utilizar directamente en el código y, por lo tanto, se pueden asignar tanto a tipos primitivos como a tipos de referencia. Hay algunos tipos de literales, que se pueden categorizar de la siguiente manera.

Literales de Tipo Primitivo

Ya has utilizado algunos literales en la sección sobre tipos primitivos. Para cada tipo primitivo, hay un literal, como los de nuestros ejemplos: 42, 'a' y true. Enteros como 42 son literales enteros. Del mismo modo, caracteres como 'a' son literales de caracteres, y true y false son literales booleanos.

Los literales de tipos primitivos también se pueden utilizar para crear valores para tipos de referencia. El literal int se usó para crear un objeto Integer con el código Integer.valueOf(42). También hay una forma abreviada para eso, y puedes asignar el valor directamente así:

Integer theAnswer = 42;

42 es un literal entero, al igual que cualquier número entero, y puedes asignarlo directamente a la variable theAnswer sin necesidad de declaraciones adicionales. Es común ver un Integer declarado de esta manera porque es conveniente.

Este enfoque abreviado también funciona para otros literales de tipos primitivos y sus tipos de referencia correspondientes, como Boolean, por ejemplo:

Boolean isFun = true;

true es el literal, que se asigna directamente a la variable isFun de tipo Boolean. También hay un literal false, que puedes asignar de la misma manera.

El Literal de Cadena

También hay un literal especial para el tipo de referencia String, y se reconoce por las comillas dobles que rodean su valor. En este ejemplo, es "¡Hola, mundo!":

String helloWorld = "Hello, World!";

Usar literales es más simple y corto, y por eso muchos programadores lo prefieren. Sin embargo, aún puedes declarar una variable String con un nuevo objeto String, como ya lo has hecho en la sección de tipos de referencia.

El Literal Null

Hay otro literal importante: null, que representa la ausencia de un valor o la no existencia de un objeto. Null te permite crear un tipo de referencia y apuntarlo a null en lugar de apuntarlo a un objeto. null se puede usar para todos los tipos de referencia pero no para ningún tipo primitivo.

Hay una advertencia con el literal null: puedes declarar variables con él, pero no puedes usar estas variables hasta que les asignes un valor adecuado y no nulo. Si intentas usar una variable de tipo de referencia con un valor null, obtendrás un error. Aquí tienes un ejemplo:

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

Cuando intentes ejecutar este código en jshell, verás un error similar al siguiente:

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

Dependiendo de tu sistema operativo y versión de Java, la salida puede ser diferente.

El error java.lang.NullPointerException se lanza porque estás intentando invocar el método getClass() de String (que devuelve el nombre de la clase) en la variable initiallyNullString (que apunta a un objeto nulo).

Nota: Para simplificar, llamamos java.lang.NullPointerException un error aunque técnicamente es una excepción. Para más información sobre excepciones y errores, consulta el tutorial, Manejo de Excepciones en Java.

Para abordar el error, debes reasignar el valor de initiallyNullString de esta manera:

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

El nuevo código corregido imprimirá la siguiente salida:

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

La salida anterior muestra cómo initiallyNullString es primero null, luego se convierte en un nuevo objeto String que contiene "not null any longer". A continuación, cuando se invoca el método getClass() en el objeto instanciado, obtienes java.lang.String, donde String es el nombre de la clase y java.lang es su paquete. Finalmente, se imprime un mensaje completo y significativo: "El nombre de la clase es: class java.lang.String".

Declaraciones de valores null como estas son más comunes en código heredado. Se han utilizado para crear una variable primero y luego asignarle su valor real más tarde, generalmente pasando por alguna lógica que determina esto último. Sin embargo, desde la versión 8 de Java hay un nuevo tipo de referencia llamado Optional, que es más adecuado para casos en los que se ha utilizado null anteriormente.

Inferencia de Tipo de Variable Local

Hasta ahora, has estado utilizando algunos de los tipos de datos comunes en Java para definir variables. Sin embargo, Java 10 introdujo una nueva característica llamada inferencia de tipo de variable local, que te permite usar la palabra clave var frente a una nueva variable. Con esta característica, Java inferirá (es decir, adivinará automáticamente) el tipo de datos a partir del contexto local. La inferencia de tipo es controvertida, ya que contrasta con la verbosidad previamente explicada al definir variables. Las ventajas y desventajas de esta característica son discutibles, pero el hecho es que otros lenguajes tipados estáticamente, como C++, admiten la inferencia de tipo.

En cualquier caso, la inferencia de tipo no puede reemplazar completamente el uso de tipos de datos porque solo funciona con variables locales, es decir, variables dentro de un método. Veamos un ejemplo con var:

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

Declaras la variable hello con la palabra clave var para instruir a Java que detecte su tipo de datos. Después, la imprimes en la consola de la manera habitual para confirmar que funciona según lo esperado:

Ouput
hello ==> "Hello" Hello

Este ejemplo funcionará siempre y cuando tu instalación de Java (más específicamente, el JDK) sea de la versión 10 en adelante. La palabra clave var no es compatible con versiones antiguas.

El proceso de inferencia de tipos ocurre durante el proceso de compilación, es decir, cuando compila el código. El proceso de compilación convierte el código fuente de texto plano en código máquina y aplica diversas optimizaciones, incluida la inferencia de tipos. Esto asegura que la cantidad correcta de memoria del sistema esté disponible para las variables inferidas por tipo. Por lo tanto, el código máquina que se ejecuta después de compilar está completamente optimizado, como si hubiera especificado manualmente todos los tipos de datos.

En este ejemplo, la palabra clave var funciona porque la variable es local, y el tipo de datos var solo funciona con variables locales. Las variables locales se definen dentro de los métodos y solo son accesibles dentro de los métodos, por eso se llaman “locales”.

Para mostrar que var solo se puede usar para variables locales, intente colocarlo fuera del método principal, de esta manera:

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

Cuando pegue el código anterior en jshell, obtendrá el siguiente error:

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

var no está permitido allí porque hello está fuera de un método y ya no se considera local. Por lo tanto, la inferencia de tipos no funciona para variables no locales porque el contexto no se puede usar de manera confiable para detectar el tipo de datos.

Aunque usar var puede ser desafiante y no es obligatorio, es probable que te encuentres con él, por lo que es útil conocerlo.

Palabras Clave Reservadas

Cuando declaras variables en Java, hay una regla más importante que debes conocer. Hay palabras clave reservadas que no puedes usar como nombres de variables. Por ejemplo, no puedes declarar una primitiva de tipo int y nombrarla new de esta manera:

  1. int new = 1;

Si intentas este ejemplo, obtendrás errores de compilación porque new es una palabra clave 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; | ^----------^

La palabra clave new se utiliza para crear nuevos objetos y Java no espera encontrarla en esta posición. En la lista de errores en la salida anterior, la primera parte es la más importante:

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

El error '.class' expected significa que cuando usas la palabra clave new, Java espera que le siga una clase. En este punto, Java no puede interpretar la declaración y le siguen el resto de los errores.

El resto de las palabras clave reservadas, como abstract, continue, default, for, y break, también tienen significados específicos en Java y no se pueden usar como nombres de variables. Puedes encontrar la lista completa de palabras clave reservadas en la página de Palabras clave del lenguaje Java. Incluso si no recuerdas todas las palabras clave reservadas, puedes utilizar errores de compilación para identificar el problema.

Conclusión

En este tutorial, aprendiste sobre los tipos de datos primitivos y de referencia en Java, que es un tema complejo pero esencial. Tómate tu tiempo para practicarlo y repasar los ejemplos más de una vez. Intenta cambiar algunos de los tipos de datos y valores. Presta atención a cuándo se lanzan errores y cuándo no para desarrollar un sentido de ejecución de código exitosa.

Para obtener más información sobre Java, consulta nuestra serie Cómo Codificar en Java.

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