0% encontró este documento útil (0 votos)
19 vistas49 páginas

Objetos en Java: Creación y Gestión

El documento introduce conceptos básicos del lenguaje Java, centrándose en los objetos como instancias de clases que tienen estado y comportamiento. Se explica la creación, utilización y destrucción de objetos, destacando la gestión automática de memoria a través del garbage collector. Además, se menciona el método finalize() para realizar acciones antes de la recolección de memoria, aunque su ejecución no está garantizada.
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
19 vistas49 páginas

Objetos en Java: Creación y Gestión

El documento introduce conceptos básicos del lenguaje Java, centrándose en los objetos como instancias de clases que tienen estado y comportamiento. Se explica la creación, utilización y destrucción de objetos, destacando la gestión automática de memoria a través del garbage collector. Además, se menciona el método finalize() para realizar acciones antes de la recolección de memoria, aunque su ejecución no está garantizada.
Derechos de autor
© All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Introducción al lenguaje Java

CONCEPTOS BASICOS DE JAVA


Objetos
Antes de entrar en el estudio de las Clases en Java, hay que presentar a los objetos,
que son las instanciaciones de una clase. En este caso, haciendo un símil con la vida real,
estamos colocando el carro delante del caballo, pero se supone que el lector tiene el bagaje
suficiente como para entender lo que se expondrá, sin necesidad de conocer en
profundidad lo que es y cómo funciona una clase. Además, esto permitirá obviar muchas
cosas cuando se entre en el estudio de las clases. Y, en última instancia, siempre puede el
lector pasar a la sección siguiente y luego volver aquí.
Un objeto es la instanciación de una clase y tiene un estado y un funcionamiento.
El estado está contenido en sus variables (variables miembro), y el funcionamiento viene
determinado por sus métodos. Las variables miembro pueden ser variables de instancia o
variables de clase. Se activa el funcionamiento del objeto invocando a uno de sus métodos,
que realizará una acción o modificará su estado, o ambas cosas. Cuando un objeto ya no se
usa en C++, se destruye y la memoria que ocupaba se libera y es devuelta al Sistema.
Cuando un objeto ya no se usa en Java, simplemente se anula. Posteriormente, el
reciclador de memoria (garbage collector) puede recuperar esa memoria para devolverla al
Sistema.
Las afirmaciones anteriores son un compendio de lo que se puede esperar de un
objeto. A continuación se verá más en detalle cómo es la creación, el uso y la destrucción
de un objeto. Se utilizará C++ como punto de comparación para dar el salto a Java, en
deferencia a los muchos lectores que conocen los entresijos de los objetos en C++, de ahí
que también que se inicie el estudio de la vida de los objetos suponiendo que se dispone de
acceso a clases ya existentes o que se tiene conocimiento de cómo crearlas, aunque
posteriormente se vuelva sobre ello.
Creación de Objetos
Un objeto es (de nuevo) una instancia de una clase. Tanto en Java como en C++, la
creación de un objeto se realiza en tres pasos (aunque se pueden combinar):
 Declaración, proporcionar un nombre al objeto
 Instanciación, asignar memoria al objeto
 Inicialización, opcionalmente se pueden proporcionar valores iniciales a las variables
de instancia del objeto
Cuando se trata de Java, es importante reconocer la diferencia entre objetos y
variables de tipos básicos, porque en C++ se tratan de forma similar, cosa que no ocurre en
Java.
Tanto en Java como en C++ la declaración e instanciación de una variable de tipo
básico, utiliza una sentencia que asigna un nombre a la variable y reserva memoria para
almacenar su contenido:
int miVariable;
En los dos lenguajes se puede inicializar la variable con un valor determinado en el
momento de declararla, es decir, podemos resumir los tres pasos anteriormente citados de
declaración, instanciación e inicialización, en una sola sentencia:
int miVariable = 7;
Y, lo más importante, este sucede en tiempo de compilación.
Introducción al lenguaje Java

Java no permite la instanciación de variables de tipos básicos en memoria


dinámica, aunque hay una serie de objetos que coinciden con los tipos básicos y que
pueden utilizarse para este propósito. Java tampoco permite la instanciación de objetos en
memoria estática.
Cuando C++ instancia un array de objetos en memoria dinámica, lo que hace es
instanciar el array de objetos y devolver un puntero al primer objeto. En Java, cuando se
instancia un array (que siempre ha de ser en memoria dinámica), se instancia un array de
referencias, o punteros (llámese como se quiera) a los objetos. En Java, es necesario
utilizar múltiples veces el operador new para instanciar un array de objetos, mientras que
en C++ sólo es necesario su uso una única vez. El siguiente código de ejemplo, ,
corresponde a la aplicación Java que realiza el mismo trabajo que el programa C++ visto
antes, pero con las restricciones que se acaban de enumerar. Se ha incorporado la
presentación en pantalla no sólo del dato al que apunta un objeto, sino también de la
dirección en la que se encuentra almacenado ese dato (la dirección del objeto). La salida
del programa sería:
C:\> java java501
miVariableInt contiene 6
miPunteroObj contiene java501@208741
miPunteroObj apunta a 12
miArrayPunterosObj contiene java501@20875f java501@208760 java501@208761
miArrayPunterosObj apunta a 13 14 15
El formato en que se presenta un objeto en Java, cuando el compilador no conoce
el tipo de que se trata, consiste en el identificador de la clase y el valor hexadecimal de la
dirección.
En Java no siempre es necesaria la declaración de un objeto (darle un nombre). En
el siguiente ejemplo, , se instancia un nuevo objeto que se usa en una expresión, sin
previamente haberlo declarado.
import [Link].*;

