Tabla de Contenidos

03 - Variables y constantes

Una variable es una posición de memoria donde se almacena un valor. Para crear una variable necesitamos darle un nombre simbólico para acceder a ella. Antes de ver como se declaran las variables en Java, vamos a ver un par de distinciones entre la forma de declarar y usar las variables en los lenguajes de programación.

Tipado de las variables

Muchas veces se confunden los términos anteriores y no está clara la distinción entre ambos (incluso en muchos sitios las definiciones de tipado estático/dinámico y fuerte/débil están intercambiadas). En cualquier caso, lo importante es saber cómo se declaran las variables en el lenguaje en el que estamos programando y qué puedo y qué no puedo hacer con ellas.

Por ejemplo, supongamos que defino una variable llamada a de tipo entero y le asigno un valor de 5 (el código no está escrito en ningún lenguaje de programación específico):

var int a
a = 5

Con un lenguaje de tipado estático, la siguiente instrucción no estaría permitida:

a = "Hola"

Mientras que con un lenguaje de tipado dinámico no habría ningún problema.

Por otra parte, defino otra variable b de tipo string (cadena de caracteres):

var int a
var string b
a = 5
b = "Hola"

En un lenguaje con tipado fuerte, la siguiente operación no estaría permitida:

resultado = a + b

En un lenguaje de tipado débil, se convertiría una de las variables automáticamente al tipo de la otra y se realizaría la operación (el resultado, en este caso, sería “5Hola”).

Java es un lenguaje de tipado estático y “débil”. Lo de “débil” lo ponemos entre comillas, ya que, aunque permite ciertas operaciones de conversión automática de variables (por ejemplo, el código anterior no daría error), no lo permite en la mayoría de ocasiones.

Por ejemplo, el siguiente código daría error en Java, ya que hemos definido la variable a de tipo entero y después le asignamos un string (tipado estático):

class Tipado
{
    public static void main(String[] args)
    {
        int a = 5;
        System.out.println(a);
        a = "hola";
        System.out.println(a);
    }
}

javac Tipado.java

Tipado.java:7: error: incompatible types: String cannot be converted to int
        a = "hola";
            ^
1 error

Vamos a probar ahora el tipado débil:

class Tipado2
{
    public static void main(String[] args)
    {
        int a = 5;
        String b = "Hola";
        System.out.println(a+b);
    }
}

Si lo compilamos, vemos que no da ningún error.

javac Tipado2.java

Y, al ejecutarlo, nos muestra por pantalla la concatenación de 5 (lo convierte automáticamente en string) con “Hola”:

java Tipado2

5Hola

Sin embargo, si cambiamos el tipo de la variable b a boolean (sólo puede tomar como valores true o false), sí que da error de compilación:

class Tipado3
{
    public static void main(String[] args)
    {
        int a = 5;
        boolean b = true;
        System.out.println(a+b);
    }
}

javac Tipado3.java

Tipado3.java:7: error: bad operand types for binary operator '+'
        System.out.println(a+b);
                            ^
  first type:  int
  second type: boolean
1 error

Aunque pueda parecer una ventaja el tipado débil, ya que nos permite ahorrar tiempo a la hora de programar, en general no suele ser buena idea ir cambiando el tipo de una variable a lo largo del programa. Que un lenguaje te permita hacer ciertas cosas (como PHP o JavaScript) no implica que tengas que hacerlas. Más vale acostumbrarse a programar bien que tomar “atajos” que a la larga pueden ser perjudiciales.

Definición de variables

Para definir variables en Java solo hay que indicar el tipo de variable y un identificador (nombre de la variable):

int a;
String b;
boolean c;

Podemos aprovechar y asignarle un valor inicial a la variable al mismo tiempo que la definimos, teniendo en cuenta el tipo de variable que es:

int a = 5;

