2-Tipos y alcance de variables. Casting. Estructuras de
programación. Clases envoltorio
Una variable es un contenedor de bits que representan a un valor. Se emplean
para almacenar datos que pueden cambiar durante la ejecución de un programa. En
función de los datos que almacenan se
clasifican en:
- Variables primitivas: almacenan datos numéricos, valores lógicos o
caracteres.
- Variables referenciadas: asociadas a objetos o instancias de una
clase. Por ejemplo, para almacenar cadenas de caracteres se empleará una variable referenciada
asociada a la clase String, para almacenar información sobre la fecha actual,
otra asociada a la clase Date, etc. Se estudiarán más adelante.
Además de estos dos tipos de variables se estudiarán los arrays de variables
primitivas y de variables referenciadas. Un array, como se verá en el tema
correspondiente, es una variable referenciada asociada a la clase Object (clase madre
de todos los objetos Java).
Se va a profundizar un poco más en el concepto de variable: como se ha
comentado anteriormente, no es nada más que
un contenedor de bits que representan a un valor. Ocurre lo siguiente:
- En el caso de variables primitivas, los bits representan un número entero
que coincide con el valor de la variable, con lo que se va a trabajar a lo
largo del programa. Por ejemplo, se tienen variables de tipo byte (utilizan 8
bits en memoria) que pueden almacenar números enteros comprendidos entre -128
y 127, de tipo int (utilizan 32 bits en memoria) para almacenar enteros entre,
aproximadamente, -2150 millones y 2150 millones, de tipo float para números
decimales, etc.

- En el caso de variables referenciadas o asociadas a objetos, los bits
representan un numerajo que permite acceder al valor de la variable, es decir,
al objeto, pero no es el valor u objeto en sí.

Todos los nombres empleados para hacer referencia a variables deben cumplir lo siguiente:
- Su primer carácter debe ser una letra, el símbolo del subrayado o el
carácter dólar $.
- No son válidos las palabras reservadas de Java.
- No se admiten espacios en blanco.
- Son case-sensitive (sensibles a mayúsculas).
Aparte de estas normas conviene que los nombres de las variables indiquen qué
dato almacenan con el fin de facilitar la lectura del programa y, por otra
parte, si un nombre tiene más de dos palabras la primera letra de la primera
palabra irá en minúscula, la primera letra de la segunda palabra en mayúscula,
ídem con la tercera y así sucesivamente.
Ejemplo:
int miVariableEntera=100;
Son variables que almacenan números enteros. Se pueden dividir en los
siguientes tipos:

|
NOTA: el tipo de variable en que se almacena por defecto un numero entero
es int. El valor por defecto asociado a cualquier variable entera no
inicializada es 0.
|
Son variables que almacenan datos numéricos con decimales. Se pueden
dividir en los siguientes tipos:

|
NOTA: el tipo de variable en que se almacena por defecto un numero
decimal es double. El valor por defecto asociado a cualquier variable real no
inicializada es 0.0.
|
Son variables que almacenan dos posibles valores: true o false. No se
corresponden con ningún valor numérico.
Ejemplo:
boolean tienesCalor=true;
|
NOTA: el valor por defecto asociado a cualquier variable booleana no
inicializada es false.
|
Son variables que almacenan caracteres individuales (letra, numero,
signo ?, etc...). El carácter que se inicializa debe ir entre apóstrofes o
comillas simples 'a'.
El código de caracteres empleado por Java es Unicode y recoge los
caracteres de prácticamente todos los idiomas importantes del mundo (son unos
65.536). Los caracteres Unicode del alfabeto occidental corresponden a
los primeros 256 enteros; es decir van desde [0, 255].
A cada carácter le corresponde unívocamente un número entero perteneciente
al intervalo [0, 65536] o a [0, 255] si se trabaja sólo con el alfabeto
occidental. Por ejemplo, la letra ñ es el entero 164. Más adelante se verá que
el casting entre variables primitivas enteras y la variable char está
permitido.
Ejemplo:
char miCaracter='n';
char miCaracter1=110; (ídem antes, pero mediante el
entero que le corresponde según Unicode)
char miCaracter2='\u006E'; (ídem antes, pero según notación Unicode. La
notación Unicode, en general, es así: \uXXXX siendo X un dígito o cifra
fundamental del sistema de numeración hexadecimal (0,1,2,...,9,A,B,...,F))
Asociado a este tipo de variable se tienen las secuencias de escape. Se
emplean para representar caracteres especiales (por ejemplo, unas comillas
dentro de una instrucción que exige una cadena entrecomillada) y caracteres no
imprimibles como el tabulador, salto de línea, etc. Van precedidos de la
contrabarra. Algunos de ellos se detallan en la tabla siguiente:

|
NOTA: el valor por defecto asociado a cualquier variable char no
inicializada es '\u0000'
|
Ejemplo: todos los códigos de este tema se guardarán en c:\cursojava\tema2

Código fuente
Por consola:
Comienza programa
El valor de tengoCalor es true
El valor de letra es n
El valor de letra1 es n
El valor de letra2 es n
Eso es
un mensaje
de tres lineas
Me llamo "Jesus"
Me llamo \Jesus\
Asociadas a objetos o instancias de una clase. Se irán estudiando durante
el curso.
Casting o transformaciones de tipo
El casting es un procedimiento para transformar una variable primitiva de
un tipo a otro, o transformar un objeto de una clase a otra clase siempre y
cuando haya una relación de herencia entre ambas (este último casting es el
más importante y se verá más adelante).
Dentro del casting de variables primitivas se distinguen dos clases:
- Implícito: no se necesita escribir código para que se lleve a cabo. Ocurre
cuando se realiza una conversión ancha (widening casting), es decir, cuando se
coloca un valor pequeño en un contenedor grande.
Ejemplo 1:

