Java vs. C++: un Estudio Comparativo de Java

por Pedro Agull� Soliveres, [email protected]

Introducci�n

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: a medio camino entre C++ y Smalltalk

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++.

Hola en Java

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.

Tipos predefinidos y variables

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" );

Operadores

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++.

Estructuras de control

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;
	};

Clases y objetos

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 {
	};

Excepciones

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.

Paquetes: las librer�as Java

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).

Threads

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.

Applets

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().

Otros

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.

Acerca del autor

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.

Hosted by www.Geocities.ws

1