Aunque se pueden crear variables en cualquier parte del código (con la condición de no usarlas antes de que estén creadas), se suelen definir al principio de los bloques de programación (inicio del método main(), inicio de una clase…)
Podemos definir varias variables en una misma línea siempre que sean todas del mismo tipo:
int a, b;
Desde Java 10, las variables locales pueden utilizar inferencia de tipos. De esta forma, no es necesario especificar el tipo dado que Java lo inferirá automáticamente. Debes tener en cuenta que esto solo funciona para variables locales dentro de un método.
var numero = 10;

Tipos de datos primitivos

En Java existen dos grandes grupos de tipos de datos, los tipos de datos primitivos y los tipos de datos referencia. Los tipos de datos primitivos son elementos de información básicos como letras y números, mientras que los tipos de datos de referencia representan instancias de clases.

Los tipos de datos primitivos en Java son:

Tipo Tamaño(bits) Definición Valor por defecto
int 32 Número entero con signo. Rango [-2³¹, 2³¹ - 1] 0
byte 8 Número entero con signo. Rango [-2⁷, 2⁷ - 1] 0
short 16 Número entero con signo. Rango [-2¹⁶, 2¹⁶ - 1] 0
long 64 Número entero con signo. Rango [-2⁶⁴, 2⁶⁴ - 1] 0L
float 32 Número real en coma flotante con precisión simple de 32 bits 0.0f
double 64 Número real en coma flotante con precisión doble de 64 bits 0.0d
char 16 Carácter Unicode ‘u0000’
boolean 1 true o false false

Hay un tipo de dato String para el manejo de cadenas que no es en sí un tipo de dato primitivo. Con el tipo de dato String podemos manejar cadenas de caracteres separadas por dobles comillas.

El String no es un tipo de dato primitivo del lenguaje Java, pero su uso es igual de importante que el de los tipos de datos primitivos. En realidad, una variable de tipo String es un objeto de la clase String. Al contrario de otros lenguajes, las variables de tipo String en Java son inmutables, es decir, no pueden cambiar su valor. En los siguientes temas veremos más sobre los String en Java.

Como hemos dicho, las variables de tipo String son inmutables, con lo que una vez creadas no podríamos añadir ningún otro carácter (o cadena) a nuestro String. Si queremos poder modificar nuestra cadena, podemos utilizar una variable de otra clase muy parecida: StringBuilder
Cuando definimos una variable como tipo float hay que tener cuidado cuando le asignamos valor. Por ejemplo, el siguiente código dará error:

float miVariable;

miVariable = 0.0;

La razón es que, por defecto, Java asigna al valor 0.0 el tipo double. Igual que cuando escribimos un valor entero (1, 145…) le asigna, por defecto, el tipo int.

Si queremos asegurarnos que el valor es de tipo float deberemos añadir una f al final del valor:

float miVariable;

miVariable = 0.0f;

Conversión de tipos (cast)

En ocasiones nos interesa convertir un valor en un tipo de dato diferente. Para hacerlo, podemos utilizar el operador de conversión (cast). Un operador de conversión consta de un tipo de dato dentro de un paréntesis. Se debe colocar un operador de conversión a la izquierda del valor que se desea convertir.

class Cast {

    public static void main(String[] args) {
        int a = 5;
        double b;

        b = (double) a;
        System.out.println(b);

    }
}

5.0

Si es necesario convertir más de un simple valor o una variable, entonces es importante asegurarse de colocar un paréntesis a la expresión entera que se desea convertir.

class Cast {

    public static void main(String[] args) {
        double a = 5;
        double b;
        int resultado;

        resultado = (int) (a * b);
    }
}

En el fragmento de código anterior, si no hubiésemos puesto el paréntesis a la expresión, la conversión se habría aplicado sólo al primer valor a la derecha del operador, es decir, a la variables a, en lugar de hacerlo en la expresión completa.

Ejecuta ahora el siguiente código:

class Cast
{

    public static void main(String[] args) {
        int a =9;
        int b=2;
        float c;

        c = (float) (a/b);
        System.out.println(c);
    }
}

4.0