Ejemplo 2: similar al anterior.

En cambio,

- Explícito: sí es necesario escribir código. Ocurre cuando se realiza
una conversión estrecha (narrowing casting), es decir, cuando se coloca un valor
grande en un contenedor pequeño. Son susceptibles de pérdida de datos.
Ejemplo 1:

|
NOTA: si se sustituyera la primera línea int num1=100 por int
num1=1000000, el código compilaría bien, pero habría pérdida de datos, pues el 1000000 se
sale del rango de short [-32768, 32767]. Al mostrar por consola el valor se
obtendría un resultado incongruente. |
Ejemplo 2:

Ejemplo 3: continuación del Ejemplo 2 del casting implícito
Para que la línea

compile debe hacerse un casting explícito a long

pero no

porque, en la línea anterior, 10000000000 es considerado int, mientras que en
las de arriba, double.
Dicho esto, se va a analizar un ejemplo un tanto extraño.
Ejemplo extraño:

Dado que cualquier entero, por defecto, se almacena en un int (4 bytes), con
la línea anterior se pretende colocar un valor grande (el int 10) en un
contenedor pequeño (una primitiva de tipo byte con capacidad para 1 byte). Esto,
según lo expuesto anteriormente, precisa de casting explícito.

Pero, resulta que no hace falta, ya que el compilador, cuando se trabaja con
enteros, digamos que, provoca un "casting implícito contranatura" y transforma
automáticamente a byte el int 10. Ocurriría lo mismo si se trabajara con short y
char.
Lo que pasa (y esto es lo que resulta un tanto extraño) es que no ocurre lo
anterior con los decimales: por eso, una línea como

provoca error de compilación. Recordar que cualquier decimal, por defecto, se
almacena en un double (8 bytes) y que un tipo float tiene capacidad para 4 bytes. En los
decimales, el compilador no fuerza el casting implícito contranatura. De ahí que
sea necesario un casting explícito a float para evitar el fallo de compilación.
|
NOTA: quizá se evitarían estas situaciones, si el compilador no forzara el casting
implícito contranatura a byte, short o char de un int y provocara error de compilación, del mismo modo que
cuando se declara un float y no se castea explícitamente. Pero, de momento, esto
es lo que hay. |
Código de partida para explicar el casting entre variables primitivas que
almacenan datos numéricos:

Las líneas 3 y 4 almacenan al número 10 mediante una variable primitiva
de tipo byte vía "casting implícito contranatura", el 3000 mediante una de tipo short,
también vía "casting implícito contranatura". Lo más intuitivo es
definirlas mediante un casting explícito, pero tal y como están también se
puede.
Supuestamente, la línea 5, almacena el 3000000000 mediante una variable de tipo long,
vía casting implícito, pero es falso. Ocurre lo que se ha comentado
en el Ejemplo 2 del casting implícito: 3000000000 no es considerado como long sino como int y 3000 millones no
pertenece al rango asociado a int (aprox. [-2150 millones, 2150 millones]). Si
se intenta compilar, se produciría error.
Supuestamente, la línea 6, almacena el 256.5 mediante una variable de tipo float
(ocupa 4 bytes en memoria), vía "casting implícito contranatura", pero, como se
ha comentado en el Ejemplo extraño, es falso,
ya que en decimales nunca se produce. Debe castearse explícitamente a float.
El código correcto sería:

Código fuente
- Una cuestión a tener en cuenta relacionada con el casting entre
variables primitivas es la siguiente:
En Java se realizan automáticamente conversiones de una variable primitiva
de un tipo a otra de otro de igual o mayor precisión.
La precisión depende del número de bytes ocupados en memoria y del rango de
valores asociado: a mayor número de bytes ocupados, mayor precisión y mayor
rango asociado. Así, pasar de byte a short, de short a int, de byte a int, .
es automático; en definitiva: pasar de una variable primitiva de un tipo de la
cadena de la siguiente línea a otra que se encuentre a su derecha es
automático.
byte-->short-->int-->long-->float-->double
Así, por ejemplo, si un método necesita un long como argumento y se le
pasa un entero perteneciente al rango de int, promociona automáticamente a long
y no es necesario casting.
En cambio, si se le pasa un entero que se sale fuera del rango de int, es
necesario realizar un casting para que la llamada al método no provoque error al
compilar.
Un ejemplo de esto ocurre con el método estático de java.lang.Thread "void sleep(long
retardo)" que introduce un retardo en la ejecución del código, coincidente
con el entero, en milisegundos, que se le pasa al argumento. Este método se estudiará más adelante.

Por consola:
Hola
-- Después de tres segundos --
Adios
En cambio, si se sustituye
Thread.sleep(3000) por Thread.sleep(3000000000)
no compila ya que el entero que se le pasa no pertenece al rango de int y no
puede promocionar a long automáticamente.
Para que compile es necesario hacer un casting explícito:
Thread.sleep(3000000000L)
- Para finalizar con el casting entre primitivas, conviene tener en cuenta lo
siguiente:
- No es posible realizar casting entre una variable primitiva booleana y
cualquier otra variable primitiva.
- Sí es posible realizar casting entre una variable primitiva char y una
variable primitiva que almacene enteros.
Ejemplo:

Código fuente
Por consola:
ñ
ñ
|