class java502 {
public static void main( String args[] ) {
[Link]( new Date() );
}
}
La inicialización de un objeto, se puede realizar utilizando las constantes de la
clase, de forma que un objeto de un mismo tipo puede ser declarado e inicializado de
formas diferentes.
Utilización de Objetos
Una vez que se tiene declarado un objeto con sus variables y sus métodos,
podemos acceder a ellos para que el uso para el que se ha creado ese objeto entre en
funcionamiento.
Para acceder a variables o métodos en Java, se especifica el nombre del objeto y el
nombre de la variable, o método, separados por un punto (.).
[Link]( dateContainer( "29/07/97" );
Destrucción de Objetos
El programador Java no necesitará preocuparse más de devolver la memoria, ese
bien tan preciado, al sistema operativo. Eso se realizará automáticamente de la mano del
reciclador de basura, que también es bien conocido como garbage collector.
Introducción al lenguaje Java

Sin embargo, hay una mala noticia. Y es que en Java no hay soporte para algo
parecido a un destructor (en C++), que pueda garantizar su ejecución cuando el objeto deje
de existir. Por lo tanto, excepto la devolución de memoria al sistema operativo; la
liberación de los demás recursos que el objeto utilizase vuelve a ser responsabilidad del
programador. Pero, algo se ha ganado, ya no debería aparecer más el fatídico mensaje de
"No hay memoria suficiente", o su versión inglesa de "Out of Memory".
Liberación de Memoria
El único propósito para el que se ha creado el reciclador de memoria es para
devolver al sistema operativo la memoria ocupada por objetos que no es necesaria. Un
objeto es blanco del garbage collector para su reciclado cuando ya no hay referencias a ese
objeto. Sin embargo, el que un objeto sea elegido para su reciclado, no significa que eso se
haga inmediatamente.
El garbage collector de Java comprueba todos los objetos, y aquellos que no
tienen ninguna (esto es, que no son referenciados por ningún otro objeto) son marcados y
liberada la memoria que estaban consumiendo.
El garbage collector es un thread, o hilo de ejecución, de baja prioridad que puede
ejecutarse síncrona o asíncronamente, dependiendo de la situación en que se encuentre el
sistema Java. Se ejecutará síncronamente cuando el sistema detecta poca memoria o en
respuesta a un programa Java. El programador puede activar el garbage collector en
cualquier momento llamando al método [Link](). Se ejecutará asíncronamente cuando
el sistema se encuentre sin hacer nada (idle) en aquellos sistemas operativos en los que
para lanzar un thread haya que interrumpir la ejecución de otro, como es el caso de
Windows 95/NT.
No obstante la llamada al sistema para que se active el garbage collector no
garantiza que la memoria que consumía sea liberada.
A continuación se muestra un ejemplo sencillo de funcionamiento del garbage
collector:
String s; // no se ha asignado memoria todavía
s = new String( "abc" ); // memoria asignada
s = "def"; // se ha asignado nueva memoria
// (nuevo objeto)
Más adelante veremos en detalle la clase String, pero una breve descripción de lo
que hace esto es; crear un objeto String y rellenarlo con los caracteres "abc" y crear otro
(nuevo) String y colocarle los caracteres "def".
En esencia se crean dos objetos:
 Objeto String "abc"
 Objeto String "def"
Al final de la tercera sentencia, el primer objeto creado de nombre s que contiene
"abc" se ha salido de alcance. No hay forma de acceder a él. Ahora se tiene un nuevo
objeto llamado s y contiene "def". Es marcado y eliminado en la siguiente iteración del
thread reciclador de memoria.
El Método finalize()
Antes de que el reciclador de memoria reclame la memoria ocupada por un objeto,
se puede invocar el método finalize(). Este método es miembro de la clase Object, por lo
tanto, todas las clase lo contienen. La declaración, por defecto, de este método es:
protected void finalize() {
Introducción al lenguaje Java

}
Para utilizar este método, hay que sobrecargarlo, proporcionando el código que
contenga las acciones que se desee ejecutar antes de liberar la memoria consumida por el
objeto. Aunque se puede asegurar que el método finalize() se invocará antes de que el
garbage collector reclame la memoria ocupada por el objeto, no se puede asegurar cuando
se liberará esa memoria; e incluso, si esa memoria será liberada.
El ejemplo java504 trata de ilustrar todo esto.
/**
* Este ejemplo intenta demostrar que la actuacion del reciclador de
* de memoria, o garbage collector, tiene un funcionamiento asincrono e
* [Link] instancian un numero de objetos que son inmediatamente
* seleccionados para su reciclado.
* Se instancia mas de 1000 objetos antes de que llegue la recoleccion
* y terminacion del programa.
*/
class Obj {
static boolean recicladorActivo = false;
static boolean flagSalida = false;
static int objCreados = 0;
static int objFinalizados = 0;
static int objFinalizadosConSalidaATrue = 0;
int numObjetos;
int arrayEnteros[]; // Array utiliza para consumir memoria
// Constructor parametrizado
Obj( int contador ) {
arrayEnteros = new int[500];
numObjetos = contador;
objCreados++;
// La siguiente sentencia indica cuando se crea el objeto #1000
if( numObjetos == 1000 )
[Link]( "Creado el objeto #1000" );
}
// El siguiente metodo sobreescrito se ejecuta de forma asincrona
// en relacion con el programa principal. Sera llamado por el
// reciclador de memoria una vez por cada uno de los objetos
// antes de que la memoria ocupada por ese objeto sea reclamada

protected void finalize() {


// Contador del numero de objetos finalizados
objFinalizados++;
// En la primera llamada al metodo finalize(), fijamos ela
bandera
// que indica que el reciclador de memoria ha entrado en accion
if( !recicladorActivo ) {
recicladorActivo = true;
[Link](
"Iniciado el reciclado en el Objeto numero "
+ numObjetos );
}
// Se fija la bandera de salida cuando el reciclador finaliza el
// objeto #1000. Esto no tendra impacto alguno sobre el programa
// principal hasta que el hilo principal tome el control desde el
// hilo del reciclador. El orden en que se finalizan los objetos,
// no es necesariamente el mismo orden en el que se han creado,
// sino que el orden y el momento en que se recicla un objeto,
// viene determinado por el reciclador de memoria
if( numObjetos == 1000 ) {
Introducción al lenguaje Java

[Link](
"Finalizado el objeto #1000. Fija la salida a true" );
flagSalida = true;
}
// Contador de los objetos finalizados despues de haber fijado
// la bandera de salida y antes de que el control sea devuelto
// al hilo principal del programa
if( flagSalida )
objFinalizadosConSalidaATrue++;
}
}
public class java504 {
volatile static int objContador = 0;
public static void main( String args[] ) {
// El programa termina en algun momento despues de que el metodo
// finalize() llamado por el reciclador, fije la bandera de
// salida a true, pero no antes de que el hilo principal de
// ejecucion del programa retome el control desde el hilo de
// ejecucion del reciclador. Los objetos que son creados en
// este bucle no estan asignados a una variable de referencia y
// ademas son marcados inmediatamente para su reciclado
while( ![Link] ) {
// Se van instanciando nuevos objetos mientras la bandera
// de salida este a falso
new Obj( objContador++ );
}
[Link]( "Objetos totales creados = "
+ [Link] );
[Link]( "Objetos totales finalizados = "
+ [Link] );
[Link]( + [Link] +
" objetos fueron finalizados despues de fijar la Salida" );
}
}

Este programa instancia una gran cantidad de objetos y monitoriza la actividad del
garbage collector. En concreto, el programa instancia los objetos y los hace seleccionables
inmediatamente por el garbage collector para su reciclado (no asignándolos a variable
alguna, con lo cual no hay ninguna referencia a ellos, que es la condición para que sean
marcados para su reciclaje). Cuando el objeto 1000 es finalizado, se fija el flagSalida, que
está monitorizado en el thread principal, para terminar el programa cuando sea true. Sin
embargo, si se observa la ejecución, el garbage collector finaliza 86 objetos más, después
de haberse fijado el flagSalida a true antes de que se devuelva el control al thread principal
y pueda concluir el programa. La salida por pantalla de la ejecución sería:
%java java504
Iniciado el reciclado en el Objeto numero 0
Creado el objeto #1000
Finalizado el objeto #1000. Fija la salida a true
Objetos totales creados = 1087
Objetos totales finalizados = 1086
86 objetos fueron finalizados despues de fijar la Salida
Si se experimenta con distintos valores para el número de objetos creados, se podrá
observar un funcionamiento diferente del garbage collector y además, de forma
impredecible.
Reiteramos que el método finalize() no es un destructor al uso como en C++.
Nunca se puede asegurar cuando se ejecutará, o incluso, si será ejecutado. Por tanto, si se
Introducción al lenguaje Java

necesita que un objeto libere los recursos que estaba consumiendo, antes de que el
programa finalice, no se puede confiar esa tarea al método finalize(), sino que hay que
llamar a métodos que realicen esas tareas explícitamente.
Además del método [Link](), que permite invocar al garbage collector en
cualquier instante, también puede se posible forzar la ejecución de los métodos finalize()
de los objetos marcados para reciclar, llamando al método:
[Link]();
Pero, la circunstancia explicada anteriormente sigue vigente, es decir, nadie
garantiza que esto libere todos los recursos y, por lo tanto, que se produzca la finalización
del objeto.
Si se modifica el programa del ejemplo anterior, para incluir la finalización, tal
como se muestra en el ejemplo , se podrá observar esta circunstancia. La clase principal
del ejemplo quedaría como muestra el trozo de código que se reproduce a continuación.
public class java505 {
volatile static int objContador = 0;

public static void main( String args[] ) {


while( objContador < 1200 ) {
// Solicita el reciclado cada 120 objetos
if( (objContador % 120 ) == 0 )
[Link]();
new Obj( objContador++ );
}
[Link]( "Objetos totales creados = " +
[Link] );
[Link]( "Objetos totales finalizados = " +
[Link] );
// Intenta forzar la finalizacion de los objetos
[Link]( "Intenta forzar la finalizacion." );
[Link]();
[Link]( "Objetos totales finalizados = " +
[Link] );
}
}
La salida del programa por pantalla en esta ocasión sería:
%java java505
Iniciado el reciclado en el Objeto numero 0
Creado el objeto #1000
Finalizado el objeto #1000. Fija la salida a true
Objetos totales creados = 1200
Objetos totales finalizados = 1010
Intenta forzar la finalizacion.
Objetos totales finalizados = 1089
Como se puede observar, solamente se han finalizado 1080 objetos de los 1200
instanciados. 1010 de ellos fueron finalizados cuando se instanciaron, por lo tanto, parece
una acción del garbage collector. Posteriormente, se finalizaron 79, posiblemente debido a
la invocación del método de finalización, [Link]().
Introducción al lenguaje Java

Clases
Las clases son lo más simple de Java. Todo en Java forma parte de una clase, es
una clase o describe como funciona una clase. El conocimiento de las clases es
fundamental para poder entender los programas Java. Todas las acciones de los programas
Java se colocan dentro del bloque de una clase o un objeto. Un objeto es una instancia de
una clase. Todos los métodos se definen dentro del bloque de la clase, Java no soporta
funciones o variables globales. Así pues, el esqueleto de cualquier aplicación Java se basa
en la definición de una clase.
Todos los datos básicos, como los enteros, se deben declarar en las clases antes de
hacer uso de ellos. En C la unidad fundamental son los ficheros con código fuente, en Java
son las clases. De hecho son pocas las sentencias que se pueden colocar fuera del bloque
de una clase. La palabra clave import (equivalente al #include) puede colocarse al
principio de un fichero, fuera del bloque de la clase. Sin embargo, el compilador
reemplazará esa sentencia con el contenido del fichero que se indique, que consistirá,
como es de suponer, en más clases.
La definición de una clase consta de dos partes, la declaración y el cuerpo, según la
siguiente sintaxis:
DeclaracionClase {
CuerpoClase
}
sin el punto y coma (;) final
La declaración de la clase indica al compilador el nombre de la clase, la clase de la
que deriva (su superclase), los privilegios de acceso a la clase (pública, abstracta, final) y
si la clase implementa o no, uno o varios interfaces. El nombre de la clase debe ser un
identificador válido en Java.
Cada clase Java deriva, directa o indirectamente, de la clase Object. La clase padre
inmediatamente superior a la clase que se está declarando se conoce como superclase. Si
no se especifica la superclase de la que deriva una clase, se entiende que deriva
directamente de la clase Object (definida en el paquete [Link]).
En la declaración de una clase se utiliza la palabra clave extends para especificar
la superclase, de la forma:
class MiClase extends SuperClase {
// cuerpo de la clase
}
Una clase hereda las variables y métodos de su superclase y de la superclase de esa
clase, etc.; es decir, de todo el árbol de jerarquía de clases desde la clase que estamos
declarando hasta la raíz del árbol: Object. En otras palabras, un objeto que es instanciado
desde una clase determinada, contiene todas las variables y métodos de instancia definidos
para esta clase y sus antecesores; aunque los métodos pueden ser modificados
(sobreescritos) en algún lugar.
Una clase puede implementar uno o más interfaces, declarándose esto utilizando la
palabra clave implements, seguida de la lista de interfaces que implementa, se paradas por
coma (,), de la forma:
class MiClase extends SuperClase implements MiInterfaz,TuInterfaz {
// cuerpo de la clase
}
Introducción al lenguaje Java

Cuando una clase indique que implementa un interfaz, se puede asegurar que
proporciona una definición para todos y cada uno de los métodos declarados en ese
interfaz; en caso contrario, el compilador generará errores al no poder resolver los
métodos del interfaz en la clase que lo implementa.
Hay cierta similitud entre un interfaz y una clase abstracta, aunque las definiciones
de métodos no están permitidas en un interfaz y sí se pueden definir en una clase abstracta.
El propósito de los interfaces es proporcionar nombres, es decir, solamente declara lo que
necesita implementar el interfaz, pero no cómo se ha de realizar esa implementación; es
una forma de encapsulación de los protocolos de los métodos sin forzar al usuario a
utilizar la herencia.
Cuando se implementa un interfaz, los nombres de los métodos de la clase deben
coincidir con los nombres de los métodos que están declarados en es interfaz, todos y cada
uno de ellos.
El cuerpo de la clase contiene las declaraciones, y posiblemente la inicialización,
de todos los datos miembros, tanto variables de clase como variables de instancia, y la
definición completa de todos los métodos.
Las variables pueden declararse dentro del cuerpo de la clase o dentro del cuerpo
de un método de la clase. Sin embargo, éstas últimas no son variables miembro de la clase,
sino variables locales del método en la que están declaradas.
Un objeto instanciado de una clase tiene un estado definido por el valor actual de
las variables de instancia y un entorno definido por los métodos de instancia. Es típico de
la programación orientada a objetos el restringir el acceso a las variables y proporcionar
métodos que permitan el acceso a esas variables, aunque esto no es estrictamente
necesario.
Alguna o todas las variables pueden declararse para que se comporten como si
fuesen constantes, utilizando la palabra clave final.
Los objetos instanciados desde una clase contienen todos los métodos de instancia
de esa clase y también todos los métodos heredados de la superclase y sus antecesores. Por
ejemplo, todos los objetos en Java heredan, directa o indirectamente, de la clase Object;
luego todo objeto contiene los miembros de la clase Object, es decir, la clase Object define
el esta y entorno básicos para todo objeto Java. Esto difiere significativamente de C++, en
donde no hay una clase central de la que se derive todo, por lo que no hay una definición
de un estado básico en C++.
Las características más importantes que la clase Object cede a sus descendientes son:
 Posibilidad de cooperación consigo o con otro objeto
 Posibilidad de conversión automática a String
 Posibilidad de espera sobre una variable de condición
 Posibilidad de notificación a otros objetos del estado de la variable de condición
La sintaxis general de definición de clase se podría extender tal como se muestra
en el siguiente diagrama, que debe tomarse solamente como referencia y no al pie de la
letra:
NombreDeLaClase {
// declaración de las variables de instancia
// declaración de las variables de la clase
metodoDeInstancia() {
// variables locales
Introducción al lenguaje Java

// código
}
metodoDeLaClase() {
// variables locales
// código
}
}
Tipos de Clases
Hasta ahora sólo se ha utilizado la palabra clave public para calificar el nombre de
las clases que hemos visto, pero hay tres modificadores más. Los tipos de clases que
podemos definir son:
public
Las clases public son accesibles desde otras clases, bien sea directamente o por
herencia, desde clases declaradas fuera del paquete que contiene a esas clases públicas, ya
que, por defecto, las clases solamente son accesibles por otras clases declaradas dentro del
mismo paquete en el que se han declarado. Para acceder desde otros paquetes, primero
tienen que ser importadas. La sintaxis es:

public class miClase extends SuperClase implements


miInterface,TuInterface {
// cuerpo de la clase
}
Aquí la palabra clave public se utiliza en un contexto diferente del que se emplea
cuando se define internamente la clase, junto con private y protected.
abstract
Una clase abstract tiene al menos un método abstracto. Una clase abstracta no se
instancia, sino que se utiliza como clase base para la herencia. Es el equivalente al
prototipo de una función en C++.
final
Una clase final se declara como la clase que termina una cadena de herencia, es lo
contrario a una clase abstracta. Nadie puede heredar de una clase final. Por ejemplo, la
clase Math es una clase final. Aunque es técnicamente posible declarar clases con varias
combinaciones de public, abstract y final, la declaración de una clase abstracta y a la vez
final no tiene sentido, y el compilador no permitirá que se declare una clase con esos dos
modificadores juntos.
synchronizable
Este modificador especifica que todos los métodos definidos en la clase son
sincronizados, es decir, que no se puede acceder al mismo tiempo a ellos desde distintos
threads; el sistema se encarga de colocar los flags necesarios para evitarlo. Este
mecanismo hace que desde threads diferentes se puedan modificar las mismas variables
sin que haya problemas de que se sobreescriban.
Si no se utiliza alguno de los modificadores expuestos, por defecto, Java asume
que una clase es:
 No final
 No abstracta
 Subclase de la clase Object
Introducción al lenguaje Java

 No implementa interfaz alguno


Introducción al lenguaje Java

Variables Miembro
Una clase en Java puede contener variables y métodos. Las variables pueden ser
tipos primitivos como int, char, etc. Los métodos son funciones.
Por ejemplo, en el siguiente trozo de código podemos observarlo:
public class MiClase {
int i;

public MiClase() {
i = 10;
}
public void Suma_a_i( int j ) {
int suma;
suma = i + j;
}
}
La clase MiClase contiene una variable (i) y dos métodos, MiClase() que es el
constructor de la clase y Suma_a_i( int j ).
La declaración de una variable miembro aparece dentro del cuerpo de la clase,
pero fuera del cuerpo de cualquier método de esa clase. Si se declara dentro de un
métodos, será una variable local del método y no una variable miembro de la clase. En el
ejemplo anterior, i es una variable miembro de la clase y suma es una variable local del
método Suma_ a_i().
El tipo de una variable determina los valores que se le pueden asignar y las
operaciones que se pueden realizar con ella.
El nombre de una variable ha de ser un identificador válido en Java. Por convenio,
los programadores Java empiezan los nombres de variables con una letra minúscula, pero
no es imprescindible. Los nombres de las variables han de ser únicos dentro de la clase y
se permite que haya variables y métodos con el mismo nombre.
Java permite la inicialización de variables miembro de tipos primitivos en el
momento de la declaración .
La sintaxis completa de la declaración de una variable miembro de una clase en
Java sería:

[especificador_de_acceso][static][final][transient][volatile]
tipo nombreVariable [= valor_inicial];

Ambito de una Variable


Los bloques de sentencias compuestas en Java se delimitan con dos llaves. Las
variables de Java sólo son válidas desde el punto donde están declaradas hasta el final de
la sentencia compuesta que la engloba. Se pueden anidar estas sentencias compuestas, y
cada una puede contener su propio conjunto de declaraciones de variables locales. Sin
embargo, no se puede declarar una variable con el mismo nombre que una de ámbito
exterior.
El siguiente ejemplo intenta declarar dos variables separadas con el mismo
nombre. En Java, esto es ilegal, aunque en C se lo tragara.
class Ambito {
int i = 1; // ámbito exterior
{ // crea un nuevo ámbito
Introducción al lenguaje Java

int i = 2; // error de compilación


}
}
Variables de Instancia
La declaración de una variable miembro dentro de la definición de una clase sin
anteponerle la palabra clave static, hace que sea una variable de instancia en todos los
objetos de la clase. El significado de variable de instancia sería, más o menos, que
cualquier objeto instanciado de esa clase contiene su propia copia de toda variable de
instancia. Si se examinara la zona de memoria reservada a cada objeto de la clase, se
encontraría la reserva realizada para todas las variables de instancia de la clase. En otras
palabras, como un objeto es una instancia de una clase, y como cada objeto tiene su propia
copia de un dato miembro particular de la clase, entonces se puede denominar a ese dato
miembro como variable de instancia.
En Java, se accede a las variables de instancia asociadas a un objeto determinado
utilizando el nombre del objeto, el operador punto (.) y el nombre de la variable:
[Link];

Variables Estáticas
La declaración de un dato miembro de una clase usando static, crea una variable
de clase o variable estática de la clase. El significado de variable estática es que todas las
instancias de la clase (todos los objetos instanciados de la clase) contienen la mismas
variables de clase o estáticas. En otras palabras, en un momento determinado se puede
querer crear una clase en la que el valor de una variable de instancia sea el mismo (y de
hecho sea la misma variable) para todos los objetos instanciados a partir de esa clase. Es
decir, que exista una única copia de la variable de instancia, entonces es cuando debe
usarse la palabra clave static.
class Documento extends Pagina {
static int version = 10;
}
El valor de la variable version será el mismo para cualquier objeto instanciado de
la clase Documento. Siempre que un objeto instanciado de Documento cambie la variable
version, ésta cambiará para todos los objetos.
Si se examinara en este caso la zona de memoria reservada por el sistema para cada
objeto, se encontraría con que todos los objetos comparten la misma zona de memoria
para cada una de las variables estáticas, por ello se llaman también variables de clase,
porque son comunes a la clase, a todos los objetos instanciados de la clase.
Java se puede acceder a las variables de clase utilizando el nombre de la clase y el
nombre de la variable, no es necesario instanciar ningún objeto de la clase para acceder a
las variables de clase.
En Java, a las variables de clase se accede utilizando el nombre de la clase, el
nombre de la variable y el operador punto (.). La siguiente línea de código, ya archivista,
se utiliza para acceder a la variable out de la clase System. En el proceso, se accede al
método println() de la variable de clase que presenta una cadena en el dispositivo estándar
de salida.
[Link]( "Hola, Mundo" );
Introducción al lenguaje Java

Es importante recordar que todos los objetos de la clase comparten las misma
variables de clase, porque si alguno de ellos modifica alguna de esas variables de clase,
quedarán modificadas para todos los objetos de la clase. Esto puede utilizarse como una
forma de comunicación entre objetos.
Constantes
En Java, se utiliza la palabra clave final para indicar que una variable debe
comportarse como si fuese constante, significando con esto que no se permite su
modificación una vez que haya sido declarada e inicializada.
Como es una constante, se le ha de proporcionar un valor en el momento en que se
declare, por ejemplo:
class Elipse {
final float PI = 3.14159;
. . .
}
Si se intenta modificar el valor de una variable final desde el código de la
aplicación, se generará un error de compilación.
Si se usa la palabra clave final con una variable o clase estática, se pueden crear
constantes de clase, haciendo de esto modo un uso altamente eficiente de la memoria,
porque no se necesitarían múltiples copias de las constantes.
La palabra clave final también se puede aplicar a métodos, significando en este
caso que los métodos no pueden ser sobreescritos.
Introducción al lenguaje Java

Metodos
Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras
clases. La implementación de un método consta de dos partes, una declaración y un
cuerpo. La declaración en Java de un método se puede expresar esquemáticamente como:
tipoRetorno nombreMetodo( [lista_de_argumentos] )
{ cuerpoMetodo }
La sintaxis de la declaración completa de un método es la que se muestra a
continuación .
especificadorAcceso static abstract final native synchronized
tipoRetorno nombreMetodo( lista_de_argumentos )throws listaEscepciones
especificadorAcceso, determina si otros objetos pueden acceder al método y cómo pueden
hacerlo.
static, indica que los métodos pueden ser accedidos sin necesidad de instanciar un objeto
del tipo que determina la clase.
abstract, indica que el método no está definido en la clase, sino que se encuentra
declarado ahí para ser definido en una subclase (sobreescrito).
final, evita que un método pueda ser sobreescrito.
native, son métodos escritos es otro lenguaje.
synchronized, se usa en el soporte de multithreading.
lista_de_argumentos, es la lista opcional de parámentros que se pueden pasar al método.
throws listaExcepciones, indica las excepciones que puede generar y manipular el
método.

Valor de Retorno de un Método


En Java es imprescindible que a la hora de la declaración de un método, se indique
el tipo de dato que ha de devolver. Si no devuelve ningún valor, se indicará el tipo void
como retorno. Todos los tipos primitivos en Java se devuelven por valor y todos los
objetos se devuelven por referencia. Para devolver un valor se utiliza la palabra clave
return. La palabra clave return va seguida de una expresión que será evaluada para
saber el valor de retorno. Esta expresión puede ser compleja o puede ser simplemente el
nombre de un objeto, una variable de tipo primitivo o una constante.
El ejemplo [Link] ilustra el retorno por valor y por referencia.

// Este ejemplo ilustra el retorno de valores tanto por referencia


// como por valor

// Un objeto de esta clase sera devuelto por referencia


class miClase {
int varInstancia = 10;
}
class java506 {
// Metodo que devuelve por Valor
int retornoPorValor() {
// Devuelve un tipo primitivo por valor
return( 5 );
}
// Metodo que devuelve por Referencia
Introducción al lenguaje Java

miClase retornoPorReferencia() {
// Devuelve un objeto por referencia
return( new miClase() );
}
public static void main( String args[] ) {
// Instancia un objeto
java506 obj = new java506();
[Link]( "El Valor devuelto es "+[Link]());
[Link](
"El Valor de la variable de instancia en el objeto devuelto es "+
[Link]().varInstancia ); // Atencion a los dos
puntos
}
}
Si un programa Java devuelve una referencia a un objeto y esa referencia no es
asignada a ninguna variable, o utilizada en una expresión, el objeto se marca
inmediatamente para que el reciclador de memoria en su siguiente ejecución devuelve la
memoria ocupada por el objeto al sistema, asumiendo que la dirección no se encuentra ya
almacenada en ninguna otra variable.
Nombre del Método
El nombre del método puede ser cualquier identificador legal en Java. Java soporta
el concepto de sobrecarga de métodos, es decir, permite que dos métodos compartan el
mismo nombre pero con diferente lista de argumentos, de forma que el compilador pueda
diferenciar claramente cuando se invoca a uno o a otro, en función de los parámetros que
se utilicen en la llamada al método.
El siguiente fragmento de código muestra una clase Java con cuatro métodos
sobrecargados, el último no es legal porque tiene el mismo nombre y lista de argumentos
que otro previamente declarado:
class MiClase {
. . .
void miMetodo( int x,int y ) { . . . }
void miMetodo( int x ) { . . . }
void miMetodo( int x,float y ) { . . . }
// void miMetodo( int a,float b ) { . . . } // no válido
}
Todo lenguaje de programación orientado a objetos debe soportar las
características de encapsulación, herencia y polimorfismo. La sobrecarga de métodos es
considerada por algunos autores como polimorfismo en tiempo de compilación. En Java,
los métodos sobrecargados siempre deben devolver el mismo tipo.

Métodos de Instancia
Cuando se incluye un método en una definición de una clase Java sin utilizar la
palabra clave static, estamos generando un método de instancia. Aunque cada objeto de
la clase no contiene su propia copia de un método de instancia (no existen múltiples copias
del método en memoria), el resultado final es como si fuese así, como si cada objeto
dispusiese de su propia copia del método. Cuando se invoca un método de instancia a
través de un objeto determinado, si este método referencia a variables de instancia de la
clase, en realidad se están referenciando variables de instancia específicas del objeto
Introducción al lenguaje Java

específico que se está invocando. La llamada a los métodos de instancia en Java se realiza
utilizando el nombre del objeto, el operador punto y el nombre del método.
[Link]();
Los métodos de instancia tienen acceso tanto a las variables de instancia como a
las variables de clase.
Métodos Estáticos
Cuando una función es incluida en una definición de clase o un método de una
clase Java, y se utiliza la palabra static, se obtiene un método estático o método de
clase. Lo más significativo de los métodos de clase es que pueden ser invocados sin
necesidad de que haya que instanciar ningún objeto de la clase. En Java se puede invocar
un método de clase utilizando el nombre de la clase, el operador punto y el nombre del
método.
[Link]();
En Java, los métodos de clase operan solamente como variables de clase; no tienen
acceso a variables de instancia de la clase, a no ser que se cree un nuevo objeto y se
acceda a las variables de instancia a través de ese objeto.
Si se observa el siguiente trozo de código de ejemplo:
class Documento extends Pagina {
static int version = 10; // Variable de clase
int numero_de_capitulos; // Variable de instancia
static void annade_un_capitulo() {
numero_de_capitulos++; // esto no funciona
}
static void modifica_version( int i ) {
version++; // esto si funciona
}
}
la modificación de la variable numero_de_capitulos no funciona porque se está violando
una de las reglas de acceso al intentar acceder desde un método estático a una variable no
estática.
Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma
página de variables; es decir, todos los objetos que se generen comparten la misma zona
de memoria. Los métodos estáticos se usan para acceder solamente a variables estáticas.
Paso de parámetros
En Java, todos los métodos deben estar declarados y definidos dentro de la clase, y
hay que indicar el tipo y nombre de los argumentos o parámetros que acepta. Los
argumentos son como variables locales declaradas en el cuerpo del método que están
inicializadas al valor que se pasa como parámetro en la invocación del método. En Java,
todos los argumentos de tipos primitivos deben pasarse por valor, mientras que los objetos
deben pasarse por referencia. Cuando se pasa un objeto por referencia, se está pasando la
dirección de memoria en la que se encuentra almacenado el objeto. Si se modifica una
variable que haya sido pasada por valor, no se modificará la variable original que se haya
utilizado para invocar al método, mientras que si se modifica una variable pasada por
referencia, la variable original del método de llamada se verá afectada de los cambios que
se produzcan en el método al que se le ha pasado como argumento.
Introducción al lenguaje Java

El ejemplo [Link] se ilustra el paso de parámetros de tipo primitivo y


también el paso de objetos, por valor y por referencia, respectivamente.
// Esta clase se usa para instanciar un objeto referencia
class MiClase {
int varInstancia = 100;
}
// Clase principal
class java515 {
// Función para ilustrar el paso de parámetros
void pasoVariables( int varPrim,MiClase varRef ) {
[Link]( "--> Entrada en la funcion pasoVariables" );
[Link]( "Valor de la variable primitiva: "+varPrim );
[Link]( "Valor contenido en el objeto: "+ [Link] );
[Link]( "-> Modificamos los valores" );
[Link] = 101;
varPrim = 201;
[Link]( "--> Todavia en la funcion pasoVariables" );
[Link]( "Valor de la variable primitiva: "+varPrim );
[Link]( "Valor contenido en el objeto: "+ [Link] );
}
public static void main( String args[] ) {
// Instanciamos un objeto para acceder a sus métodos
java515 aObj = new java515();
// Instanciamos un objeto normal
MiClase obj = new MiClase();

// Instanciamos una variable de tipo primitivo


int varPrim = 200;
[Link]( "> Estamos en main()" );
[Link]( "Valor de la variable primitiva: "+varPrim );
[Link]( "Valor contenido en el objeto: "+ [Link] );
// Llamamos al método del objeto
[Link]( varPrim,obj );
[Link]( "> Volvemos a main()" );
[Link]( "Valor de la variable primitiva, todavia : "+ varPrim );
[Link]( "Valor contenido ahora en el objeto: "+ [Link] );
}
}
En Java los métodos tienen acceso directo a las variables miembro de la clase. El
nombre de un argumento puede tener el mismo nombre que una variable miembro de la
clase. En este caso, la variable local que resulta del argumento del método, oculta a la
variable miembro de la clase. Cuando se instancia un método se pasa siempre una
referencia al propio objeto que ha llamado al método, es la referencia this.

Constructores
Java soporta la sobrecarga de métodos, es decir, que dos o más métodos puedan
tener el mismo nombre, pero distinta lista de argumentos en su invocación. Si se
sobrecarga un método, el compilador determinará ya en tiempo de compilación, en base a
lista de argumentos con que se llame al método, cual es la versión del método que debe
utilizar.
Java soporta la noción de constructor. El constructor es un tipo específico de
método que siempre tiene el mismo nombre que la clase y se utiliza para construir objetos
de esa clase. No tiene tipo de dato específico de retorno, ni siquiera void. Esto se debe a
que el tipo específico que debe devolver un constructor de clase es el propio tipo de la
clase.
Introducción al lenguaje Java

En este caso, pues, no se puede especificar un tipo de retorno, ni se puede colocar


ninguna sentencia que devuelva un valor. Los constructores pueden sobrecargarse, y
aunque puedan contener código, su función primordial es inicializar el nuevo objeto que se
instancia de la clase. En Java, ha de hacerse una llamada al constructor para instanciar un
nuevo objeto.
Cuando se declara una clase en Java, se pueden declarar uno o más constructores
opcionales que realizan la inicialización cuando se instancia (se crea una ocurrencia) un
objeto de dicha clase.
Utilizando el código de la sección anterior, cuando se crea una nueva instancia de
MiClase, se crean (instancias) todos los métodos y variables, y se llama al constructor de
la clase:
MiClase mc;
mc = new MiClase();
La palabra clave new se usa para crear una instancia de la clase. Antes de
ser instanciada con new no consume memoria, simplemente es una declaración de
tipo. Después de ser instanciado un nuevo objeto mc, el valor de i en el objeto mc
será igual a 10. Se puede referenciar la variable (de instancia) i con el nombre del
objeto:
mc.i++; // incrementa la instancia de i de mc
Al tener mc todas las variables y métodos de MiClase, se puede usar la primera
sintaxis para llamar al método Suma_a_i() utilizando el nuevo nombre de clase mc:
mc.Suma_a_i( 10 );
y ahora la variable mc.i vale 21.
Luego, en Java, cuando se instancia un objeto, siempre se hace una llamada directa
al constructor como argumento del operador new. Este operador se encarga de que el
sistema proporcione memoria para contener al objeto que se va a crear.
En Java solamente se pueden instanciar en la pila de memoria, es decir, solamente
se pueden instanciar utilizando el operador new para poder solicitar memoria al sistema en
tiempo de ejecución y utilizar el constructor para instanciar el objeto en esa zona de
memoria. Si al intentar instanciar un objeto, la Máquina Virtual Java (JVM) no puede
localizar la memoria que necesita ese objeto, bien inmediatamente o haciendo ejecutarse al
reciclador de memoria, el sistema lanzará un OutOfMemoryError.
En Java si no se proporciona explícitamente un constructor, el sistema proporciona
uno por defecto que inicializará automáticamente todas las variables miembro a cero o su
equivalente.
Se puede pensar en el constructor de defecto en Java como un método que tiene el
mismo nombre que la clase y una lista de argumentos vacía.
MiClase objeto;
Si se proporciona uno o más constructores, el constructor de defecto no se
proporciona automáticamente y si fuese necesaria su utilización, tendría que
proporcionarlo explícitamente el programa.
Las dos sentencias siguientes muestran cómo se utiliza el constructor en Java para
declarar, instanciar y, opcionalmente, inicializar un objeto:
MiClase miObjeto = new MiClase();
MiClase miObjeto = new MiClase( 1,2,3 );
Introducción al lenguaje Java

Las dos sentencias devuelven una referencia al nuevo objeto que es almacenada en
la variable miObjeto. También se puede invocar al constructor sin asignar la referencia a
una variable. Esto es útil cuando un método requiere un objeto de un tipo determinado
como argumento, ya que se puede incluir una llamada al constructor de este objeto en la
llamada al método:
miMetodo( new MiConstructor( 1,2,3 ) );
Aquí se instancia e inicializa un objeto y se pasa a la función. Para que el programa
compile adecuadamente, debe existir una versión de la función que espere recibir un
objeto de ese tipo como parámetro.
Cuando un método o una función comienza su ejecución, todos los parámetros se
crean como variables locales automáticas. En este caso, el objeto es instanciado en
conjunción con la llamada a la función que será utilizada para inicializar esas variables
locales cuando comience la ejecución y luego serán guardadas. Como son automáticas,
cuando el método concluye su ejecución, será marcado para su destrucción (en Java).
En el siguiente ejemplo, [Link], se ilustran algunos de los conceptos sobre
constructores que se han planteado en esta sección.
class MiClase {
int varInstancia;
// Este es el constructor parametrizado
MiClase( int dato ) {
// rellenamos la variable de instancia con los datos
// que se pasan al constructor
varInstancia = dato;
}
void verVarInstancia() {
[Link]( "El Objeto contiene " + varInstancia );
}
}
class java507 {
public static void main( String args[] ) {
[Link]( "Lanzando la aplicacion" );
// Instanciamos un objeto de este tipo llamando al
// constructor de defecto
java507 obj = new java507();
// Llamamos a la funcion pasandole un constructor
// parametrizado como parametro
[Link]( new MiClase( 100 ) );
}
// Esta funcion recibe un objeto y llama a uno de sus metodos
// para presentar en pantalla el dato que contiene el objeto
void miFuncion( MiClase objeto){
[Link]();
}
}
Herencia
En casos en que se vea involucrada la herencia, los constructores toman un
significado especial porque lo normal es que la subclase necesite que se ejecute el
constructor de la superclase antes que su propio constructor, para que se inicialicen
correctamente aquellas variables que deriven de la superclase. En Java, la sintaxis para
conseguir esto es sencilla y consiste en incluir en el cuerpo del constructor de la subclase
como primera línea de código la siguiente sentencia:
super( parametros_opcionales );
Introducción al lenguaje Java

Esto hará que se ejecute el constructor de la superclase, utilizando los parámetros


que se pasen para la inicialización. En el código del ejemplo siguiente, [Link] se
ilustra el uso de esta palabra clase para llamar al constructor de la superclase desde una
subclase.
class SuperClase {
int varInstancia;
// Es necesario proporcionar el constructor por defecto,que
// es aquel que no tiene parametros de llamada
SuperClase(){}
// Este es el constructor parametrizado de la superclase
SuperClase( int pDato ) {
[Link](
"Dentro del constructor de la SuperClase" );
varInstancia = pDato;
}
void verVarInstancia() {
[Link]( "El Objeto contiene " + varInstancia );
}
}
class SubClase extends SuperClase {
// Este es el constructor parametrizado de la subclase
SubClase( int bDato ) {
// La siguiente sentencia println no compila, la llamada
// a super() debe estar al principio de un metodo en caso de
// que aparezca
// [Link]( "En el constructor de la SubClase" );
// Llamamos al constructor de la superclase
super( bDato );
[Link](
"Dentro del constructor de la SubClase" );
}
}
class java508 {
public static void main( String args[] ) {
[Link]( "Lanzando la aplicacion" );
// Instanciamos un objeto de este tipo llamando al
// constructor de defecto
java508 obj = new java508();
// Llamamos a la funcion pasandole un constructor de la
// subclase parametrizado como parametro
[Link]( new SubClase( 100 ) );
}
// Esta funcion recibe un objeto y llama a uno de sus metodos
// para presentar en pantalla el dato que contiene el objeto,
// en este caso el metodo es heredado de la SuperClase
void miFuncion( SubClase objeto ) {
[Link]();
}
}
Si super no aparece como primera sentencia del cuerpo de un constructor, el
compilador Java inserta una llamada implícita, super(), al constructor de la superclase
inmediata. Es decir, el constructor por defecto de la superclase es invocado
automáticamente cuando se ejecuta el constructor para una nueva subclase, si no se
especifica un constructor parametrizado para llamar al constructor de la superclase.

Control de Acceso
Introducción al lenguaje Java

El control de acceso también tiene un significado especial cuando se trata de


constructores. Aunque en otra sección se trata a fondo el tela del control de acceso en
Java, con referencia a los constructores se puede decir que el control de acceso que se
indique determina la forma en que otros objetos van a poder instanciar objetos de la clase.
En la siguiente descripción, se indica cómo se trata el control de acceso cuando se tienen
entre manos a los constructores:
private
Ninguna otra clase puede instanciar objetos de la clase. La clase puede contener
métodos públicos, y estos métodos pueden construir un objeto y devolverlo, pero nadie
más puede hacerlo.
protected
Solamente las subclases de la clase pueden crear instancias de ella.
public
Cualquier otra clase puede crear instancias de la clase.
package
Nadie desde fuera del paquete puede construir una instancia de la clase. Esto es útil
si se quiere tener acceso a las clases del paquete para crear instancias de la clase, pero que
nadie más pueda hacerlo, con lo cual se restringe quien puede crear instancias de la clase.
En Java una instancia de una clase, un objeto, contiene todas las variables y
métodos de instancia de la clase y de todas sus superclases. Sin embargo, soporta la
posibilidad de sobreescribir un método declarado en una superclase, indicando el mismo
nombre y misma lista de argumentos.
Como aclaración a terminología que se empleo en este documento, quiero indicar
que cuando digo sobrecargar métodos, quiero decir que Java requiere que los dos métodos
tengan el mismo nombre, devuelvan el mismo tipo, pero tienen una diferente lista de
argumentos. Y cuando digo sobreescribir métodos, quiero decir que Java requiere que los
dos métodos tengan el mismo nombre, mismo tipo de retorno y misma lista de argumentos
de llamada.
En Java, si una clase define un método con el mismo nombre, mismo tipo de
retorno y misma lista de argumentos que un método de una superclase, el nuevo método
sobreescribirá al método de la superclase, utilizándose en todos los objetos que se creen en
donde se vea involucrado el tipo de la subclase que sobreescribe el método.

Finalizadores
Java no utiliza destructores ya que tiene una forma de recoger automáticamente
todos los objetos que se salen del alcance. No obstante proporciona un método que,
cuando se especifique en el código de la clase, el reciclador de memoria (garbage
collector) llamará:
// Cierra el canal cuando este objeto es reciclado
protected void finalize() {
close();
}
Cada objeto tiene el método finalize(), que es heredado de la clase Object. Si se
necesitase realizar alguna limpieza asociada con la memoria, se puede sobreescribir el
método finalize() y colocar en él el código que sea necesario.
Introducción al lenguaje Java

La regla de oro a seguir es que no se debe poner ningún código que deba ser
ejecutado en el método finalize(). Por ejemplo, si se necesita concluir la comunicación con
un servidor cuando ya no se va a usar un objeto, no debe ponerse el código de desconexión
en el método finalize(), porque puede que nunca sea llamado. Luego, en Java, es
responsabilidad del programador escribir métodos para realizar limpieza que no involucre
a la memoria ocupada por el objeto y ejecutarlos en el instante preciso. El método
finalize() y el reciclador de memoria son útiles para liberar la memoria de la pila y debería
restringirse su uso solamente a eso, y no depender de ellos para realizar ningún otro tipo
de limpieza.
No obstante, Java dispone de dos métodos para asegurar que los finalizadores se
ejecuten. Los dos métodos habilitan la finalización a la salida de la aplicación, haciendo
que los finalizadores de todos los objetos que tengan finalizador y que todavía no hayan
sido invocados automáticamente, se ejecuten antes de que la Máquina Virtual Java
concluya la ejecución de la aplicación. Estos dos métodos son:
runFinalizersOnExit( boolean ), método estático de [Link], y
runFinalizersOnExit( boolean ), método estático de [Link]
Una clase también hereda de sus superclase el método finalize(), y en caso
necesario, debe llamarse una vez que el método finalize() de la clase haya realizado las
tareas que se le hayan encomendado, de la forma:
[Link]();
En la construcción de un objeto, se desplaza uno por el árbol de jerarquía, de
herencia, desde la raíz del árbol hacia las ramas, y en la finalización, es al revés, los
desplazamientos por la herencia debe ser desde las ramas hacia las superclases hasta llegar
a la clase raíz.
La Clase String
Java posee gran capacidad para el manejo de cadenas dentro de sus clases String y
StringBuffer. Un objeto String representa una cadena alfanumérica de un valor constante
que no puede ser cambiada después de haber sido creada. Un objeto StringBuffer
representa una cadena cuyo tamaño puede variar, o puede ser modificada por programa.
Los Strings son objetos constantes y por lo tanto muy baratos para el sistema. La
mayoría de las funciones relacionadas con cadenas esperan valores String como
argumentos y devuelven valores String.
Hay que tener en cuenta que las funciones estáticas no consumen memoria del
objeto, con lo cual es más conveniente usar Character que char . No obstante, char se usa,
por ejemplo, para leer ficheros que están escritos desde otro lenguaje.
Existen varios constructores para crear nuevas cadenas:
String();
String( String value );
String( char value[] );
String( char value[],int offset,int count );
String( byte bytes[],int offset,int length );
String( byte bytes[],int offset,int length,String enc );
String( byte bytes[] );
String( byte bytes[],String enc );
String( StringBuffer buffer );
Tal como uno puede imaginarse, las cadenas pueden ser muy complejas, existiendo
muchos métodos muy útiles para trabajar con ellas y, afortunadamente, la mayoría están
Introducción al lenguaje Java

codificados en la clase String, por lo que sería conveniente que el programador tuviese una
copia de la declaración de la clase String encima de su mesa de trabajo, para determinar el
significado de los parámetros en los constructores y métodos de la clase String. Es más,
esta necesidad puede extenderse a todas las demás clases, pero claro, teniendo en cuenta el
espacio disponible sobre la mesa de trabajo.
También merece comentario el parámetro enc que se utiliza en algunos
constructores de la clase y que permite encriptar la cadena que se le pasa como parámetro
en función de los caracteres que se indiquen en el parámetro. En caso de usar alguno de
estos constructores, hay que tener en cuenta que la cadena que se devuelve puede que no
tenga la misma longitud que la que se le pasa para codificar, por lo que deben utilizarse
con cuidado estos constructores.
Hay que resaltar el hecho de que mientras que el contenido de un objeto String no
puede modificarse, una referencia a un objeto String puede hacerse que apunte a otro
objeto String, de tal forma que parece que el primer objeto ha sido modificado. Esta
característica es la que ilustra el ejemplo .
Es importante resaltar que la siguiente sentencia no modifica el objeto original
referenciado por la variable str1:
thisObj.str1 = thisObj.str1 + " " + thisObj.str2;
sino que, esta sentencia crea un nuevo objeto que es la concatenación de los objetos
existentes y hace que la variable de referencia str1 apunte al nuevo objeto, en lugar de
apuntar al objeto original. Ese objeto original queda marcado para que el reciclador de
memoria en su siguiente pasada, devuelva la memoria que ocupaba al sistema.
Una reflexión especial merecen los arrays de Strings. La siguiente sentencia
declara e instancia un array de referencias a cinco objetos de tipo String:
String miArray = new String[5];
Este array no contiene los datos de las cadenas. Solamente reserva una zona de
memoria para almacenar las cinco referencias a cadenas. No se guarda memoria para
almacenar los caracteres que van a componer esas cinco cadenas, por lo que hay que
reservar expresamente esa memoria a la hora de almacenar los datos concretos de las
cadenas, tal como se hace en la siguiente línea de código:
miArray[0] = new String( "Esta es la primera cadena" );
miArray[1] = new String( "Esta es la segunda cadena" );
Esto resultará conocido a los programadores C/C++, ya que sería el método
habitual de reservar memoria para un array de punteros de tipo char y luego hacer que esos
punteros apunten a cadenas localizadas en algún lugar de la memoria.
Funciones Básicas
La primera devuelve la longitud de la cadena y la segunda devuelve el carácter que
se encuentra en la posición que se indica en indice :
int length();
char charAt( int indice );
Funciones de Comparación de Strings
boolean equals( Object obj );
boolean equalsIgnoreCase( String str2 );
Lo mismo que equals() pero no tiene en cuenta mayúsculas o minúsculas.
int compareTo( String str2 );
Introducción al lenguaje Java

Devuelve un entero menor que cero si la cadena es léxicamente menor que str2 .
Devuelve cero si las dos cadenas son léxicamente iguales y un entero mayor que cero si la
cadena es léxicamente mayor que str2.
Funciones de Comparación de Subcadenas
boolean regionMatches( int offset,String other,int ooffset,
int len );
boolean regionMatches( boolean ignoreCase,int offset,String other,
int ooffset,int len );
Comprueba si una región de esta cadena es igual a una región de otra cadena.
boolean startsWith( String prefix );
boolean startsWith( String prefix,int toffset );
boolean endsWith( String suffix );
Devuelve si esta cadena comienza o termina con un cierto prefijo o sufijo
comenzando en un determinado desplazamiento.
int indexOf( int ch );
int indexOf( int ch,int fromIndex );
int lastIndexOf( int ch );
int lastIndexOf( int ch,int fromIndex );
int indexOf( String str );
int indexOf( String str,int fromIndex );
int lastIndexOf( String str );
int lastIndexOf( String str,int fromIndex );
Devuelve el primer/último índice de un carácter/cadena empezando la búsqueda a
partir de un determinado desplazamiento.
String substring( int beginIndex );
String substring( int beginIndex,int endIndex );
String concat( String str );
String replace( char oldChar,char newChar );
String toLowerCase();
String toUpperCase();
String trim();
Ajusta los espacios en blanco al comienzo y al final de la cadena.
void getChars( int srcBegin,int srcEnd,char dst[],int dstBegin );
void getBytes( int srcBegin,int srcEnd,byte dst[],int dstBegin );
String toString();
char toCharArray();
int hashCode();
Con frecuencia se necesita convertir un objeto cualquiera a un objeto String,
porque sea necesario pasarlo a un método que solamente acepte valores String, o por
cualquier otra razón. Todas las clases heredan el método toString() de la clase Object y
muchas de ellas lo sobreesciben para proporcionar una implementación que tenga sentido
para esas clases. Además, puede haber ocasiones en que sea necesario sobreescribir el
método toString() para clases propias y proporcionarles un método de conversión
adecuado.
En el ejemplo , se sobreescribe el método toString() para la nueva clase que se crea
en el código.
class java805 {
// Se definen las variables de instancia para la clase
String uno;
String dos;
String tres;
Introducción al lenguaje Java

// Constructor de la clase
java805( String a,String b,String c ) {
uno = a;
dos = b;
tres = c;
}

public static void main( String args[] ) {


// Se instancia un objeto de la clase
java805 obj = new java805( "Tutorial","de","Java" );

// Se presenta el objeto utilizando el metodo sobreescrito


[Link]( [Link]() );
}

// Sobreescritura del metodo toString() de la clase Object


public String toString() {
// Convierte un objeto a cadena y lo devuelve
return( uno+" "+dos+" "+tres );
}
}
Funciones de Conversión
La clase String posee numerosas funciones para transformar valores de otros tipos
de datos a su representación como cadena. Todas estas funciones tienen el nombre de
valueOf, estando el método sobrecargado para todos los tipos de datos básicos.
A continuación se muestra un ejemplo de su utilización:
String Uno = new String( "Hola Mundo" );
float f = 3.141592;

String PI = [Link]( f );
String PI = [Link]( f ); // Mucho más correcto
String valueOf( boolean b );
String valueOf( char c );
String valueOf( int i );
String valueOf( long l );
String valueOf( float f );
String valueOf( double d );
String valueOf( Object obj );
String valueOf( char data[] );
String valueOf( char data[],int offset,int count );
Usa arrays de caracteres para la cadena.
String copyValueOf( char data[] );
String copyValueOf( char data[],int offset,int count );
Crea un nuevo array equivalente para la cadena.
Ahora bien, la conversión contraria de valor numérico a cadena no tiene métodos
en Java, a semejanza del atoi() o atof() de C++. En el ejemplo , se muestra una forma de
realizar esta conversión, aunque puede haber otras y seguramente mejores.
class java806 {
public static void main( String args[] ) {
int numero = new Integer( "1234" ).intValue();

[Link]( "El valor entero de la variable " +


"numero es " + numero );
}
}
Introducción al lenguaje Java

Se crea un objeto Integer temporal que contiene el valor especificado para el


String. El método intValue() de la clase Integer es el que se utiliza para extraer el valor del
entero de ese objeto y asignarlo a una variable de tipo int . Esto puede utilizarse como
patrón para implementar métodos de conversión atoi() propios.
this
Al acceder a variables de instancia de una clase, la palabra clave this hace
referencia a los miembros de la propia clase en el objeto actual; es decir, this se refiere al
objeto actual sobre el que está actuando un método determinado y se utiliza siempre que
se quiera hace referencia al objetoactual de la clase. Volviendo al ejemplo de MiClase, se
puede añadir otro constructor de la forma siguiente:
public class MiClase {
int i;
public MiClase() {
i = 10;
}
// Este constructor establece el valor de i
public MiClase( int valor ) {
this.i = valor; // i = valor
}
// Este constructor también establece el valor de i
public MiClase( int i ) {
this.i = i;
}
public void Suma_a_i( int j ) {
i = i + j;
}
}
Aquí this.i se refiere al entero i en la clase MiClase, que corresponde al objeto
actual. La utilización de this en el tercer constructor de la clase, permite referirse
directamente al objeto en sí, en lugar de permitir que el ámbito actual defina la resolución
de variables, al utilizar i como parámetro formal y después this para acceder a la variable
de instancia del objeto actual.
La utilización de this en dicho contexto puede ser confusa en ocasiones, y algunos
programadores procuran no utilizar variables locales y nombres de parámetros formales
que ocultan variables de instancia. Una filosofía diferente dice que en los métodos de
inicialización y constructores, es bueno seguir el criterio de utilizar los mismos nombres
por claridad, y utilizar this para no ocultar las variables de instancia. Lo cierto es que es
más una cuestión de gusto personal que otra cosa el hacerlo de una forma u otra.
La siguiente aplicación de ejemplo, , utiliza la referencia this al objeto para acceder
a una variable de instancia oculta para el método que es llamado.

class java509 {
// Variable de instancia
int miVariable;

// Constructor de la clase
public java509() {
miVariable = 5;
}

// Metodo con argumentos


void miMetodo(int miVariable) {
Introducción al lenguaje Java

[Link]( "La variable Local miVariable contiene "


+ miVariable );
[Link](
"La variable de Instancia miVariable contiene "
+ [Link] );
}

public static void main( String args[] ) {


// Instanciamos un objeto del tipo de la clase
java509 obj = new java509();
// que utilizamos para llamar a su unico metodo
[Link]( 10 );
}
}
super
Si se necesita llamar al método padre dentro de una clase que ha reemplazado ese
método, se puede hacer referencia al método padre con la palabra clave super:
import MiClase;
public class MiNuevaClase extends MiClase {
public void Suma_a_i( int j ) {
i = i + ( j/2 );
super.Suma_a_i( j );
}
}
En el siguiente código, el constructor establecerá el valor de i a 10, después lo
cambiará a 15 y finalmente el método Suma_a_i() de la clase padre MiClase lo dejará en
25:
MiNuevaClase mnc;
mnc = new MiNuevaClase();
mnc.Suma_a_i( 10 );
Si un método sobreescribe un método de su superclase, se puede utilizar la palabra
clave super para eludir la versión sobreescrita de la clase e invocar a la versión original del
método en la supreclase. Del mismo modo, se puede utilizar super para acceder a variables
miembro de la superclase.
En el ejemplo , la aplicación utiliza super para referirse a una variable local en un
método y a una variable de la superclase que tiene el mismo nombre. El programa también
utiliza super para invocar al constructor de la superclase desde en constructor de la
subclase.
Herencia
La herencia es el mecanismo por el que se crean nuevos objetos definidos en
términos de objetos ya existentes. Por ejemplo, si se tiene la clase Ave, se puede crear la
subclase Pato, que es una especialización de Ave.
class Pato extends Ave {
int numero_de_patas;
}
La palabra clave extends se usa para generar una subclase (especialización) de un
objeto. Un Pato es una subclase de Ave. Cualquier cosa que contenga la definición de Ave
será copiada a la clase Pato, además, en Pato se pueden definir sus propios métodos y
variables de instancia. Se dice que Pato deriva o hereda de Ave.
Además, se pueden sustituir los métodos proporcionados por la clase base.
Introducción al lenguaje Java

Utilizando nuestro anterior ejemplo de MiClase, aquí hay un ejemplo de una clase
derivada sustituyendo a la función Suma_a_i():
import MiClase;
public class MiNuevaClase extends MiClase {
public void Suma_a_i( int j ) {
i = i + ( j/2 );
}
}
Ahora cuando se crea una instancia de MiNuevaClase, el valor de i también se
inicializa a 10, pero la llamada al método Suma_a_i() produce un resultado diferente:
MiNuevaClase mnc;
mnc = new MiNuevaClase();
mnc.Suma_a_i( 10 );
Java se diseñó con la idea de que fuera un lenguaje sencillo y, por tanto, se le
denegó la capacidad de la herencia múltiple, tal como es conocida por los programadores
C++.
Subclases
Como ya se ha indicado en múltiples ocasiones en esta sección, cuando se puede
crear nuevas clases por herencia de clases ya existentes, las nuevas clases se llaman
subclases, mientras que las clases de donde hereda se llaman superclases.
Cualquier objeto de la subclase contiene todas las variables y todos los métodos de
la superclase y sus antecesores.
Todas las clases en Java derivan de alguna clase anterior. La clase raíz del árbol de
la jerarquía de clases de Java es la clase Object, definida en el paquete [Link]. Cada vez
que se desciende en el árbol de jerarquía, las clases van siendo más especializadas.
Cuando se desee que nadie pueda derivar de una clase, se indica que es final; y lo
mismo con los métodos, si no se desea que se puedan sobreescribir, se les antepone la
palabra clave final.
Lo contrario de final es abstract. Una clase marcada como abstracta, únicamente
está diseñada para crear subclases a partir de ella, no siendo posible instanciar ningún
objeto a partir de una clase abstracta.
Introducción al lenguaje Java

La clase OBJECT
La clase Object, como ya se ha indicado anteriormente, es la clase raíz de todo el
árbol de la jerarquía de clases Java, y proporciona un cierto número de métodos de utilidad
general que pueden utilizar todos los objetos. La lista completa se puede ver en la
documentación del API de Java, aquí solamente se tratarán algunos de ellos; por ejemplo,
Object proporciona:
Un método por el que un objeto se puede comparar con otro objeto
Un método para convertir un objeto a una cadena
Un método para esperar a que ocurra una determinada condición
Un método para notificar a otros objetos que una condición ha cambiado
Un método para devolver la clase de un objeto

El método equals()
public boolean equals( Object obj );
Todas las clases que se definan en Java heredarán el método equals(), que se puede
utilizar para comparar dos objetos. Esta comparación no es la misma que proporciona el
operador ==, que solamente compara si dos referencias a objetos apuntan al mismo objeto.
El método equals() se utiliza para saber si dos objetos separados son del mismo
tipo y contienen los mismos datos. El método devuelve true si los objetos son iguales y
false en caso contrario.
El sistema ya sabe de antemano cómo aplicar el método a todas las clases estándar
y a todos los objetos de los que el compilador tiene conocimiento. Por ejemplo, se puede
usar directamente para saber si dos objetos String son iguales.
Las subclases pueden sobreescribir el método equals() para realizar la adecuada
comparación entre dos objetos de un tipo que haya sido definido por el programador.
En la aplicación de ejemplo siguiente, [Link] se sobreescribe el método para
comparar dos objetos de la nueva clase que crea la aplicación.
Hay que observar que en la lista de argumentos del método equals() hay que
pasarle un argumento de tipo Object. Si se define un método con un argumento de tipo
diferente, se estará sobrecargando el método, no sobreescribiéndolo.
En el ejemplo, una vez que se ejecuta, es necesario hacer un moldeo del argumento
al tipo de la clase antes de intentar realizar la comparación. Se utiliza el operador
instanceof para confirmar que el objeto es del tipo correcto. Uno de los objetos
proporcionados para comprobar su equivalencia es de tipo erróneo (String) y el método
sobreescrito equals() indicará que no es un objeto equivalente.
Introducción al lenguaje Java

class java511 {
int miDato;

// Constructor parametrizado
java511( int dato ) {
miDato = dato;
}

public static void main(String args[] ) {


// Se instancian los objetos que se van a testear
java511 obj1 = new java511( 2 );
java511 obj2 = new java511( 2 );
java511 obj3 = new java511( 3 );
String obj4 = "Un objeto String";

// Se realizan las comprobaciones y se presenta por pantalla


// el resultado de cada una de ellas
[Link]( "obj1 equals obj1: " +
[Link]( obj1 ) );
[Link]( "obj1 equals obj2: " +
[Link]( obj2 ) );
[Link]( "obj1 equals obj3: " +
[Link]( obj3 ) );
[Link]( "obj1 equals obj4: " +
[Link]( obj4 ) );
}

// Se sobreescribe el metodo equals()


public boolean equals( Object arg ) {
// Se comprueba que el argumento es del tipo adecuado y
// que no es nulo. Si lo anterior se cumple se realiza
// la comprobacion de equivalencia de los datos.
// Observese que se ha empleado el operador instanceof
if( (arg != null) && (arg instanceof java511) ) {
// Hacemos un moldeado del Object general a tipo java511
java511 temp = (java511)arg;

// Se realiza la comparacion y se devuelve el resultado


return( [Link] == [Link] );
}
else {
// No es del tipo esperado
return( false );
}
}
}
El método getClass()
public final native Class getClass();
En Java existe la clase Class, que se define de la forma (la declaración no está
completa, consultar el API de Java para ello):
public final class [Link] extends [Link] {
// Métodos
public static Class forName(String className);
public ClassLoader getClassLoader();
public Class[] getClasses();
public Class getComponentType();
public Constructor getConstructor(Class[] parameterTypes);
public Constructor[] getConstructors();
public Class[] getDeclaredClasses();
Introducción al lenguaje Java

public Constructor[] getDeclaredConstructors();


public Field getDeclaredField(String name);
public Field[] getDeclaredFields();
public Method getDeclaredMethod(String name, Class[] parTypes);
public Method[] getDeclaredMethods();
public Class getDeclaringClass();
public Field getField(String name);
public Field[] getFields();
public Class[] getInterfaces();
public Method getMethod(String name, Class[] parameterTypes);
public Method[] getMethods();
public int getModifiers();
public String getName();
public URL getResource(String name);
public InputStream getResourceAsStream(String name);
public Object[] getSigners();
public Class getSuperclass();
public boolean isArray();
public boolean isAssignableFrom(Class cls);
public boolean isInstance(Object obj);
public boolean isInterface();
public boolean isPrimitive();
public Object newInstance();
public String toString(); // Overrides [Link]()
}
Instancias de la clase Class representan las clases e interfaces que está ejecutando
la aplicación Java. No hay un constructor para la clase Class, sus objetos son construidos
automáticamente por la Máquina Virtual Java (JVM) cuando las clases son cargadas, o
por llamadas al método defineClass() del cargados de clases.
Es una clase importante porque se le pueden realizar peticiones de información
sobre objetos, como cuál es su nombre o cómo se llama su superclase.
El método getClass() de la clase Object se puede utilizar para determinar la clase
de un objeto. Es decir, devuelve un objeto de tipo Class, que contiene información
importante sobre el objeto que crea la clase. Una vez determinada la clase del objeto, se
pueden utilizar los métodos de la clase Class para obtener información acerca del objeto.
Además, habiendo determinado la clase del objeto, el método newInstance() de la
clase Class puede invocarse para instanciar otro objeto del mismo tipo. El resultado es que
el operador new será utilizado con un constructor de una clase conocida.
Hay que hacer notar que la última afirmación del párrafo anterior es una situación
que el compilador no conoce en tiempo de compilación, es decir, no sabe el tipo del objeto
que va a ser instanciado. Por lo tanto, si se necesita referenciar al nuevo objeto, es
necesario declarar la variable de referencia del tipo genérico Object, aunque el objeto
actual tomará todos los atributos de la subclase actual por la que será instanciado.
Hay que hacer notar que el método getClass() es un método final y no puede ser
sobreescrito. Devuelve un objeto de tipo Class que permite el uso de los métodos
definidos en la clase Class sobre ese objeto.
El siguiente programa, [Link] , ilustra alguna de estas características.
Primero, instancia un objeto, mira la clase de ese objeto y utiliza alguno de los métodos de
la clase Class para obtener y presentar información acerca del objeto. Luego, pregunta al
usuario si quiere instanciar un nuevo objeto, instanciando un objeto de tipo String en un
caso o, en el otro caso, se aplica el método getClass() a un objeto existente y utilizando el
método newInstance() se instancia un nuevo objeto del mismo tipo.
Introducción al lenguaje Java

import [Link].*;

class java512 {

public static void main( String args[] ) {


java512 obj1 = new java512();
// Se usa el metodo getClass() de la clase Object y dos
// metodos de la clase Class para obtener y presentar
// informacion acerca del objeto
[Link]( "Nombre de la clase de obj1: "
+ [Link]().getName() );
[Link]( "Nombre de la superclase de obj1: "
+ [Link]().getSuperclass() );

// Ahora se instancia otro objeto basandose en la entrada


// del usuario, de forma que el compilador no tiene forma
// de conocer el tipo del objeto en tiempo de compilacion
// Se declara una referencia a un objeto generico
Object obj2 = null;

// Se pide la entrada al usuario


[Link]( "Introduce un 0 o un 1" );
try {
// Captura la entrada del usuario
int dato = [Link]();
// Si se ha introducido un 0
if( (char)dato == '0' )
// Se crea un objeto String
obj2 = "Esto es un objeto String";
// Sino, se crea un nuevo objeto del mismo tipo
// que el anterior
else
obj2 = [Link]().newInstance();
} catch( Exception e ) {
[Link]( "Excepcion " + e );
}

// Ahora se indican la clase y superclase del nuevo objeto


[Link]( "Nombre de la clase de obj2: "
+ [Link]().getName() );
[Link]( "Nombre de la superclase de obj2: "
+ [Link]().getSuperclass());
}
}
El método toString()
public String toString();
La clase Object dispone de este método que puede usarse para convertir todos los
objetos conocidos por el compilador a algún tipo de representación de cadena, que
dependerá del objeto.
Por ejemplo, el método toString() extrae el entero contenido en un objeto Integer.
De forma similar, si se aplica el método toString() al objeto Thread, se puede obtener
información importante acerca de los threads y presentarla como cadena.
Este método también se puede sobreescribir, o redefinir, para convertir los objetos
definidos por el programador a cadenas. El programa siguiente, [Link] , redefine el
método toString() de una clase recién definida para que pueda utilizarse en la conversión
de objetos de esta clase a cadenas.
Introducción al lenguaje Java

class java513 {
// Se definen las variables de instancia para la clase
String uno;
String dos;
String tres;
// Constructor de la clase
java513( String a,String b,String c ) {
uno = a;
dos = b;
tres = c;
}

public static void main( String args[] ) {


// Se instancia un objeto de la clase
java513 obj = new java513( "Tutorial","de","Java" );

// Se presenta el objeto utilizando el metodo sobreescrito


[Link]( [Link]() );
}

// Sobreescritura del metodo toString() de la clase Object


public String toString() {
// Convierte un objeto a cadena y lo devuelve
return( uno+" "+dos+" "+tres );
}
}
Otros Métodos
Hay otros métodos útiles en la clase Object que son, o serán, discutidos en otras
secciones de este documento. Por ejemplo, el método
protected void finalize();
que se tratará en el apartado de finalizadores. O también, los métodos que se utilizan en la
programación de threads para hacer que varios threads se sincronicen, como son
public final void wait();
public final native void wait( long timeout );
public final native void notify();
public final native void notifyAll();
que se tratarán cuando se hable del multithreading en Java.

Interfaces
Los métodos abstractos son útiles cuando se quiere que cada implementación de la
clase parezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los
métodos abstractos. Los interfaces proporcionan un mecanismo para abstraer los métodos
a un nivel superior, lo que permite simular la herencia múltiple de otros lenguajes. Un
interfaz sublima el concepto de clase abstracta hasta su grado más alto.
Un interfaz podrá verse simplemente como una forma, es como un molde,
solamente permite declarar nombres de métodos, listas de argumentos, tipos de retorno y
adicionalmente miembros datos (los cuales podrán ser únicamente tipos básicos y serán
tomados como constantes en tiempo de compilación, es decir, static y final).
Un interfaz contiene una colección de métodos que se implementan en otro lugar.
Los métodos de una clase son public, static y final. La principal diferencia entre
interface y abstract es que un interfaz proporciona un mecanismo de encapsulación de los
protocolos de los métodos sin forzar al usuario a utilizar la herencia. Por ejemplo
Introducción al lenguaje Java

public interface VideoClip {


// comienza la reproduccion del video
void play();
// reproduce el clip en un bucle
void bucle();
// detiene la reproduccion
void stop();
}
Las clases que quieran utilizar el interfaz VideoClip utilizarán la palabra
implements y proporcionarán el código necesario para implementar los métodos que se
han definido para el interfaz:

class MiClase implements VideoClip {


void play() {
<código>
}
void bucle() {
<código>
}
void stop() {
<código>
}
Al utilizar implements para el interface es como si se hiciese una acción de copiar-
y-pegar del código del interface, con lo cual no se hereda nada, solamente se pueden usar
los métodos.
La ventaja principal del uso de interfaces es que una clase interface puede ser
implementada por cualquier número de clases, permitiendo a cada clase compartir el
interfaz de programación sin tener que ser consciente de la implementación que hagan las
otras clases que implementen el interface.

class MiOtraClase implements VideoClip {


void play() {
<código nuevo>
}
void bucle() {
<código nuevo>
}
void stop() {
<código nuevo>
}
Es decir, el aspecto más importante del uso de interfaces es que múltiples objetos
de clases diferentes pueden ser tratados como si fuesen de un mismo tipo común, donde
este tipo viene indicado por el nombre del interfaz.
Aunque se puede considerar el nombre del interfaz como un tipo de prototipo de
referencia a objetos, no se pueden instanciar objetos en sí del tipo interfaz. La definición
de un interfaz no tiene constructor, por lo que no es posible invocar el operador new sobre
un tipo interfaz.
Un interfaz puede heredar de varios interfaces sin ningún problema. Sin embargo,
una clase solamente puede heredar de una clase base, pero puede implementar varios
Introducción al lenguaje Java

interfaces. También, el JDK ofrece la posibilidad de definir un interfaz vacío, como es el


caso de Serialize, que permite serializar un objeto. Un interfaz vacío se puede utilizar
como un flag, un marcador para marcar a una clase con una propiedad determinada.
La aplicación , ilustra algunos de los conceptos referentes a los interfaces. Se
definen dos interfaces, en uno de ellos se definen dos constantes y en el otro se declara un
método put() y un método get(). Las constantes y los métodos se podrían haber colocado
en la misma definición del interfaz, pero se han separado para mostrar que una clase
simple puede implementar dos o más interfaces utilizando el separador coma (,) en la lista
de interfaces.
También se definen dos clases, implementando cada una de ellas los dos interfaces.
Esto significa que cada clase define el método put() y el método get(), declarados en un
interfaz y hace uso de las constantes definidas en el otro interfaz. Estas clase se encuentran
en ficheros separados por exigencias del compilador, los ficheros son y , y el contenido de
ambos ficheros es el que se muestra a continuación:

public interface Constantes {


public final double pi = 6.14;
public final int constanteInt = 125;
}

public interface MiInterfaz {


void put( int dato );
int get();
}
Es importante observar que en la definición de los dos métodos del interfaz, cada
clase los define de la forma más adecuada para esa clase, sin tener en cuenta cómo estará
definidos en las otras clases.
Una de las clases también define el método show(), que no está declarado en el
interfaz. Este método se utiliza para demostrar que un método que no está declarado en el
interfaz no puede ser accedido utilizando una variable referencia de tipo interfaz.
El método main() en la clase principal ejecuta una serie de instanciaciones,
invocaciones de métodos y asignaciones destinadas a mostrar las características de los
interfaces descritos anteriormente. Si se ejecuta la aplicación, las sentencias que se van
imprimiendo en pantalla son autoexplicactivas de lo que está sucediendo en el corazón de
la aplicación.
Los interfaces son útiles para recoger las similitudes entre clase no relacionadas,
forzando una relación entre ellas. También para declarar métodos que forzosamente una o
más clases han de implementar. Y también, para tener acceso a un objeto, para permitir el
uso de un objeto sin revelar su clase, son los llamados objetos anónimos, que son muy
útiles cuando se vende un paquete de clases a otros desarrolladores.
Java no permite herencia múltiple. Hay autores que dicen que el interfaz de Java es
un sustituto de la herencia múltiple y hay otros que están en desacuerdo con eso. Lo cierto
es que sí parece una alternativa y los interfaces resuelven algunos de los problemas de la
herencia múltiple, por ejemplo:
 No se pueden heredar variables desde un interfaz
 No se pueden heredar implementaciones de métodos desde un interfaz
 La jerarquía de un interfaz es independiente de la jerarquía de clases que implementen
el mismo interfaz
Introducción al lenguaje Java

y esto no es cierto en la herencia múltiple, tal como se ve desde C++.


Definición
La definición de un interfaz es semejante a la de una clase. La definición de
interfaz tiene dos componentes, declaración y cuerpo. En forma esquemática sería:

DeclaracionInterfaz {
// cuerpoInterfaz
}
Declaración
La mínima declaración consiste en la palabra clave interface y el nombre del
interfaz. Por convenio, los nombres de interfaces comienzan con una letra mayúscula,
como los nombres de las clases, pero no es obligatorio.
La declaración de interfaz puede tener dos componentes adicionales, el
especificador de acceso public y la lista de superinterfaces.
Un interfaz puede extender otros interfaces. Sin embargo, mientras que una clase
solamente puede extender otra clase, un interfaz puede extender cualquier número de
interfaces. En el ejemplo se muestra la definición completa de un interfaz, declaración y
cuerpo.

public interface MiInterfaz extends InterfazA,InterfazB {


public final double PI = 3.14159;
public final int entero = 125;
void put( int dato );
int get();
}
El especificador de acceso public indica que el interfaz puede ser utilizado por
cualquier clase de cualquier paquete. Si se omite, el interfaz solamente será accesible a
aquellas clases que estén definidas en el mismo paquete.
La cláusula extends es similar a la de la declaración de clase, aunque un interfaz
puede extender múltiples interfaces. Un interfaz no puede extender clases.
La lista de superinterfaces es un lista separada por comas de todas las interfaces
que la nueva interfaz va a extender. Un interfaz hereda todas las constantes y métodos de
sus superinterfaces, excepto si el interfaz oculta una constante con otra del mismo nombre,
o si redeclara un método con una nueva declaración de ese método.
El cuerpo del interfaz contiene las declaraciones de los métodos, que terminan en
un punto y coma y no contienen código alguno en su cuerpo. Todos los métodos
declarados en un interfaz son implícitamente, public y abstract, y no se permite el uso de
transient, volatile, private, protected o syncronized en la declaración de miembros en un
interfaz. En el cuerpo del interfaz se pueden definir constantes, que serán implícitamente
public, static y final.
Implementación
Un interfaz se utiliza definiendo una clase que implemente el interfaz a través de
su nombre. Cuando una clase implementa un interfaz, debe proporcionar la definición
completa de todos los métodos declarados en el interfaz y, también, la de todos los
métodos declarados en todos los superinterfaces de ese interfaz.
Introducción al lenguaje Java

Una clase puede implementar más de un interfaz, incluyendo varios nombre de


interfaces separados por comas. En este caso, la clase debe proporcionar la definición
completa de todos los métodos declarados en todos los interfaces de la lista y de todos los
superinterfaces de esos interfaces.
En ejemplo, se puede observar una clase que implementa dos interfaces, Constantes y
MiInterfaz.
class ClaseA implements Constantes,MiInterfaz {
double dDato;
// Define las versiones de put() y get() que utiliza la ClaseA
public void put( int dato ) {
// Se usa "pi" del interfaz Constantes
dDato = (double)dato * pi;
[Link](
"En put() de ClaseA, usando pi del interfaz " +
"Constantes: " + dDato );
}
public int get() {
return( (int)dDato );
}
// Se define un metodo show() para la ClaseA que no esta
// declarado en el interfaz MiInterfaz
void show() {
[Link](
"En show() de ClaseA, dDato = " + dDato );
}
}
Como se puede observar, esta clase proporciona la definición completa de los
métodos put() y get() del interfaz MiInterfaz, a la vez que utiliza las constantes definidas
en el interfaz Constantes. Además, la clase proporciona la definición del método show()
que no está declarado en ninguno de los interfaces, sino que es propio de la clase.
La definición de un interfaz es una definición de un nuevo tipo de datos. Se puede
utilizar el nombre del interfaz en cualquier lugar en que se pueda utilizar un nombre de
tipo de dato. Sin embargo, no se pueden instanciar objetos del tipo interfaz, porque un
interfaz no tiene constructor. En esta sección, hay numerosos ejemplos de uso del nombre
de un interfaz como tipo.
Herencia "Multiple"
Un interfaz no es solamente una forma más pura de denominar a una clase
abstracta, su propósito es mucho más amplio que eso. Como un interfaz no tiene ninguna
implementación, es decir, no hay ningun tipo de almacenamiento asociado al interfaz, no
hay nada que impida la combinación de varios interfaces. Esto tiene mucho valor porque
hay veces en que es necesario que un objeto X sea a y b y c. En Java, se puede hacer , pero
solamente una de las clases puede tener implementación, con lo cual los problemas que se
presentan en C++ no suceden con Java cuando se combinan multiples interfaces.
En una clase derivada, el programador no está forzado a tener una clase base sea
esta abstracta o concreta, es decir, una sin métodos abstractos. Si se hereda desde una clase
no interfaz, solamente se puede heredar de una. El resto de los elementos base deben ser
interfaces. Todos los nombres de interfaces se colocan después de la palabra clave
implements y separados por comas. Se pueden tener tantos interfaces como se quiera y
cada uno de ellos será un tipo independiente. El ejemplo siguiente, , muestra una clase
concreta combinada con varios interfaces para producir una nueva clase
import [Link].*;
Introducción al lenguaje Java

interface Luchar {
void luchar();
}
interface Nadar {
void nadar();
}
interface Volar {
void volar();
}
class Accion {
public void luchar() {}
}

class Heroe extends Accion implements Luchar,Nadar,Volar {


public void nadar() {}
public void volar() {}
}

public class java516 {


static void a( Luchar x ) {
[Link]();
}
static void b( Nadar x ) {
[Link]();
}
static void c( Volar x ) {
[Link]();
}
static void d( Accion x ) {
[Link]();
}

public static void main( String args[] ) {


Heroe i = new Heroe();
a( i ); // Trata al Heroe como Luchar
b( i ); // Trata al Heroe como Nadar
c( i ); // Trata al Heroe como Volar
d( i ); // Trata al Heroe como Accion
}
}
Se puede observar que Heroe combina la clase concreta Accion con los interfaces
Luchar, Nadar y Volar. Cuando se combina la clase concreta con interfaces de este modo,
la clase debe ir la primera y luego los interfaces; en caso contrario, el compilador dará un
error.
La autentificación, signature, para luchar() es la misma en el interfaz Luchar y en
la clase Accion, y luchar() no está proporcionado con la definicion de Heroe. Si se quiere
crear un objeto del nuevo tipo, la clase debe tener todas las definiciones que necesita, y
aunque Heroe no proporciona una definicion explicita para luchar(), la definición es
proporcionada automáticamente por Accion y así es posible crear objetos de tipo Heroe.
En la clase java516, hay cuatro métodos que toman como argumentos los distintos
interfaces y la clase concreta. Cuando un objeto Heroe es creado, puede ser pasado a
cualquierea de estos métodos, lo que significa que se está realizando un upcasting al
interfaz de que se trate. Debido a la forma en que han sido diseñados los interfaces en
Java, esto funciona perfectamente sin ninguna dificultad ni esfuerzo extra por parte del
programador.
Introducción al lenguaje Java

La razón principal de la existencia de los interfaces en el ejemplo anterior es el


proder realizar un upcasting a más de un tipo base. Sin embargo, hay una segunda razón
que es la misma por la que se usan clases abstractas: evitar que el programador cliente
tenga que crear un objeto de esta clase y establecer que solamente es un interfaz. Y esto
plantea la cuestión de qué se debe utilizar pues, un interfaz o una clase abastacta. Un
interfaz proporciona los beneficios de una clase abstracta y, además, los beneficios propios
del interfaz, así que es posible crear una clase base sin la definición de ningún método o
variable miembro, por lo que se deberían utilizar mejor los interfaces que las clases
abstractas. De hecho, si se desea tener una clase base, la primera elección debería ser un
interfaz, y utilizar clases abstractas solamente si es necesario tener alguna variable
miembro o algun método implementado.
Introducción al lenguaje Java

Paquetes
Para explicar el tema de los paquetes imaginarse una ciudad en la cual hay varios
bloques de apartamentos propiedad de una única empresa inmobiliaria. Esta empresa
dispone además de comercios, zonas de recreo y almacenes. Se puede pensar en la
empresa como una lista de referencias a cada una de sus propiedades; es decir, la
inmobiliaria sabe exactamente donde está un apartamento determinado y puede hacer uso
de él en el momento en que lo necesite.
Si ahora se mira lo anterior en términos de Java, la empresa inmobiliaria es el
paquete. Los paquetes agrupan a librerías de clases, como las librerías que contienen
información sobre distintas propiedades comerciales. Un paquete será, pues, la mayor
unidad lógica de objetos en Java.
Los paquetes se utilizan en Java de forma similar a como se utilizan las librerías en
C++, para agrupar funciones y clases, sólo que en Java agrupan diferentes clases y/o
interfaces. En ellos las clases son únicas, comparadas con las de otros paquetes, y además
proporcionan un método de control de acceso. Los paquetes también proporcionan una
forma de ocultar clases, evitando que otros programas o paquetes accedan a clases que son
de uso exclusivo de una aplicación determinada.
Declaración de Paquetes
Los paquetes se declaran utilizando la palabra package seguida del nombre del
paquete. Esto debe estar al comienzo del fichero fuente, en concreto, debe ser la primera
sentencia ejecutable del código Java, excluyendo, claro está, los comentarios y espacios en
blanco. Por ejemplo:
package mamiferos;
class Ballena {
. . .
}
En este ejemplo, el nombre del paquete es mamiferos. La clase Ballena se
considera como parte del paquete. La inclusión de nuevas clases en el paquete es muy
sencilla, ya que basta con colocar la misma sentencia al comienzo de los ficheros que
contengan la declaración de las clases. Como cada clase se debe colocar en un fichero
separado, cada uno de los ficheros que contengan clases pertenecientes a un mismo
paquete, deben incluir la misma sentencia package, y solamente puede haber una
sentencia package por fichero.
Se recuerda que el compilador Java solamente requiere que se coloquen en ficheros
separados las clases que se declaren públicas. Las clases no públicas se pueden colocar en
el mismo fichero fuente, al igual que las clases anidadas. Aunque es una buena norma de
programación que todas las clases se encuentren en un único fichero, la sentencia package
colocada el comienzo de un fichero fuente afectará a todas las clases que se declaren en
ese fichero.
Java también soporta el concepto de jeraquía de paquetes. Esto es parecido a la
jerarquía de directorios de la mayoría de los sistemas operativos. Se consigue
especificando múltiples nombres en la sentencia package, separados por puntos. Por
ejemplo, en las sentencias siguientes, la clase Ballena pertenece al paquete mamiferos que
cae dentro de la jerarquía del paquete animales.
package [Link];
class Ballena {
. . .
}
Introducción al lenguaje Java

Esto permite agrupar clases relacionadas en un solo paquete, y agrupar paquetes


relacionados en un paquete más grande. Para referenciar a un miembro de otro paquete, se
debe colocar el nombre del paquete antes del nombre de la clase. La siguiente sentencia es
un ejemplo de llamada al método obtenerNombre() de la clase Ballena que pertenece al
subpaquete mamiferos del paquete animales:
[Link]();
La analogía con la jerarquía de directorios se ve reforzada por el intérprete Java, ya
que éste requiere que los ficheros .class se encuentren físicamente localizados en
subdirectorios que coincidan con el nombre del subpaquete. En el ejemplo anterior, si se
encontrase en una máquina Unix, la clase Ballena debería estar situada en el camino
siguiente:
animales/mamiferos/[Link]
Por supuesto, las convenciones en el nombre de los directorios serán diferentes
para los distintos sistemas operativos. El compilador Java colocará los ficheros .class en el
mismo directorio que se encuentren los ficheros fuentes, por lo que puede ser necesario
mover los ficheros .class resultantes de la compilación al directorio adecuado, en el caso
de que no se encuentren los fuentes en el lugar correcto del árbol jerárquico. Aunque los
ficheros .class también se pueden colocar directamente en el directorio que se desee
especificando la opción -d (directorio) a la hora de invocar al compilador. La siguiente
línea de comando colocará el fichero resultante de la compilación en el subdirectorio
animales/mamiferos/Ballenas, independientemente de cual sea el directorio desde el
cual se esté invocando al compilador.
> javac -d animales/mamiferos/Ballena [Link]
Todas las clases quedan englobadas dentro de un mismo paquete, si no se
especifica explíctamente lo contrario, es decir, aunque no se indique nada, las clases
pertenecen a un paquete; ya que, como es normal en Java, lo que no se declara
explícitamente, toma valores por defecto. En este caso, hay un paquete sin nombre que
agrupa a todos los demás paquetes. Si un paquete no tiene nombre, no es posible para los
demás paquetes referenciar a ese paquete, por eso es conveniente colocar todas las clases
no triviales en paquetes, para que puedan ser referenciadas posteriormente desde cualquier
otro programa.
Acceso a Otros Paquetes
Se decía que se pueden referenciar paquetes precediendo con su nombre la clase
que se quiere usar. También se puede emplear la palabra clave import, si se van a colocar
múltiples referencias a un mismo paquete, o si el nombre del paquete es muy largo o
complicado.
La sentencia import se utiliza para incluir una lista de paquetes en los que buscar
una clase determinada, y su sintaxis es:
import nombre_paquete;
Esta sentencia, o grupo de ellas, deben aparecer antes de cualquier declaración de
clase en el código fuente. Por ejemplo:
import [Link];
En este ejemplo, todos los miembros (variables, métodos) de la clase Ballena están
accesibles especificando simplemente su nombre, sin tener que precederlo del nombre
completo del paquete.
Introducción al lenguaje Java

Esta forma de abreviar tienes sus ventajas y sus desventajas. La ventaja principal
es que el código no se vuelve demasiado difícil de leer y además es más rápido de teclear.
La desventaja fundamental es que resulta más complicado el saber exactamente a qué
paquete pertenece un determinado miembro; y esto es especialmente complicado cuando
hay muchos paquetes importados.
En la sentencia import también se admite la presencia del carácter *, asterisco.
Cuando se emplea, se indica que toda la jerarquía de clases localizada a partir del punto en
que se encuentre, debe ser importada, en lugar de indicar solamente una determinada
clase. Por ejemplo, la siguiente sentencia indicaría que todas la clases del subpaquete
[Link], deben ser importadas:
import [Link].*;
Esta es una forma simple y sencilla de tener acceso a todas las clases de un
determinado paquete. Aunque el uso del asterisco debe hacerse con cautela, porque al ya
de por sí lento compilador, si se pone un asterisco, se cargarán todos los paquetes, lo que
hará todavía más lenta la compilación. No obstante, el asterisco no tiene impacto alguno a
la hora de la ejecución, solamente en tiempo de compilación.
La sentencia import se utiliza en casi todos los ejemplos del Tutorial,
fundamentalmente para acceder a las distintas partes del API de Java. Por defecto, el
conjunto de clases bajo [Link].* se importan siempre; las otras librerías deben ser
importadas explícitamente. Por ejemplo, las siguientes líneas de código premiten el acceso
a las clases correspondientes a las librerías de manipulacion de imágenes y gráficos:
import [Link];
import [Link];
Nomenclatura de Paquetes
Los paquetes pueden nombrarse de cualquier forma que siga el esquema de
nomenclatura de Java. Por convenio, no obstante, los nombres de paquetes comienzan por
una letra minúscula para hacer más sencillo el reconocimiento de paquetes y clases,
cuando se tiene una referencia explícita a una clase. Esto es porque los nombres de las
clases, también por convenio, empiezan con una letra mayúscula. Por ejemplo, cuando se
usa el convenio citado, es obvio que tanto animales como mamiferos son paquetes y que
Ballena es una clase. Cuanquier cosa que siga al nombre de la clase es un miembro de esa
clase:
[Link]();
Java sigue este convenio en todo el API. Por ejemplo, el método
[Link]() que tanto se ha utilizado sigue esta nomenclatura. El nombre del
paquete no se declara explícitamente porque [Link].* siempre es importado
implícitamente. System es el nombre de la clase perteneciente al paquete [Link] y
está capitalizado. El nombre completo del método es:
[Link]();
Cada nombre de paquete ha de ser único, para que el uso de paquetes sea realmente
efectivo. Los conflictos de nombres pueden causar problemas a la hora de la ejecución en
caso de duplicidad, ya que los ficheros de clases podrían saltar de uno a otro directorio. En
caso de proyectos pequeños no es difícil mantener una unicidad de nombres, pero en caso
de grandes proyectos; o se sigue una norma desde el comienzo del proyecto, o éste se
convertirá en un auténtico caos.
No hay ninguna organización en Internet que controle esta nomenclatura, y muchas
de las aplicaciones Java corren sobre Web. Hay que tener presente que muchos servidores
Introducción al lenguaje Java

Web incluyen applets de múltiples orígenes, con lo cual parece poco menos que imposible
el evitar que alguien duplique nombres.
Como norma y resumen de todo lo dicho, a la hora de crear un paquete hay que
tener presente una serie de ideas:
 La palabra clave package debe ser la primera sentencia que aparezca en el fichero,
exceptuando, claro está, los espacios en blanco y comentarios
 Es aconsejable que todas las clases que vayan a ser incluidas en el paquete se
encuentren en el mismo directorio. Como se ha visto, esta recomendación se la puede uno
saltar a la torera, pero se corre el riesgo de que aparezcan determinados problemas difíciles
de resolver a la hora de compilar, en el supuesto caso de que no se hile muy fino
 Ante todo, recordar que en un fichero únicamente puede existir, como máximo, una
clase con el especificador de acceso public, debiendo coincidir el nombre del fichero con
el nombre de la clase
Paquetes de Java
El lenguaje Java proporciona una serie de paquetes que incluyen ventanas,
utilidades, un sistema de entrada/salida general, herramientas y comunicaciones. En la
versión actual del JDK, algunos de los paquetes Java que se incluyen son los que se
muestran a continuación, que no es una lista exhaustiva, sino para que el lector pueda tener
una idea aproximada de lo que contienen los paquetes más importantes que proporciona el
JDK de Sun. Posteriormente, en el desarrollo de otros apartados del Tutorial, se
introducirán otros paquetes que también forman parte del JDK y que, incorporan
características a Java que hacen de él un lenguaje mucho más potente y versátil, como son
los paquetes Java2D o Swing, que han entrado a formar parte oficial del JDK en la versión
JDK 1.2.
[Link]
Este paquete contiene clases diseñadas para usar con applets. Hay la clase Applet
y tres interfaces: AppletContext, AppletStub y AudioClip.

[Link]
El paquete Abstract Windowing Toolkit (awt) contiene clases para generar widgets
y componentes GUI (Interfaz Gráfico de Usuario), de manipulación de imágenes,
impresión, fuentes de caracteres, cursores, etc.. Incluye las clases Button, Checkbox,
Choice, Component, Graphics, Menu, Panel, TextArea, TextField...
[Link]
El paquete de entrada/salida contiene las clases de acceso a ficheros, de filtrado de
información, serialización de objetos, etc.: FileInputStream, FileOutputStream,
FileReader, FileWriter. También contiene los interfaces que facilitan la utilización de las
clases: DataInput, DataOutput, Externalizable, FileFilter, FilenameFilter,
ObjectInput, ObjectOutput, Serializable...
[Link]
Este paquete incluye las clases del lenguaje Java propiamente dicho: Object,
Thread, Exception, System, Integer, Float, Math, String, Package, Process, Runtime,
etc.
[Link]
Introducción al lenguaje Java

Este paquete da soporte a las conexiones del protocolo TCP/IP y, además, incluye
las clases Socket, URL y URLConnection.
[Link]
Este paquete incluye todos los interfaces que dan acceso a Bases de Datos a través
de JDBC, Java DataBase Connectivity, como son: Array, Blob, Connection, Driver,
Ref, ResultSet, SQLData, SQLInput, SQLOutput, Statement, Struct; y algunas clases
específicas: Date, DriveManager, Time, Types...
[Link]
Este paquete es una miscelánea de clases útiles para muchas cosas en
programación: estructuras de datos, fechas, horas, internacionalización,etc. Se incluyen,
entre otras, Date (fecha), Dictionary (diccionario), List (lista), Map (mapa), Random
(números aleatorios) y Stack (pila FIFO). Dentro de este paquete, hay tres paquetes muy
interesantes: [Link], que proporciona clases para leer y crear ficheros JAR;
[Link], que proporciona clases para manipular tipos MIME, Multipurpose
Internet Mail Extension (RFC 2045, RFC 2046) y [Link], que proporciona clases
para comprimir, descomprimir, calcular checksums de datos, etc. con los formatos
estándar ZIP y GZIP.
Introducción al lenguaje Java

Referencias
Java especifica sus tipos primitivos, elimina cualquier tipo de punteros y tiene
tipos referencia mucho más claros que C y C++.
Todo este maremágnum de terminología provoca cierta consternación, así que los párrafos
que siguen intentan aclarar lo que realmente significan los términos que se utilizan.
Se conocen ya ampliamente todos los tipos básicos de datos: datos base,
integrados, primitivos e internos; que son muy semejantes en C, C++ y Java; aunque Java
simplifica un poco su uso a los desarrolladores haciendo que el chequeo de tipos sea
bastante más rígido. Además, Java añade los tipos boolean y hace imprescindible el uso de
este tipo booleano en sentencias condicionales.
Punteros
C y C++ permiten la declaración y uso de punteros, que pueden ser utilizados en
cualquier lugar. Esta tremenda flexibilidad resulta muy útil, pero también es la causa de
que se pueda colgar todo el sistema.
La intención principal en el uso de los punteros es comunicarse más directamente
con el hardware, haciendo que el código se acelere. Desafortunadamente, este modelo de
tan bajo nivel hace que se pierda robustez y seguridad en la programación y hace muy
difíciles tareas como la liberación automática de memoria, la defragmentación de
memoria, o realizar programación distribuida de forma clara y eficiente.
Referencias en Java
Las referencias en Java no son punteros ni referencias como en C++. Este hecho
crea un poco de confusión entre los programadores que llegan por primera vez a Java. Las
referencias en Java son identificadores de instancias de las clases Java. Una referencia
dirige la atención a un objeto de un tipo específico. No hay por qué saber cómo lo hace ni
se necesita saber qué hace ni, por supuesto, su implementación.
Piénsese en una referencia como si se tratase de la llave electrónica de la
habitación de un hotel. Vamos a utilizar precisamente este ejemplo del Hotel para
demostrar el uso y la utilización que se puede hacer de las referencias en Java. Primero se
crea la clase Habitacion, que está implementada en el fichero , mediante instancias de la
cual se levantará el Hotel:
public class Habitacion {
private int numHabitacion;
private int numCamas;

public Habitacion() {
habitacion( 0 );
}

public Habitacion( int numeroHab ) {


habitacion( numeroHab );
}

public Habitacion( int numeroHab,int camas ) {


habitacion( numeroHab );
camas( camas );
}

public synchornized int habitacion() {


return( numHabitacion );
}
Introducción al lenguaje Java

public synchronized void habitacion( int numeroHab ) {


numHabitacion = numeroHab;
}

public synchronized int camas() {


return( camas );
}

public syncronized void camas( int camas ) {


numCamas = camas;
}
}
El código anterior sería el corazón de la aplicación. Ahora se construye el Hotel
creando Habitaciones y asignándole a cada una de ellas su llave electrónica; tal como
muestra el código siguiente, :
public class Hotel1 {
public static void main( String args[] ) {
Habitacion llaveHab1; // paso 1
Habitacion llaveHab2;

llaveHab1 = new Habitacion( 222 ); // pasos 2, 3, 4 y 5


llaveHab2 = new Habitacion( 1144,3 );
// ^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^
// A B y D C
}
}
Para explicar el proceso, se dividen las acciones en los cinco pasos necesarios para
poder entrar en la habitación del hotel. Aunque no se incluye, se puede también considerar
el caso de la necesidad de un cerrajero, para que ante la pérdida de la llave se pueda abrir
la puerta; y que, en el caso particular de este hotel virtual Java, sería el garbage collector,
que recicla la habitación una vez que se hayan perdido todas las llaves.
El primer paso es la creación de la llave, es decir, definir la variable referencia, por
defecto nula.
El resto de los pasos se agrupan en una sola sentencia Java. La parte B en el código
anterior indica al gerente del Hotel que ya dispone de una nueva habitación. La parte C
llama al decorador de interiores para que "vista" la habitación según un patrón
determinado, para que no desentonen unas habitaciones con otras y no se pierdan las señas
de identidad del hotel. El código electrónico que permitirá acceder a la habitación se
genera en la parte D, una vez conocido el interior de la habitación y ya se programa en la
llave en la parte A.
Si se abandona el ejemplo real a un lado y se detiene uno en lo que ocurre en la
ejecución del código, se observa que el operador new busca espacio para una instancia de
un objeto de una clase determinada e inicializa la memoria a los valores adecuados. Luego
invoca al método constructor de la clase, proporcionándole los argumentos adecuados. El
operador new devuelve una referencia a sí mismo, que es inmediatamente asignada a la
variable referencia.
Se pueden tener múltiples llaves para una misma habitación:
. . .
Habitacion llaveHab3,llaveHab4;
llaveHab3 = llaveHab1;
llaveHab4 = llavehab2;
Introducción al lenguaje Java

De este modo se consiguen copias de las llaves. Las habitaciones en sí mismas no


se han tocado en este proceso. Así que, ya hay dos llaves para la habitación 222 y otras
dos para la habitación 1144.
Una llave puede ser programada para que funcione solamente con una habitación
en cualquier momento, pero se puede cambiar su código electrónico para que funcione con
alguna otra habitación; por ejemplo, para cambiar una habitación anteriormente utilizada
por un empedernido fumador por otra limpia de olores y con vistas al mar. Se cambiaría la
llave duplicada de la habitación del fumador (222) por la habitación con olor a sal marina,
1144:
. . .
llaveHab3 = llaveHab2;
Ahora hay una llave para la habitación 222 y tres para la habitación 1144;
manteniendo una llave para cada habitación en la conserjería, para poder utilizarla como
llave maestra, en el caso de que alguien pierda su llave propia.
Alguien con la llave de una habitación puede hacer cambios en ella, y los
compañeros que tengan llave de esa misma habitación, no tendrán conocimiento de esos
cambios hasta que vuelvan a entrar en la habitación. Por ejemplo, si que quita una de las
camas de la habitación, entrando en ella con la llave maestra:
. . .
[Link]( 2 );
Ahora cuando los inquilinos entren en la habitación podrán comprobar el cambio
realizado:
. . .
[Link]();
Referencias y Arrays
Como en C y C++, Java dispone de arrays de tipos primitivos o de clases. Los
arrays en C y C++ son básicamente un acompañante para los punteros. En Java, sin
embargo, son ciudadanos de primera clase.
Para expandir el hotel creando todo un ala de habitaciones, , se creará un juego de
llaves maestras y luego se construirán las habitaciones:
public class Hotel2 {
// Número de habitaciones por ala
public static final int habPorAla = 12;

public static void main( String args[] ) {


Habitacion llavesMaestras[]; // paso 1
llavesMaestras = new Habitacion[ habPorAla ]; // pasos 2-5
// ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// A B, C, D y E
int numPiso = 1;
for( int i=0; i < habPorAla; i++ ) // pasos 6-9
llavesMaestras[ i ] = new Habitacion( numPiso * 100 + i,
( 0 == (i%2)) ? 2 : 1 );
for( int i=0; i < habPorAla; i++ ) // pasos 10-11
llavesMaestras[i].printData();
}
}
Cada paso en el ejemplo es semejante al que ya se ha visto antes. El paso 1
especifica que el juego de llaves maestras es un grupo de llaves de habitaciones.
Introducción al lenguaje Java

Los pasos 2 a 5 son, en este caso, la parte principal. En lugar de crear una
habitación, el gerente ordena construir un grupo contiguo de habitaciones. El número de
llaves se especifica entre corchetes y todas se crean en blanco.
Los pasos 6 a 9 son idénticos a los pasos 2 a 5 del ejemplo anterior, excepto en que
en este caso todas las llaves pasan a formar parte del juego maestro. Los números de piso
se dan en miles para que cuando se creen las habitaciones, todas tengan el mismo formato.
También todas las habitaciones de número par tienen una sola cama, mientras que las
habitaciones impares tendrán dos camas.
Los pasos 10 y 11 permiten obtener información de cada una de las habitaciones
del hotel.
Referencias y Listas
Hay gente que piensa que como Java no dispone de punteros, resulta demasiado
complejo construir listas enlazadas, árboles binarios y grafos. Java dispone de estructuras
de datos de esos tipos y además proporciona otras muchas como: mapas, conjuntos, tablas
Hash, diccionarios, etc., que en el JDK 1.2 están muy bien diseñadas y siguen la
nomenclatura al uso de la OOP. No obstante, en los párrafos siguientes se demuestra que
quien así piensa está bastante equivocado, porque incluso de forma pedestre se pueden
construir estas estructuras de datos sin demasiado esfuerzo.
Hay que retomar el ejemplo de los arrays, y en vez de éstos utilizar una lista
doblemente enlazada. El paquete de la lista simple se compone de dos clases. Cada
elemento de la lista es un NodoListaEnlazada, :
public class NodoListaEnlazada {
private NodoListaEnlazada siguiente;
private NodoListaEnlazada anterior;
private Object datos;
// . . .
}
Cada NodoListaEnlazada contiene una referencia a su nodo precedente en la lista y
una referencia al nodo que le sigue. También contiene una referencia genérica a cualquier
clase que se use para proporcionar acceso a los datos que el usuario proporcione.
La lista enlazada, , contiene un nodo principio-fin y un contador para el número de
nodos en la lista:
public class ListaEnlazada {
public NodoListaEnlazada PrincipioFin;
private int numNodos;
// . . .
}
El nodo especial PrincipioFin es sencillo, para simplificar el código. El contador se
usa para optimizar los casos más habituales.
Hay que revisar pues el código del Hotel, ahora , que será prácticamente el mismo
que en el caso de los arrays:
public class Hotel3 {
// Número de habitaciones por ala
public static final int habPorAla = 12;

public static void main( String args[] ) {


ListaEnlazada llaveMaestra; // paso 1
llaveMaestra = new ListaEnlazada(); // pasos 2-5
Introducción al lenguaje Java

int numPiso = 1;
for( int i=0; i < habPorAla; i++ ) // pasos 6-9
[Link]( i,
new Habitacion( numPiso * 100 + i,
( 0 == (i%2)) ? 2 : 1 );
for( int i=0; i < habPorAla; i++ ) // pasos 10-12
( (Habitacion)[Link](i) ).printData();
}
}
El paso 1 es la llave maestra de la lista. Está representada por una lista generica; es
decir, una lista de llaves que cumple la convención que se ha establecido. Se podría
acelerar el tiempo de compilación metiendo la lista genérica ListaEnlazada dentro de una
ListaEnlazadaHabitacion.
Los pasos 2 a 5 son equivalentes a los del primer ejemplo. Se construye e inicializa
una nueva ListaEnlazada, que se usará como juego de llaves maestras.
Los pasos 6 a 9 son funcionalmente idénticos a los del ejemplo anterior con arrays,
pero con diferente sintaxis. En Java, los arrays y el operador [] son internos del lenguaje.
Como Java no soporta la sobrecarga de operadores por parte del usuario, solamente se
puede utilizar en su forma normal.
La ListaEnlazada proporciona el método insertAt() que coge el índice en la lista,
donde el nuevo nodo ha de ser insertado, como primer argumento. El segundo argumento
es el objeto que será almacenado en la lista. Obsérvese que no es necesario colocar moldeo
alguno para hacer algo a una clase descendiente que depende de uno de sus padres.
Los pasos 10 a 12 provocan la misma salida que los pasos 10 y 11 del ejemplo con
arrays. El paso 10 coge la llave del juego que se indica en el método getAt(). En este
momento, el sistema no sabe qué datos contiene la llave, porque el contenido de la
habitación es genérico. Pero el programa sí sabe lo que hay en la lista, así que informa al
sistema haciendo un moldeado a la llave de la habitación (este casting generará un
chequeo en tiempo de ejecución por parte del compilador, para asegurarse de que se trata
de una Habitacion). El paso 12 usa la llave para imprimir la información.

También podría gustarte