¿Por qué no está haciendo bien, aparentemente, la conversión a float? El resultado debería ser 4.5, sin embargo la aplicación muestra 4.0. La razón es que la división entre dos enteros devuelve un entero truncado, con lo que en realidad lo que está haciendo es primero dividir 9 entre 2, que es igual a 4 (trunca el número 4.5) y después convertir ese número a float (4.0). Para solucionarlo, lo que debemos hacer es convertir uno de los operandos en float, no toda la expresión. De esta forma, Java convertirá automáticamente el otro operando en float para poder hacer la división entre números decimales:

class Cast
{

    public static void main(String[] args) {
        int a =9;
        int b=2;
        float c;

        c =  (float) a/ b;
        System.out.println(c);
    }
}

4.5

El operador (cast) solo funciona con tipos primitivos. Por ejemplo, si intentamos convertir un String en un int, nos dará un error:
        String cadena = "0";
        int numero;

        numero = (int) cadena;

Cast.java:39: error: incompatible types: String cannot be converted to int
        numero = (int) cadena;
                       ^
1 error

Para intentar convertir datos de tipo String en int podemos utilizar el método Integer.parseInt():

        String cadena = "0";
        int numero;

        numero = Integer.parseInt(cadena);

Igual que la clase String, tenemos clases para tipos primitivos con funcionalidad añadida, como Integer, por ejemplo. Si queremos convertir un String a Integer (en lugar de a int) podemos usar Integer.valueOf(String).

Nombre de las variables

En Java podemos utilizar cualquier palabra como nombre de variable cumpliendo una serie de normas:

Además de las normas anteriores, existen ciertas palabras reservadas en Java que no podemos utilizar como nombre de variables:

abstract continue for new switch
assert default goto package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strctfp volatile
const float native super while

Aparte de las palabras anteriores, true, false y null tampoco se pueden utilizar para uso propio.

Buenas prácticas

Aunque podemos escoger cualquier nombre para nuestras variables (excepto los nombres reservados), deberíamos seguir una serie de normas de estilo para aumentar la legibilidad de nuestro código. Mira el siguiente ejemplo:

class Nombres
{
    public static void main(String[] args)
    {
        int d;

        d = 135;
    }
}

¿A qué hace referencia esa d? Compáralo con el siguiente código:

class Nombres
{
    public static void main(String[] args)
    {
        int distanciaKilometros;

        distanciaKilometros = 135;
    }
}

En el segundo ejemplo, sabemos a primera vista lo que significa esa variable (la distancia en kilométros). Además, si ponemos nombres descriptivos a nuestras variables nos podemos ahorrar comentarios para describir, precisamente, esas variables. Por ejemplo, en el primer trozo de código no es raro ver algo similar a ésto:

class Nombres
{
    public static void main(String[] args)
    {
        //distancia en Km.
        int d;

        d = 135;
    }
}

En el segundo ejemplo, no sería necesario ese comentario, ya que sabemos que representa esa variable con ver su nombre.

Tampoco deberíamos, como norma general, usar abreviaturas, sobre todo cuando puede dar lugar a equívocos (en el ejemplo anterior podríamos haber usado la abreviatura Km, ya que es difícil confundirla con otra palabra). Por ejemplo, en el siguiente código, ¿qué significa hp?

class Nombres
{
    public static void main(String[] args)
    {
        int hp;
    }
}

Podríamos estar refiriéndonos a la hipotenusa de un triángulo, o a un tipo de ordenador, o a puntos horizontales… Está claro que podríamos sacar el significado por el contexto de la clase a la que pertenece, pero siempre es mejor evitar equívocos a la hora de referirnos a las variables.

Otra recomendación es evitar nombres de variables similares con variaciones mínimas. Por ejemplo:

class Nombres
{
    public static void main(String[] args)
    {
        String ValueFromJsonDocumentByKey;
        String ValueFromXMLDocumentByKey;
    }
}

¿Cuánto tiempo se tarda en apreciar la diferencia entre ambas variables? Mejor nombrar las variables de forma que podamos distinguirlas de un vistazo.

