Objetos en Java: Creación y Gestión
Objetos en Java: Creación y Gestión
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
[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;
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:
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];
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.
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
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
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
Control de Acceso
Introducción al lenguaje Java
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;
}
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();
class java509 {
// Variable de instancia
int miVariable;
// Constructor de la clase
public java509() {
miVariable = 5;
}
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;
}
import [Link].*;
class java512 {
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;
}
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
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.
interface Luchar {
void luchar();
}
interface Nadar {
void nadar();
}
interface Volar {
void volar();
}
class Accion {
public void luchar() {}
}
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
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 );
}
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;
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.