|
por Pedro Agull� Soliveres, [email protected]
Java está en boca de todos: los applets, pequeños componentes desarrollados en Java, están arrasando en la Web, y gran cantidad de compañías dedicadas al desarrollo de herramientas de programación tienen o han anunciado la pronta aparición de herramientas de desarrollo basadas en Java, la mayor parte de ellas visuales: es el caso de Visual J++ (Jakarta) de Microsoft, Latté de Borland, Café de Symantec, etc. Java es, pues, un lenguaje cuya importancia está creciendo rápidamente y que no quedará circunscrito al desarrollo de applets para Internet: por su potencia, su campo de aplicación es el mismo de lenguajes como Smalltalk o el mismísimo C++, con los que se puede comparar favorablemente. Java es un lenguaje que le debe mucho a C++: este artículo trata de explicar qué es y cómo se escribe Java comparándolo con C++.
Java se parece mucho en la sintaxis a C++, pero por su filosofía se puede considerar como un lenguaje a mitad de camino entre C++ y Smalltalk. Java incorpora recolección automática de memoria (garbage collection), como Smalltalk, lo que elimina una de las fuentes de problemas más importante en C++, la gestión de memoria. Sin embargo, como C++, Java es un lenguaje tipificado, en el que se chequean tipos en la compilación, no como en Smalltalk, donde nos enteramos de que estamos pasando un dato del tipo equivocado a un método solo al ejecutar el programa (habitualmente con resultados fatales). Java dispone, como Smalltalk, de una librería de clases estándar, algo de lo que C++ careció al comienzo (aunque hoy ya dispone de una librería de clases estándar). Como sucede con Smalltalk, Java tiene una clase base única, Object, mientras que en C++ una clase no tiene por qué tener una clase base, pero, al contrario que con Smalltalk, Java admite herencia múltiple, aunque en una forma un tanto distinta a la de C++ (y menos complicada de utilizar, algo en lo que Java se distingue notablemente de C++).
En Java no existen funciones libres como en C++: todo se hace a través de objetos, a la Smalltalk. Sin embargo, Java sí dispone de tipos primitivos como enteros, caracteres, etc, como en C++, al contrario que en Smalltalk, donde todos los tipos de datos son clases. Por último, Java no dispone de templates, una característica de C++ extremadamente potente, ni de macros, ni nos va a dejar acceder directamente a la memoria: no hay punteros en Java.
Como vemos, Java se halla realmente en un punto intermedio entre C++ y Smalltalk, a pesar de que su un vistazo a código fuente Java pueda hacernos creer que es básicamente C++.
Seguramente la mejor forma de comenzar a explicar
la sintaxis de un nuevo lenguaje de programación es escribir
el programa "Hola mundo". Aquí está la
versión de "Hola mundo" para Java:
import java.io.*;
// ¡Saludando al mundo!
class hola {
public static void main( String args[ ) {
System.out.print( "Hola mundo\n");
};
};
El programa que hemos escrito debe guardarse en un archivo llamado hola.java, ya que Java solo proporciona acceso a una única clase en cada archivo (si hay más, no son visibles fuera del archivo): dicha clase y el archivo deben tener el mismo nombre.
Examinemos en detalle las líneas de código
fuente:
import java.io.*;
le está diciendo al compilador de java que
queremos utilizar la librería java.io, dedicada a entrada
y salida, y además todas sus clases, de ahí el asterisco.
Es comparable a un #include en C++. La línea,
// ¡Saludando al mundo!
es un comentario, como en C++. También podríamos escribir /* ¡Saludando al mundo!*/.
En la siguiente línea estamos declarando una
clase, llamada hola: no es posible tener funciones libres en Java,
por lo que la función principal de la aplicación
(main() en C++) debe ser un método
de una clase, por lo que necesitamos definir al menos una en nuestra
aplicación.
class hola {
...
};
Esa clase tiene un método público,
llamado main,
public static void main( String args[ ) {
...
};
que es el que el intérprete de Java invocará al arrancar la aplicación: es necesario que tenga ese nombre y sea un método público de clase (static, igual que en C++) para que el intérprete de Java lo encuentre y pueda ejecutar la aplicación. No existen punteros en Java, así que adios a char*, y bienvenida a la clase String: a main se le pasa la línea de comandos en un array de Strings, llamado args.
Por último, main
invoca a System.out.print para imprimir
"Hola, mundo".
System.out.print( "Hola, mundo\n");
System es una clase de la librería de clases estándar de Java: contiene un objeto, out, capaz de enviar texto a la salida estándar a través de métodos como print.
Como se puede ver, a pesar de las diferencias, un programa Java es fácil de leer para un programador de C++ (y casi para los de C).
Como último paso, queda compilar el programa
y ejecutarlo. Basta para ello con ejecutar las siguientes dos
líneas de comando:
javac hola.java
que compila el programa, y para ejecutarlo
java hola
Esta última línea hace que el intérprete cargue el archivo hola.class, generado por javac.exe, encuentre la clase del mismo nombre con un método público y estático llamado main, y proceda a ejecutar el programa invocando ese método.
Una característica de Java es que el código generado no es código máquina, sino un código intermedio (bytecodes), que luego es interpretable por cualquier máquina virtual Java, lo que le confiere su excepcional portabilidad al lenguaje. Esto hace que las prestaciones de los programas Java no sean excesivamente altas: sin embargo, hoy en día ya existen compiladores just-in-time, que conforme cargan las aplicaciones Java generan el código máquina correspondiente al vuelo, de modo que la ejecución de éstas se lleva a cabo a velocidad de código compilado.
Java cuenta con tipos predefinidos similares a los de C++, pero aquí su tamaño está perfectamente delimitado: no nos vamos a encontrar con ints de 32 bits en un compilador de Java y de 16 bits en otro. Los tipos predefinidos son: byte (ocupa 8 bits), boolean (1 bit, puede adoptar los valores truey false), short (16 bits), int (32 bits), long (64 bits), float (4 bytes), double (8 bytes) y char (2 bytes). Java trabaja con cadenas Unicode, que soportan caracteres de otros alfabetos, como cirílico, kanji, etc., de ahí que un char tenga 2 bytes: con uno solo podría representar 255 caracteres distintos.
En Java no hay uniones ni structs, algo innecesario
si tenemos la posibilidad de definir clases, y los arrays son
mucho más domésticos que en C++: nos avisan cuando
se produce un desbordamiento, elevando una excepción, algo
que en C++ puede pasar inadvertido y es fuente de muchos problemas.
int i;
char x[];
x = new char[5]
x[10] = 'a'; /* Error: se sale del array, y eleva una excepción */
En Java los arrays se crean con new,
como cualquier objeto. No es correcto escribir el siguiente código,
que en C++ compilaría sin problemas:
int i;
char x[5];
// ...
Java incorpora clases para que podamos trabajar con cadenas: no existe la cadena tipo char*, dado que Java se niega en redondo a dejarnos utilizar punteros. En su lugar usamos objetos de una de dos clases: String si vamos a guardar una cadena que no va a ser modificada, o StringBuffer si vamos a guardar una cadena que deseamos sea modificable. El que existan dos clases distintas para gestionar cadenas se debe a la gran diferencia de eficiencia entre manejar cadenas de contenido y tamaño fijos, o permitir que puedan redimensionarse.
A diferencia de C++, en Java la conversión
de un objeto de un tipo a otro (typecasting) se chequea
siempre, de modo que se comprueba que es correcta la conversión:
si no es así, se elevará una excepción del
tipo ClassCastException. Por ejemplo, en el siguiente fragmento
de código no es válido convertir objeto a
String, puesto que es del tipo Object, y por tanto Java eleva
una excepción al llegar al punto del código donde
tratamos de realizar la conversión, en lugar de dejarnos
seguir adelante (lo que seguramente tendría efectos fatales).
Object objeto = new Object();
String mensaje = (String)objeto; /* Error de ejecución, objeto no es una String: Java eleva una excepción */
Java también nos permite averiguar si cierto
objeto es de una clase dada, mediante instanceof.
El siguiente código imprime distintos mensajes según
que objeto sea o no de la clase String.
if( objeto instanceof String )
System.out.println( "objeto es una String" );
else
System.out.println( "objeto NO es una String" );
Por lo que respecta al uso de variables, Java incorpora
un par de características que lo diferencian de C++: en
primer lugar, no existen variables globales en Java, todo debe
estar dentro de una clase en última instancia, lo que no
es problema porque se pueden tener variables de clase, como en
C++. En segundo lugar, Java no permite utilizar una variable a
la que no se le haya asignado previamente un valor, lo que evita
los problemas derivados de olvidar inicializar variables. El siguiente
código no compilará en Java:
int i;
if( i < 10 ) /* Este programa no compila: hay que asignarle previamente un valor a i */
System.out.print( "Hola\n" );
Todos los operadores existentes en C++ existen en
Java y tienen la misma función. Java añade ">>>",
para eliminar la ambigüedad que hay con respecto a ">>"
en C++. ¿Se debe o no conservar el bit de signo cuando realizamos
un desplazamiento de bits a la derecha? Esto no está definido
en C++, pero en Java se elimina el problema haciendo que ">>"
desplace bits a la derecha, pero conservando el bit de signo,
mientras que ">>>" desplaza bits sin preocuparse
de conservar el bit de signo. Esta preocupación por eliminar
todas las fuentes de confusión posibles es una constante
en Java, mientras que en C++ hay muchísimos detalles que
se dejan a elección del compilador. Si no quedó
clara la diferencia entre conservar el bit de signo o no, se puede
ver la diferencia ejecutando el siguiente código:
System.out.println(-4 >>> 1); /* Imprime "2147483646", no se conserva el signo */
System.out.println(-4 >> 1); /* Imprime "-2", se conserva el signo */
Otra diferencia entre C++ y Java en cuanto a los operadores es que en Java no es posible sobrecargar operadores: no será posible, por ejemplo, definir "+" como un operador que concatena Strings, algo muy común en C++.
Las estructuras de control en Java son idénticas
a las de C++, con una excepción, que no existe goto. Las
estructuras de ejecución condicional son:
if( se_cumple_condicion ) {
// Nuestro código si se cumple la condición
}
else {
// Nuestro código si no se cumple la condición
}
switch( opcion ) {
case 1: // Operación 1
break;
case 2: // Operación 2
break;
default:
}
Para iterar disponemos de while, while...do
y for:
while( se_cumpla_condicion ) {
// Nuestro código
};
do {
// Nuestro código
} while( se_cumpla_condicion );
for( i = valor_inicial; i < valor_final; i++ ) {
// Código a repetir
};
Como en C++, es posible salir de un bucle for,
while o do
mediante el uso de break. También
es posible saltarse lo que queda de bucle y continuar al comienzo
del bucle en la siguiente iteración con continue.
for( i = 1; i < 15; i++ ) {
if( i == 10 )
continue;
// Código a repetir, excepto cuando i = 10
if( necesario_salir_del_bucle )
break;
};
Hay bastantes diferencias importantes entre Java y C++ en lo que respecto al tratamiento de clases y objetos, aunque no todas de fondo. En Java la declaración e implementación de una clase se escriben en un único archivo (xxx.java), mientras que en C++ lo normal es tener un archivo de cabecera para la declaración (xxx.h), y otro para la implementación (xxx.cpp).
Todos los objetos Java se crean en el heap
de la aplicación, mediante new, no pudiéndose
crear en la pila, como ocurre en C++. Solo la segunda de las siguientes
líneas de código es correcta en Java:
Object x = Object(); /* No se puede crear un objeto en la pila */
Object y = new Object(); /* Correcto, se deben crear los objetos con new */
En Java no hay que preocuparse de liberar explícitamente la memoria asignada a los objetos, como en C++, puesto que Java ejecuta un proceso de recolección de memoria en segundo plano que se encarga de liberar la memoria de aquellos objetos que ya no son utilizables. Esta es una de las características más interesantes de Java: se ha escrito mucho sobre la recolección automática de memoria en general, y en C y C++ en particular.
En Java hay una clase base, Object, y si no se declara explícitamente la clase base de una clase implementada por el programador, se considera que ésta es Object.
Al contrario que en C++, todos los métodos
en Java son virtuales: es posible, sin embargo, evitar que una
clase derivada redefina un método declarándolo final,
como en la línea siguiente:
public final void noRedefinir() {};
Un detalle a tener en cuenta es que los métodos final suelen recibir mejor optimización por parte de los compiladores Java. Si lo que declaramos final es un atributo, entonces este es constante: no se puede modificar, y se le debe asignar un valor en la misma declaración.
En Java, como en C++, es posible sobrecargar una
función, de modo que podemos definir dos funciones con
el mismo nombre pero argumentos de distinto tipo: el compilador
se encargará de utilizar el método adecuado dependiendo
de los argumentos que le pasemos. Por tanto, el siguiente código
compilará,
public void imprimir( String s ) { System.out.print( s ); };
public void imprimir( int i ) { System.out.print( i ); };
y podremos escribir código como
miObjeto.imprimir( "Hola\n" );
miObjeto.imprimir( 100 );
Java, como C++, dispone de constructores, utilizados para crear un objeto de una clase dada: como en C++, deben tener el mismo nombre de la clase, y no pueden devolver ningún tipo de dato (ni void). Hay que tener esto presente, dado que si expecificamos que un constructor devuelve un tipo, Java no lo considerará ningún error, simplemente lo tratará como a un método más, en lugar de considerarlo un constructor. Si no definimos ningún constructor, Java genera por defecto un constructor sin argumentos, como C++.
También existe en Java el concepto de constructor
de copia, que nos permite crear un objeto a partir de otro de
la misma clase: para una hipotética clase MiClase,
el constructor de copia sería
MiClase( MiClase objetoACopiar );
Al contrario que con C++, Java no genera un constructor
de copia por defecto, ni ninguno de los otros métodos que
C++ genera por defecto. La creación de un objeto se lleva
a cabo de la siguiente forma:
MiClase miObjeto = new MiClase( ... ); /* Se invoca al constructor de MiClase */
En Java existe algo parecido a los destructores de C++, aunque su utilidad es bastante dudosa debido al mecanismo de recolección de memoria incorporado, que resulta suficiente para llevar a cabo las tareas de destrucción de objetos. El método a redefinir es finalize(), a diferencia de C++, donde el destructor debería llamarse ~MiClase.
Por lo que respecta a la herencia, se especifica
mediante la palabra extends, por ejemplo
MiClase extends Object { ... };
indica que estamos definiendo una clase llamada MiClase, que deriva de Object.
Es posible invocar un método de la clase base
(o un atributo) desde una clase derivada mediante la palabra reservada
super, por ejemplo:
super.metodoDeClaseBase();
En Java, como en C++, es posible tener métodos
y atributos de clase, para lo que se utiliza la palabra reservada
static. La siguiente clase tiene
un método y un atributo de clase.
MiClase extends Object {
public static void metodoDeClase() {};
private static int atributoDeClase;
static {
System.out.println( "Constructor de clase llamado" );
}
};
Además de declarar un método y un atributo de clase, hemos escrito un fragmento de código precedido de la palabra static, que se ejecutará la primera vez que se haga uso de la clase: es el constructor de clase, algo que no existe en C++.
Una de las características más potentes de los lenguajes Orientados a Objeto es la capacidad que tienen de implementar mecanismos, en los que se define el modo en que interactúan varios objetos de distintas clases de forma genérica, y se deja habitualmente el manejo concreto de los detalles en manos de clases derivadas de las que participan en la interacción: es conveniente encapsular estos detalles en métodos, de modo que aquellas clases que quieran participar en el mecanismo redefinan dichos métodos de acuerdo a sus necesidades, respetando el esquema general de funcionamiento del mecanismo.
Para obligar a las clases que participan en un mecanismo
a implementar ciertos métodos, se las puede hacer derivar
de una clase base que obligue a redefinir dichos métodos:
para ello, basta con declararlos como abstractos en la clase base,
lo que se hace incluyendo la palabra abstract
delante del método
abstract class claseAbstracta {
abstract void metodoAbstracto();
};
Java exige que declaremos a la clase abstracta si ésta tiene algún método abstracto, motivo por el que hemos añadido la palabra abstract delante de class. No se puede crear un objeto de una clase abstracta: puede ser interesante por este motivo declarar abstracta una clase aunque no tenga definido ningún método como abstracto, cosa que Java permite.
El caso más extremo de clase abstracta son
las clases interface, en las que
todos los métodos son abstractos, y todos los atributos
son final (es decir, no pueden modificarse).
Este tipo de clase se define como sigue:
interface ClaseInterface {
int metodoAbstracto();
int atributoFinal;
};
Como se puede ver, no es necesario especificar que
los métodos son abstractos y los atributos son finales
para este tipo de clases, utilizando abstract
y final, dado que estas características
van implícitas en la declaración de la clase como
de interface. Estas clases son especialmente
importantes porque una clase Java puede heredar de más
de una clase (herencia múltiple), siempre y cuando
todas menos una de las clases sean clases interface. Veamos cómo
se hace:
class ThreadConsultaBD extends ConsultaBD, implements Runnable {
public void run() {};
};
En el ejemplo anterior tenemos una clase que deriva de una ConsultaBD: ahora bien, queremos que pueda ejecutarse en su propio thread, motivo por el que deriva también de Runnable, una clase de la librería estándar de Java de la que hablaremos más adelante. Runnable solo proporciona un interface que ThreadConsultaDB debe redefinir para poder ejecutarse en su propio thread, y nada más. El hecho de que exista la clase Runnable y derivemos de ella nos proporciona consistencia en el interface de las distintas clases que deseemos tengan la capacidad de ejecutarse en su propio thread: todas deben redefinir run(), que Java ya sabe cómo tratar para que funcione correctamente como thread independiente. El mecanismo de herencia múltiple de Java es, gracias al concepto de clase de interface, mucho menos complejo (y peligroso) que el de C++.
La última característica que vamos
a estudiar, y una de las más importantes de un lenguaje
Orientado a Objeto, es la posibilidad de proporcionar distintos
niveles de visibilidad a los distintos métodos y atributos
de un objeto, lo que nos permite una encapsulación eficaz
de la información. En Java un atributo o método
puede tener uno de cinco niveles de visibilidad, y debe especificarse
para cada uno de los atributos o métodos por separado.
Veamos un ejemplo
class NivelesDeVisibilidad {
public int atributoPublico;
private int atributoPrivado;
protected int atributoProtected;
/* friendly */ int atributoFriendly;
private protected int atributoPrivadoProtected;
};
En el ejemplo anterior se han introducido varias palabras reservadas que controlan el nivel de visibilidad de los atributos. La primera es public: un atributo con este nivel de acceso es visible desde cualquier parte del programa, no existe ninguna restricción de visibilidad. Con private sucede todo lo contrario: el atributo solo es accesible desde dentro de la clase donde se define. Entre estos dos niveles de visibilidad nos encontramos con varios puntos intermedios: un atributo private protected es accesible por la propia clase en que se define, y cualquiera de las clases derivadas, sin importar donde estén ubicadas. Estos tres niveles de acceso corresponden a los existentes en C++, public, private y protected.
Los otros niveles de acceso no existen en C++, y están relacionados con el concepto de paquete en Java, que se estudia más adelante, y que es similar al de librería en C++, aunque más potente. Un atributo protected solo es accesible por la clase en que se define, por sus subclases, estén donde estén, y por otras clases del mismo paquete (subsistema). El último nivel de acceso existente es el nivel por defecto, friendly, aunque esta no es una palabra reservada, simplemente se asigna a los atributos para los que no se especifica ningún nivel de visibilidad explícitamente: un atributo friendly es visible desde la clase en que se define y en cualquier otra clase del mismo paquete. Una subclase en otro paquete no tendrá acceso a dicho atributo.
Por lo que respecta al control de visibilidad de
atributos y métodos en una clase, solo queda decir que
no existe el concepto de atributo o clase friend en Java,
al contrario que en C++, y que tan solo una clase de todas las
definidas en un archivo es visible fuera de él, la que
tiene el mismo nombre: todas las demás solo tienen utilidad
como clases auxiliares de ésta. A la clase visible desde
el exterior se le puede añadir la palabra reservada public
delante, por ejemplo:
public class hola {
};
Java, al igual que C++, proporciona un mecanismo
de manejo de excepciones muy potente, y utiliza las mismas palabras
reservadas, throw, try
y catch. Veamos un ejemplo:
try {
// ...
throw new ErrorDeBaseDeDatos();
// ...
}
catch( ErrorDeRed error ) {
System.out.println( "Se ha producido un error" );
};
En el código anterior, todo error que se produzca dentro del bloque try del tipo ErrorDeRed se tratará en el bloque catch(ErrorDeRed error), que no se ejecutará si no se produce ningún error o se produce un error de otro tipo. La línea con throw eleva una excepción, del tipo ErrorDeBaseDeDatos, por lo que cualquier código que siga a esta línea no se ejecutará, el error pasará al primer bloque catch que sepa tratar ese tipo de errores, aunque esté en otro método que llamaba a este código. Si desearamos tratar este tipo de error, deberíamos haber escrito un bloque catch( ErrorDeBaseDeDatos error ), o bien un bloque catch( ClaseBaseDeErrorDeBaseDeDatos error ).
Java nos permite conocer para cada método
de cada clase todos y cada uno de los tipos de errores
(léase excepciones) que se pueden dar dentro de él,
una característica realmente interesante a la hora de diseñar
nuestro tratamiento de errores: en todo momento podemos saber
cada tipo de error que se puede llegar a dar en un fragmento de
código. Es necesario dar una ayuda al compilador para ello,
para lo que hemos de indicar, al escribir un método, qué
excepciones pueden llegar a darse dentro de él. Veamos
un ejemplo, un método llamado hacerAlgo()
que puede elevar ErrorBaseDatos por sí mismo, y
que llama a otros métodos que a su vez pueden elevar excepciones
del tipo ErrorDeRed:
public void hacerAlgo() throws ErrorBaseDatos, ErrorDeRed {...};
Como vemos, hemos de añadir a continuación del nombre del método la palabra reservada throws, seguida de la lista de excepciones que el método puede elevar, sea porque las eleva él directamente, sea porque las eleva alguno de los métodos que utiliza. Para asegurarse de que estamos bien informados sobre los problemas que nos puede dar el código que escribimos, Java se negará a compilar el código si se nos olvida alguna excepción en la lista, algo que C++ no hará por nosotros.
Para hacer práctica la escritura de la lista de excepciones, Java no nos obliga a incluir en ella las excepciones definidas por el lenguaje: estas son las derivadas de RuntimeException, lo que incluye a las aritméticas, las derivadas de usar objetos null inadecuadamente, etc. Si esto no fuera así, la lista de excepciones se volvería larga e inmanejable.
Java añade una nueva palabra reservada, finally,
que no existe en C++. Todo lo que aparezca entre llaves tras finally
será ejecutado siempre, no importa que se produzca una
excepción en cualquier punto del método o no. Esto
nos permite llevar a cabo la liberación de recursos de
forma adecuada incluso aunque se produzca un error en el programa:
por ejemplo, en el siguiente fragmento de código siempre
se cerraría archivo, aunque se elevase una excepción
en cualquier punto dentro del bloque try, al estar el código
de cierre del archivo dentro del bloque finally:
archivo.abrir( "nombreArchivo" );
try {
/* No importa que ocurra un error o no aquí, el código del bloque try cerrará el archivo SIEMPRE */
// ...
}
finally {
archivo.cerrar();
};
Por último, un detalle importante: la clase base de las excepciones es Throwable. Si deseamos definir nuestra propia clase de excepción, deberemos derivarla de ella o de alguna de sus subclases.
A las librerías Java se las llama paquetes, como en Ada. Los paquetes, además de hacer las veces de almacén de código, como una librería C++, son el equivalente a nivel de implementación del concepto de subsistema en la fase de análisis: pueden agrupar una serie de clases y objetos entre los cuales haya un nivel de visibilidad y cooperación grande (acceso a gran cantidad de detalles internos), y ofrecer al exterior (otros paquetes o nuestros programas) un interface más reducido. Todos los métodos y atributos de una clase que declaremos protected o con el nivel de acceso por defecto son accesibles por las demás clases del mismo paquete, incluso aunque no sean accesibles por subclases (caso del nivel de acceso por defecto), lo que permite una buena partición del software en subsistemas.
Para ubicar una clase en un paquete llamado nombrePaquete, debemos incluir en la primera línea del archivo donde definimos la clase package.nombrePaquete.
Para poder utilizar un paquete es necesario que sus clases estén ubicadas en el subdirectorio "nombrePaquete" de uno de los directorios incluidos en la variable de entorno CLASSPATH, que Java utiliza para localizarlos.
El modo en que se puede utilizar una clase de un paquete ya lo vimos en el ejemplo de "hola mundo": se debe incluir la línea import nombrePaquete.nombreClase;.
Java ya viene con un conjunto de paquetes estándar en los que se incluye soporte para E/S (java.io), trabajo en red (java.net), librerías gráficas (java.awt), y por supuesto una serie de clases y utilidades básicas (java.lang y java.util).
Java incorpora soporte para multithreading a través de las clases Thread y Runnable de su librería estándar, lo que significa que su soporte para programación concurrente es portable. Thread cuenta con métodos para hacer inactivo un thread indefinidamente o durante un tiempo determinado por el usuario (sleep() y suspend(), respectivamente), métodos para cambiar la prioridad, detener un thread, etc.
En lo que respecto al control del acceso concurrente
a recursos, es posible impedir que dos threads puedan acceder
a la vez a un mismo objeto a través de un método
dado, declarando a este synchronized,
como en el código siguiente:
public synchronized void actualizarSaldo() { cuenta.incrementaSaldo(10000); };
Otra forma de sincronizar el acceso al objeto cuenta
es declarándolo synchronized dentro de un bloque de sincronización,
lo que se hace como sigue:
public void actualizarSaldo() {
// ...
synchronized(cuenta) { /* Abrimos un bloque de sincronización */
cuenta.incrementaSaldo(10000);
};
// ...
}
En cualquiera de los dos casos anteriores Java secuencializa el acceso a cuenta, de modo que dos procesos no puedan incrementar a la vez su saldo y por tanto, potencialmente, desajustarlo. Sin embargo, en el segundo caso dos threads pueden entrar dentro del método a la vez, aunque no puedan acceder al objeto cuenta al mismo tiempo dentro del bloque synchronized.
Para permitir sincronizar threads la clase base de Java, Object, dispone de métodos wait() y notify().
Además de todo esto, es posible manejar grupos de threads como un único elemento, a través de la clase ThreadGroup, y cambiarles la prioridad, detenerlos, etc. en grupo.
En un futuro está previsto que se pueda utilizar la plabra reservada volatile para indicar que un atributo puede ser modificado por varios threads a la vez.
No podían faltar los applets tratándose de un artículo sobre Java. En realidad, no hay demasiado que decir sobre ellos desde el punto de vista del lenguaje: los applets no son más que clases Java derivadas de la clase Applet, que les proporciona varias características que los diferencian de otras clases: son cargados a través de la red por un navegador Web, por medio de páginas HTML, en lugar de ser cargadas y ejecutadas por el programa java.exe. Dado que el orígen de un applet puede ser absolutamente desconocido, tienen por defecto severas restricciones por motivos de seguridad, como no poder leer ni escribir archivos en el disco duro, no poder lanzar programas, etc.
Un applet se incluye en una página HTML mediante
un código especial, <APPLET CODE=archivoClase.class
WIDTH=anchoAreaDibujoApplet HEIGHT=altoAreaDibujoApplet>, y
se le pasan parámetros mediante una o más líneas
<PARAM NAME=nombreParam VALUE="valorParam">. Por
ejemplo, un simple applet de la clase mensaje, que tome
un parámetro llamado texto para mostrarlo en un área
de la ventana de 200 por 100 pixels, se insertará en una
página HTML con el siguiente código
<APPLET CODE="mensaje.class" WIDTH=200 HEIGHT=100>
<PARAM NAME=texto VALUE="Hola mundo">
</APPLET>
Para que el applet muestre el mensaje, habrá que redefinir su método paint() para que lo dibuje: no es necesario aquí declarar un méodo main(), como en las aplicaciones normales Java. El applet puede leer los parámetros mediante getParameter().
Java posee algunas características adicionales
que no hemos visto, pero que merece la pena mencionar, como el
soporte para enlazar código C con Java: se indica que un
método está implementado en C con la palabra reservada
native. Por ejemplo:
public native int hacerAlgo();
Debe generarse luego un envoltorio en C para estas funciones: el JDK de Sun incluye el programa javah para generar un fichero de cabecera (.H) adecuado, y un fichero fuente (.C), a partir de los cuáles escribir el código C que implemente hacerAlgo()
También está previsto añadir mecanismos de persistencia básicos a Java: cuando se implementen, se asumirá que todos los atributos pueden ser hechos persistentes, excepto aquellos declarados transient.
Pedro Agulló está especializado en consultoría y desarrollo RAD con Java, C++, Smalltalk y Delphi, así como en el desarrollo de programas para servidores Web. Ha sido jefe del equipo de desarrollo de Nuevas Tecnologías de Aguas de Alicante, perteneciente al grupo de Aguas de Barcelona, y en la actualidad trabaja para el Instituto Nacional de Empleo.
Podeis enviar correo electrónico al autor
a [email protected]. El autor mantiene una página en la red
sobre lenguajes y metodologías de análisis y diseño
Orientados a Objeto, en http://www.ctv.es/USERS/poo.htm, incluyendo
una página dedicada exclusivamente a Java. Todos los comentarios
son bienvenidos.