Los espacios no están permitidos para nombrar variables en Java. Si necesitas nombrar variables con espacios en su nombre, existen varias opciones, entre las cuales una de la más extendidas es la notación camelCase. Esta notación consiste en nombrar todas las variables en minúscula y, si el nombre consta de varias palabras, empezar siempre la primera en minúscula y el resto de palabras empezarlas en mayúsculas, sin dejar espacio ni guiones entre ellas. Por ejemplo:

int altura;
int alturaEdificio;
int alturaEdificioGubernamental;

Si quieres ampliar información sobre cómo nombrar variables (y funciones, y clases…) echa un vistazo al libro de Robert C. Martin Código limpio.

Otra buena práctica consiste en nombrar nuestros elementos (variables, constantes, clases….) en inglés. Ten en cuenta que en un proyecto pueden trabajar personas que hablen diferentes idiomas, con lo que si usamos un idioma común (en este caso, el inglés), podemos evitar confusiones o tener que traducir nombres para ver qué significan.

Uso de las variables

Una vez hemos definido una variable, ya podemos usarla para operar con ella. Por ejemplo, podemos asignar un valor a la variable con símbolo igual( = ).

class UsoVariables
{
    public static void main(String[] args)
    {
        int a;

        a = 5;
        System.out.println("La variable a vale " + a);
        a = 19;
        System.out.println("Ahora, la variable a vale " + a);
    }
}

La variable a vale 5
Ahora, la variable a vale 19

También podemos usar operadores para hacer cálculos con ella (en temas posteriores ya veremos los distintos operadores para cada tipo de variable). Por ejemplo, podemos usar los operadores aritméticos típicos para sumar, restar, multiplicar y dividir variables. También usaremos la concatenación (unión) de cadenas con el operador +.

class UsoVariables
{
    public static void main(String[] args)
    {
        int a = 8;
        int b = 4;
        int resultado;

        resultado = a + b;
        System.out.println("a + b = " + resultado);
        resultado = a - b;
        System.out.println("a - b = " + resultado);
        resultado = a + b;
        System.out.println("a + b = " + resultado);
        resultado = a / b;
        System.out.println("a / b = " + resultado);
    }
}

a + b = 12
a - b = 4
a + b = 12
a / b = 2

Fíjate como Java vuelve a utilizar el tipado débil para convertir un int (la variable resultado) en un String y poder concatenarlo con el texto que mostramos por pantalla

Constantes

Las constantes hacen referencia a posiciones de memoria donde se almacena un valor que no cambia a lo largo de la ejecución del programa. La diferencia con las variables es obvia; mientras éstas (las variables) pueden cambiar su valor a lo largo del programa, las constantes, como su nombre indica, permanecen con su valor constante durante todo el proceso.

Para definir constantes en Java, simplemente añadimos la palabra reservada final al comienzo de la declaración de una variable:

final float PI = 3.1416f;

Por convención, se suelen nombrar las constantes en mayúsculas y separando las palabras con un guión bajo (_).

Si intentamos cambiar el valor de una variable en mitad del código, nos dará un error de compilación:

class Constantes
{
    public static void main(String[] args)
    {
        final float PI = 3.1416f;

        PI = 3.14f;
    }
}

Constantes.java:7: error: cannot assign a value to final variable PI
        PI = 3.14f;
        ^
1 error

Aunque son menos habituales en una aplicación que las variables, hay situaciones donde nos facilitan mucho las cosas. Por ejemplo, mira el siguiente código:

class Constantes
{
    public static void main(String[] args)
    {
        float distancia;
        float retardoPropagacion;

        distancia = 250.0f;
        retardoPropagacion = distancia / 299792.458f;
        System.out.println(retardoPropagacion);
    }
}

¿Qué significado tiene ese 299792.458f? Igual puede ser muy claro para los expertos en ciencia, pero a la mayoría de gente nos costaría encontrar el sentido. Compara el código anterior con el siguiente:

class Constantes
{
    public static void main(String[] args)
    {
        final float VELOCIDAD_LUZ = 299792.458f;
        float distancia;
        float retardoPropagacion;

        distancia = 250.0f;
        retardoPropagacion = distancia / VELOCIDAD_LUZ;
        System.out.println(retardoPropagacion);
    }
}

Ahora ya tenemos claro que ese valor corresponde a la velocidad de la luz en el vacío. Además, si queremos redondear el valor a 300000 km/s, sólo tenemos que cambiarlo en un sitio: la declaración de la constante, mientras que en el primer caso deberíamos buscar en todas las líneas de nuestro programa donde hemos utilizado ese valor y modificarlo.

Entrada/salida de datos

Aunque en una aplicación web la entrada y la salida de datos no se hace a través de consola, por ahora vamos a utilizar un par de métodos bastante útiles para dicho cometido.

System.out.println y System.out.print

Para mostrar algo por la salida predeterminada (en nuestro caso, la pantalla) utilizamos los métodos System.out.println o System.out.print. La diferencia es que el primero imprime el texto que le pasemos más un intro al final y el segundo no.

System.out.println("Este método escribe un texto con un intro al final");
System.out.println("Si escribo un nuevo texto a continuación se escribirá en una nueva línea");

Este método escribe un texto con un intro al final
Si escribo un nuevo texto a continuación se escribirá en una nueva línea

System.out.print("Este método escribe un texto");
System.out.println("Si escribo un nuevo texto se escribirá a continuación del primero");

Este método escribe un textoSi escribo un nuevo texto se escribirá a continuación del primero

La clase Scanner

Java proporciona una clase precompilada llamada Scanner, que permite obtener entradas ya sea del teclado o de un archivo. Por ahora nos vamos a centrar solo en recoger datos a través del teclado.

Para utilizar la clase, primero debemos crear una variable de tipo Scanner.

class EntradaSalida {

    public static void main(String[] args) {
        Scanner reader;
    }
}

Si compilamos el código anterior, veremos como Java nos da un error de compilación:

EntradaSalida.java:8: error: cannot find symbol
        Scanner reader;
        ^
  symbol:   class Scanner
  location: class EntradaSalida
1 error

Esto se debe a que la clase Scanner no forma parte del conjunto fundamental de clases de Java, con lo que primero tenemos que importarla.

import java.util.Scanner;

class EntradaSalida {

    public static void main(String[] args) {
        Scanner reader;
    }
}

No te preocupes si ahora no entiendes el concepto de clases o importación. Lo veremos con detalle cuando lleguemos a la POO (programación orientada a objetos). Por ahora, lo único que te tiene que preocupar es importar la clase con la línea anterior (import java.util.Scanner) en la primera línea de tu archivo .java y crear una variable de ese tipo (Scanner nombre_escaner).

Si compilamos el código ahora, vemos que no tenemos ningún error. Lo siguiente será crear un nuevo objeto de la misma clase (Scanner) y asignarselo a la variable creada (lo normal es hacerlo al mismo tiempo que definimos la variable):

import java.util.Scanner;

class EntradaSalida {

    public static void main(String[] args) {
        Scanner reader = new Scanner(System.in);
    }
}

De nuevo, no te preocupes por ahora por no entender bien como funciona el código. En estos primeros programas, siempre vamos a leer datos de teclado de la misma forma (importando la clase Scanner, creando una variable de ese tipo y asignándole un objeto nuevo de esa clase), con lo que puedes copiar y pegar ese código en tus nuevos archivos aunque no tengas muy claro lo que hace (cuando lleguemos a la parte de POO ya entederás todo sin problema).

Ahora ya estamos listos para leer entradas a través del teclado. La clase Scanner tiene algunos métodos que nos sirven para eso, entre ellos:

Vamos a ver algunos ejemplos. En este primer código usamos nextLine() para leer la siguiente línea que escriba el usuario (hasta que entre un intro).

import java.util.Scanner;

class EntradaSalida {

    public static void main(String[] args) {
        String cadenaCaracteres;
        Scanner reader = new Scanner(System.in);

        System.out.println("Escribe una línea de texto:");
        cadenaCaracteres = reader.nextLine();
        System.out.println("Texto leído: " + cadenaCaracteres);
    }

}

java EntradaSalida

Escribe una línea de texto:
Hola mundo
Texto leído: Hola mundo

Vamos a ver ahora como leer un número entero:

import java.util.Scanner;

class EntradaSalida {

    public static void main(String[] args) {
        String cadenaCaracteres;
        Scanner reader = new Scanner(System.in);

        System.out.println("Escribe un número");
        numeroEntero = reader.nextInt();
        System.out.println("Número leído: " + numeroEntero);    
    }

}

Escribe un número
4
Número leído: 4

Como ves, con estos métodos podemos leer y almacenar en una variable lo que escriba el usuario en el teclado.

Ten en cuenta que si encadenas varias lecturas por teclado, puede que no obtengas el resultado esperado. Por ejemplo, prueba este código:
System.out.println("Escribe un número entero: ");
numeroEntero = reader.nextInt();
System.out.println("El número es: " + numeroEntero);
System.out.println("Ahora escribe una cadena: ");
cadenaCaracteres = reader.nextLine();
System.out.println("La cadena es: " + cadenaCaracteres);

Si lo ejecutas, la salida será:

Escribe un número entero: 
4
El número es: 4
Ahora escribe una cadena: 
La cadena es: 

Parece que la clase Scanner no ha hecho una pausa en la segunda lectura (nextLine()) y no espera a que el usuario introduzca los datos. Esto es debido a que cuando el usuario escribe un número, finaliza con un intro, con lo que la clase Scanner interpreta eso como una nueva entrada y se la asigna a la variable cadenaCaracteres. Esto pasa porque nextLine() lee toda la línea incluyendo el salto de línea, mientras que las otras funciones no lo hacen. Así que nextLine() escaneará una cadena vacía si es que hay un \n (intro) en el búfer.

Una posible solución consiste en vaciar el búfer de entrada antes de llamar al método nexLine() llamando, precisamente al mismo método sin asignarlo a ninguna variable:

System.out.println("Escribe un número entero: ");
numeroEntero = reader.nextInt();
System.out.println("El número es: " + numeroEntero);
reader.nextLine();
System.out.println("Ahora escribe una cadena: ");
cadenaCaracteres = reader.nextLine();
System.out.println("La cadena es: " + cadenaCaracteres);

Si ejecutamos ahora la aplicación, el programa ya funcionará como toca:

Escribe un número entero: 
4
El número es: 4
Ahora escribe una cadena: 
Hola mundo
La cadena es: Hola mundo

Ejercicios

Ejercicio 1.a

Crea un programa que calcule el área de un triángulo con base 5 y altura 10 (recuerda que la fórmula del área es (base * altura) / 2). Tanto la base como la altura deberán ser constantes de tipo entero y el área una variable del mismo tipo (entero).

Ejercicio 1.b

Modifica el tipo de la variable area a float. Cambia el valor de la altura por 9 y ejecuta el programa de nuevo. Calcula el área a mano y comprueba si coincide con la salida del programa. Haz los cambios necesarios sin modificar el tipo de las constantes para que ambas respuestas sean iguales.

Ejercicio 1.c

Modifica el tipo de las constantes en el ejercicio 1a para que salga la respuesta correcta.

Ejercicio 1.d

Haz que la altura y la base las introduzca el usuario por teclado.

Ejerccio 2.a

Crea la constante NUMBER de tipo entero con un valor de 8. Muestra por pantalla la tabla de multiplicar de ese número.

Ejercicio 2.b

Modifica el ejercicio anterior para que el resultado sea un float sin modificar el tipo de la constante.

Ejercicio 2.c

Haz que la tabla de multiplicar se muestre del número introducido por el usuario por el teclado.