1.1 Origen de Java
Sun Microsystems, líder
en servidores para
Internet, uno de
cuyos lemas desde hace mucho tiempo es "the
network is the computer" (lo que quiere dar a entender que el
verdadero ordenador es la red en su conjunto y no cada
máquina individual), es quien ha desarrollado el lenguaje
Java, en un
intento de resolver simultáneamente todos los problemas que
se le plantean a los desarrolladores de software por la
proliferación de arquitecturas incompatibles, tanto entre
las diferentes máquinas como entre los diversos sistemas
operativos y sistemas de
ventanas que funcionaban sobre una misma máquina,
añadiendo la dificultad de crear aplicaciones distribuidas
en una red como
Internet.
He podido leer más de cinco versiones
distintas sobre el origen, concepción y desarrollo de
Java, desde la
que dice que este fue un proyecto que
rebotó durante mucho tiempo por
distintos departamentos de Sun sin que nadie le prestara ninguna
atención, hasta que finalmente encontró su nicho de
mercado en la
aldea global que es
Internet; hasta la más difundida, que justifica a
Java como
lenguaje de
pequeños electrodomésticos.
Hace algunos años, Sun Microsystems
decidió intentar introducirse en el mercado de la
electrónica de consumo y
desarrollar programas para
pequeños dispositivos electrónicos. Tras unos
comienzos dudosos, Sun decidió crear una filial,
denominada FirstPerson Inc., para dar margen de maniobra al
equipo responsable del proyecto.
El mercado
inicialmente previsto para los programas de
FirstPerson eran los equipos domésticos: microondas,
tostadoras y, fundamentalmente, televisión
interactiva. Este mercado, dada la
falta de pericia de los usuarios para el manejo de estos
dispositivos, requería unos interfaces mucho más
cómodos e intuitivos que los sistemas de
ventanas que proliferaban en el momento.
Otros requisitos importantes a tener en cuenta
eran la fiabilidad del código y la facilidad de desarrollo.
James Gosling, el miembro del equipo con más experiencia
en lenguajes de
programación, decidió que las ventajas
aportadas por la eficiencia de C++
no compensaban el gran coste de pruebas y
depuración. Gosling había estado
trabajando en su tiempo libre en
un lenguaje de
programación que él había llamado Oak,
el cual, aún partiendo de la sintaxis de C++, intentaba
remediar las deficiencias que iba observando.
Los lenguajes al uso, como C o C++, deben ser
compilados para un chip, y si se cambia el chip, todo el software debe compilarse de
nuevo. Esto encarece mucho los desarrollos y el problema es
especialmente acusado en el campo de la electrónica de consumo. La
aparición de un chip más barato y, generalmente,
más eficiente, conduce inmediatamente a los fabricantes a
incluirlo en las nuevas series de sus cadenas de producción, por pequeña que sea la
diferencia en precio ya
que,
multiplicada por la tirada masiva de los aparatos,
supone un ahorro
considerable. Por tanto, Gosling decidió mejorar las
características de Oak y
utilizarlo.
El primer proyecto en que
se aplicó este lenguaje
recibió el nombre de proyecto Green y
consistía en un sistema de
control completo
de los aparatos electrónicos y el entorno de un hogar.
Para ello se construyó un ordenador experimental
denominado *7 (Star Seven). El sistema
presentaba una interfaz basada en la representación de la
casa de forma animada y el control se
llevaba a cabo mediante una pantalla sensible al tacto. En el
sistema
aparecía Duke, la actual mascota de Java.
Posteriormente se aplicó a otro proyecto
denominado VOD (Video On Demand)
en el que se empleaba como interfaz para la
televisión interactiva. Ninguno de estos proyectos se
convirtió nunca en un sistema
comercial, pero fueron desarrollados enteramente en un Java primitivo y
fueron como su bautismo de fuego.
Una vez que en Sun se dieron cuenta de que a corto
plazo la
televisión interactiva no iba a ser un gran
éxito, urgieron a FirstPerson a desarrollar con rapidez
nuevas estrategias que
produjeran beneficios. No lo consiguieron y FirstPerson
cerró en la primavera de 1994.
Pese a lo que parecía ya un olvido
definitivo, Bill Joy, cofundador de Sun y uno de los
desarrolladores principales del Unix de Berkeley,
juzgó que Internet podría
llegar a ser el campo de juego adecuado
para disputar a Microsoft su
primacía casi absoluta en el terreno del software, y vio en Oak el
instrumento idóneo para llevar a cabo estos planes. Tras
un cambio de
nombre y modificaciones de diseño,
el lenguaje
Java fue presentado en sociedad en
agosto de 1995.
Lo mejor será hacer caso omiso de las
historias que pretenden dar carta de naturaleza a la
clarividencia industrial de sus protagonistas; porque la
cuestión es si independientemente de su origen y entorno
comercial, Java ofrece soluciones a
nuestras expectativas. Porque tampoco vamos a desechar la
penicilina aunque haya sido su origen fruto de la
casualidad.
1.2 Características de
Java
Las características principales que nos ofrece
Java respecto a cualquier otro lenguaje de
programación, son:
Simple
Java ofrece toda la funcionalidad de un lenguaje
potente, pero sin las características menos usadas y más
confusas de éstos. C++ es un lenguaje que
adolece de falta de seguridad, pero C
y C++ son lenguajes más difundidos, por ello Java se
diseñó para ser parecido a C++ y así
facilitar un rápido y fácil aprendizaje.
Java elimina muchas de las características de otros lenguajes como
C++, para mantener reducidas las especificaciones del lenguaje y
añadir características muy útiles como el
garbage collector (reciclador de memoria dinámica). No es necesario preocuparse de
liberar memoria, el
reciclador se encarga de ello y como es un thread de baja
prioridad, cuando entra en acción, permite liberar bloques
de memoria muy
grandes, lo que reduce la fragmentación de la
memoria.
Java reduce en un 50% los errores más
comunes de programación con lenguajes como C y C++ al
eliminar muchas de las características de éstos,
entre las que destacan:
aritmética de punteros
no existen referencias
registros (struct)
definición de tipos
(typedef)
macros (#define)
necesidad de liberar memoria
(free)
Aunque, en realidad, lo que hace es eliminar las
palabras reservadas (struct, typedef), ya que las clases son algo
parecido.
Además, el intérprete completo de
Java que hay en este momento es muy pequeño, solamente
ocupa 215 Kb de RAM.
Orientado a
objetos
Java implementa la tecnología
básica de C++ con algunas mejoras y elimina algunas cosas
para mantener el objetivo de la
simplicidad del lenguaje. Java trabaja con sus datos como
objetos y con interfaces a esos objetos. Soporta las tres
características propias del paradigma de
la orientación a objetos: encapsulación, herencia y
polimorfismo. Las plantillas de objetos son llamadas, como en
C++, clases y sus copias, instancias. Estas instancias, como en
C++, necesitan ser construidas y destruidas en espacios de
memoria.
Java incorpora funcionalidades inexistentes en C++
como por ejemplo, la resolución dinámica de métodos.
Esta característica deriva del lenguaje Objective C,
propietario del sistema operativo
Next. En C++ se suele trabajar con librerías
dinámicas (DLLs) que obligan a recompilar la
aplicación cuando se retocan las funciones que se
encuentran en su interior. Este inconveniente es resuelto por
Java mediante una interfaz específica llamada RTTI
(RunTime Type Identification) que define la interacción
entre objetos excluyendo variables de
instancias o implementación de métodos.
Las clases en Java tienen una representación en el runtime
que permite a los programadores interrogar por el tipo de clase y
enlazar dinámicamente la clase con el resultado de la
búsqueda.
Distribuido
Java se ha construido con extensas capacidades de
interconexión TCP/IP. Existen
librerías de rutinas para acceder e interactuar con
protocolos como
http y
ftp. Esto
permite a los programadores acceder a la información a través de la red con tanta facilidad como
a los ficheros locales.
La verdad es que Java en sí no es
distribuido, sino que proporciona las librerías y herramientas
para que los programas puedan
ser distribuidos, es decir, que se corran en varias
máquinas, interactuando.
Robusto
Java realiza verificaciones en busca de problemas
tanto en tiempo de
compilación como en tiempo de
ejecución. La comprobación de tipos en Java ayuda a
detectar errores, lo antes posible, en el ciclo de desarrollo.
Java obliga a la declaración explícita de métodos,
reduciendo así las posibilidades de error. Maneja la memoria
para eliminar las preocupaciones por parte del programador de la
liberación o corrupción
de memoria.
También implementa los arrays
auténticos, en vez de listas enlazadas de punteros, con
comprobación de límites, para evitar la posibilidad
de sobreescribir o corromper memoria resultado de punteros que
señalan a zonas equivocadas. Estas características
reducen drásticamente el tiempo de desarrollo de
aplicaciones en Java.
Además, para asegurar el funcionamiento de
la aplicación, realiza una verificación de los
byte-codes, que son el resultado de la compilación de un
programa Java.
Es un código de máquina virtual que es interpretado
por el intérprete Java. No es el código
máquina directamente entendible por el hardware, pero ya ha pasado
todas las fases del compilador: análisis de instrucciones, orden de
operadores, etc., y ya tiene generada la pila de ejecución
de órdenes.
Java proporciona,
pues:
Comprobación de punteros
Comprobación de límites de
arrays
Excepciones
Verificación de
byte-codes
Arquitectura
neutral
Para establecer Java como parte integral de la
red, el
compilador Java compila su código a un fichero objeto de
formato independiente de la arquitectura de
la máquina en que se ejecutará. Cualquier
máquina que tenga el sistema de
ejecución (run-time) puede ejecutar ese código
objeto, sin importar en modo alguno la máquina en que ha
sido generado. Actualmente existen sistemas run-time
para Solaris 2.x, SunOs 4.1.x, Windows 95,
Windows NT,
Linux, Irix,
Aix, Mac, Apple y probablemente haya grupos de
desarrollo
trabajando en el porting a otras plataformas.
El código fuente Java se "compila" a un
código de bytes de alto nivel independiente de la
máquina. Este código (byte-codes) está
diseñado para ejecutarse en una máquina
hipotética que es implementada por un sistema run-time,
que sí es dependiente de la
máquina.
En una representación en que
tuviésemos que indicar todos los elementos que forman
parte de la arquitectura de
Java sobre una plataforma genérica, obtendríamos
una figura como la siguiente:
En ella podemos ver que lo verdaderamente
dependiente del sistema es la Máquina Virtual Java (JVM) y
las librerías fundamentales, que también nos
permitirían acceder directamente al hardware de la
máquina. Además, habrá APIs de Java que
también entren en contacto directo con el hardware y serán
dependientes de la máquina, como ejemplo de este tipo de
APIs podemos citar:
Java 2D: gráficos 2D y manipulación
de imágenes
Java Media Framework : Elementos críticos
en el tiempo: audio, video…
Java Animation: Animación de objetos en
2D
Java Telephony: Integración con
telefonía
Java Share: Interacción entre aplicaciones
multiusuario
Java 3D: Gráficos 3D y su
manipulación
Seguro
La seguridad en Java
tiene dos facetas. En el lenguaje,
características como los punteros o el casting
implícito que hacen los compiladores de C
y C++ se eliminan para prevenir el acceso ilegal a la memoria.
Cuando se usa Java para crear un navegador, se combinan las
características del lenguaje con protecciones de sentido
común aplicadas al propio navegador.
El lenguaje C,
por ejemplo, tiene lagunas de seguridad
importantes, como son los errores de alineación. Los
programadores de C utilizan punteros en conjunción con
operaciones
aritméticas. Esto le permite al programador que un puntero
referencie a un lugar conocido de la memoria y
pueda sumar (o restar) algún valor, para
referirse a otro lugar de la memoria. Si
otros programadores conocen nuestras estructuras de
datos pueden
extraer información confidencial de nuestro
sistema. Con un lenguaje como C, se pueden tomar números
enteros aleatorios y convertirlos en punteros para luego acceder
a la memoria:
printf( "Escribe un valor entero:
" );
scanf( "%u",&puntero );
printf( "Cadena de memoria: %sn",puntero
);
Otra laguna de seguridad u otro
tipo de ataque, es el Caballo de Troya. Se presenta un programa como una
utilidad,
resultando tener una funcionalidad destructiva. Por ejemplo, en
UNIX se visualiza
el contenido de un directorio con el comando ls. Si un
programador deja un comando destructivo bajo esta referencia, se
puede correr el riesgo de
ejecutar código malicioso, aunque el comando siga haciendo
la funcionalidad que se le supone, después de lanzar su
carga destructiva.
Por ejemplo, después de que el caballo de
Troya haya enviado por correo el /etc/shadow a su creador,
ejecuta la funcionalidad de ls persentando el contenido del
directorio. Se notará un retardo, pero nada
inusual.
El código Java pasa muchos tests antes de
ejecutarse en una máquina. El código se pasa a
través de un verificador de byte-codes que comprueba el
formato de los fragmentos de código y aplica un probador
de teoremas para detectar fragmentos de código ilegal
-código que falsea punteros, viola derechos de acceso sobre
objetos o intenta cambiar el tipo o clase de un
objeto-.
Si los byte-codes pasan la verificación sin
generar ningún mensaje de error, entonces sabemos
que:
- El código no produce desbordamiento de
operandos en la pila - El tipo de los parámetros de todos los
códigos de operación son conocidos y
correctos. - No ha ocurrido ninguna conversión ilegal
de datos, tal como
convertir enteros en punteros. - El acceso a los campos de un objeto se sabe que
es legal: public, private, protected. - No hay ningún intento de violar las
reglas de acceso y seguridad
establecidas
El Cargador de Clases también ayuda a Java
a mantener su seguridad, separando el espacio de nombres del
sistema de ficheros local, del de los recursos
procedentes de la red. Esto limita cualquier
aplicación del tipo Caballo de Troya, ya que las clases se
buscan primero entre las locales y luego entre las procedentes
del exterior.
Las clases importadas de la red se almacenan en un
espacio de nombres privado, asociado con el origen. Cuando una
clase del espacio de nombres privado accede a otra clase, primero
se busca en las clases predefinidas (del sistema local) y luego
en el espacio de nombres de la clase que hace la referencia. Esto
imposibilita que una clase suplante a una
predefinida.
En resumen, las aplicaciones de Java resultan
extremadamente seguras, ya que no acceden a zonas delicadas de
memoria o de sistema, con lo cual evitan la interacción de
ciertos virus. Java no
posee una semántica específica para modificar la
pila de programa, la
memoria libre o utilizar objetos y métodos de
un programa sin
los privilegios del kernel del sistema
operativo. Además, para evitar modificaciones por
parte de los crackers de la red, implementa un método
ultraseguro de autentificación por clave pública.
El Cargador de Clases puede verificar una firma digital antes de
realizar una instancia de un objeto. Por tanto, ningún
objeto se crea y almacena en memoria, sin que se validen los
privilegios de acceso. Es decir, la seguridad se integra en el
momento de compilación, con el nivel de detalle y de
privilegio que sea necesario.
Dada, pues la concepción del lenguaje y si
todos los elementos se mantienen dentro del estándar
marcado por Sun, no hay peligro. Java imposibilita,
también, abrir ningún fichero de la máquina
local (siempre que se realizan operaciones con
archivos,
éstas trabajan sobre el disco duro de
la máquina de donde partió el applet), no permite
ejecutar ninguna aplicación nativa de una plataforma e
impide
que se utilicen otros ordenadores como puente, es
decir, nadie puede utilizar nuestra máquina para hacer
peticiones o realizar operaciones con
otra. Además, los intérpretes que incorporan los
navegadores de
la Web son
aún más restrictivos. Bajo estas condiciones (y
dentro de la filosofía de que el único ordenador
seguro es el
que está apagado, desenchufado, dentro de una
cámara acorazada en un bunker y rodeado por mil soldados
de los cuerpos especiales del ejército), se puede
considerar que Java es un lenguaje seguro y que los
applets están libres de virus.
Respecto a la seguridad del código fuente,
no ya del lenguaje, JDK proporciona un desemsamblador de
byte-code, que permite que cualquier programa pueda
ser convertido a código fuente, lo que para el programador
significa una vulnerabilidad total a su código. Utilizando
javap no se obtiene el código fuente original, pero
sí desmonta el programa mostrando el algoritmo que
se utiliza, que es lo realmente interesante. La protección
de los programadores ante esto es utilizar llamadas a programas
nativos, externos (incluso en C o C++) de forma que no sea
descompilable todo el código; aunque así se pierda
portabilidad. Esta es otra de las cuestiones que Java tiene
pendientes.
Portable
Más allá de la portabilidad
básica por ser de arquitectura
independiente, Java implementa otros estándares de
portabilidad para facilitar el desarrollo. Los enteros son
siempre enteros y además, enteros de 32 bits en
complemento a 2. Además, Java construye sus interfaces de
usuario a través de un sistema abstracto de ventanas de
forma que las ventanas puedan ser implantadas en entornos
Unix, Pc o
Mac.
Interpretado
El intérprete Java (sistema run-time) puede
ejecutar directamente el código objeto. Enlazar (linkar)
un programa, normalmente, consume menos recursos que
compilarlo, por lo que los desarrolladores con Java
pasarán más tiempo desarrollando y menos esperando
por el ordenador. No obstante, el compilador actual del JDK es
bastante lento. Por ahora, que todavía no hay compiladores
específicos de Java para las diversas plataformas, Java es
más lento que otros lenguajes de
programación, como C++, ya que debe ser interpretado y
no ejecutado como sucede en cualquier programa
tradicional.
Se dice que Java es de 10 a 30 veces más
lento que C, y que tampoco existen en Java proyectos de gran
envergadura como en otros lenguajes. La verdad es que ya hay
comparaciones ventajosas entre Java y el resto de los lenguajes de
programación, y una ingente cantidad de folletos
electrónicos que supuran fanatismo en favor y en contra de
los distintos lenguajes contendientes con Java. Lo que se suele
dejar de lado en todo esto, es que primero habría que
decidir hasta que punto Java, un lenguaje en pleno desarrollo y
todavía sin definición definitiva, está
maduro como lenguaje de
programación para ser comparado con otros; como por
ejemplo con Smalltalk, que lleva más de 20 años en
cancha.
La verdad es que Java para conseguir ser un
lenguaje independiente del sistema operativo
y del procesador que
incorpore la máquina utilizada, es tanto interpretado como
compilado. Y esto no es ningún contrasentido, me explico,
el código fuente escrito con cualquier editor se compila
generando el byte-code. Este código intermedio es de muy
bajo nivel, pero sin alcanzar las instrucciones máquina
propias de cada plataforma y no tiene nada que ver con el p-code
de Visual Basic. El
byte-code corresponde al 80% de las instrucciones de la
aplicación. Ese mismo código es el que se puede
ejecutar sobre cualquier plataforma. Para ello hace falta el
run-time, que sí es completamente dependiente de la
máquina y del sistema
operativo, que interpreta dinámicamente el byte-code y
añade el 20% de instrucciones que faltaban para su
ejecución. Con este sistema es fácil crear
aplicaciones multiplataforma, pero para ejecutarlas es necesario
que exista el run-time correspondiente al sistema operativo
utilizado.
Multithreaded
Al ser multithreaded (multihilvanado, en mala
traducción), Java permite muchas actividades
simultáneas en un programa. Los threads (a veces llamados,
procesos
ligeros), son básicamente pequeños procesos o
piezas independientes de un gran proceso. Al
estar los threads contruidos en el lenguaje,
son más fáciles de usar y más robustos que
sus homólogos en C o C++.
El beneficio de ser miltithreaded consiste en un
mejor rendimiento interactivo y mejor comportamiento
en tiempo real. Aunque el comportamiento
en tiempo real está limitado a las capacidades del sistema
operativo subyacente (Unix, Windows,
etc.), aún supera a los entornos de flujo único de
programa (single-threaded) tanto en facilidad de desarrollo como
en rendimiento.
Cualquiera que haya utilizado la tecnología de
navegación concurrente, sabe lo frustrante que puede ser
esperar por una gran imagen que se
está trayendo. En Java, las imágenes
se pueden ir trayendo en un thread independiente, permitiendo que
el usuario pueda acceder a la información en la página sin tener
que esperar por el navegador.
Dinamico
Java se beneficia todo lo posible de la tecnología orientada
a objetos. Java no intenta conectar todos los módulos que
comprenden una aplicación hasta el tiempo de
ejecución. Las librería nuevas o actualizadas no
paralizarán las aplicaciones actuales (siempre que
mantengan el API anterior).
Java también simplifica el uso de protocolos nuevos
o actualizados. Si su sistema ejecuta una aplicación Java
sobre la red y encuentra una pieza de la aplicación que no
sabe manejar, tal como se ha explicado en párrafos
anteriores, Java es capaz de traer automáticamente
cualquiera de esas piezas que el sistema necesita para
funcionar.
Java, para evitar que los módulos de
byte-codes o los objetos o nuevas clases, haya que estar
trayéndolos de la red cada vez que se necesiten,
implementa las opciones de persistencia, para que no se eliminen
cuando de limpie la caché de la
máquina.
¿Cuál es la ventaja de todo
esto?¿Qué gano con Java?
- Primero: No debes volver a escribir el
código si quieres ejecutar el programa en otra
máquina. Un solo código funciona para todos los
browsers compatibles con Java o donde se tenga una
Máquina Virtual de Java (Mac's, PC's, Sun's,
etc). - Segundo: Java es un lenguaje de
programación orientado a objetos, y tiene todos los
beneficios que ofrece esta metodología de programacion (más
adelante doy una pequeña introducción a la
filosofía de objetos). - Tercero: Un browser compatible con Java
deberá ejecutar cualquier programa hecho en Java, esto
ahorra a los usuarios tener que estar insertando "plug-ins" y
demás programas que a
veces nos quitan tiempo y espacio en disco. - Cuarto: Java es un lenguaje y por lo tanto
puede hacer todas las cosas que puede hacer un lenguaje de
programación: Cálculos matemáticos,
procesadores de
palabras, bases de datos,
aplicaciones gráficas, animaciones, sonido, hojas
de cálculo,
etc. - Quinto: Si lo que me interesa son las
páginas de Web, ya no
tienen que ser estáticas, se le pueden poner toda clase
de elementos multimedia y
permiten un alto nivel de interactividad, sin tener que gastar
en paquetes carísimos de multimedia.
Todo esto suena muy bonito pero tambien se
tienen algunas limitantes:
- La velocidad.
- Los programas hechos en Java no tienden a ser
muy rápidos, supuestamente se está trabajando en
mejorar esto.Como los programas de Java son interpretados nunca
alcanzan la velocidad de
un verdadero ejecutable. - Java es un lenguaje de programación. Esta es otra gran
limitante, por más que digan que es orientado a objetos
y que es muy fácil de aprender sigue siendo un lenguaje
y por lo tanto aprenderlo no es cosa fácil.
Especialmente para los no programadores. - Java es nuevo. En pocas palabras todavía
no se conocen bien todas sus capacidades.
Pero en general Java posee muchas ventajas y se
pueden hacer cosas muy interesantes con esto. Hay que prestar
especial atención a lo que está sucediendo en el
mundo de la computación, a pesar de que Java es
relativamente nuevo, posee mucha fuerza y es
tema de moda en
cualquier medio computacional. Muchas personas apuestan a futuro
y piensan en Java. La pregunta es : ¿Estarán en lo
correcto? La verdad es que no se, pero este manual no es para
filosofar sobre el futuro del lenguaje sino para aprender a
programarlo.
1.3 HotJava
HotJava, en pocas palabras, es un navegador con
soporte Java (Java-enabled), desarrollado en Java. Como cualquier
navegador de Web, HotJava
puede decodificar HTML
estándar y URLs estándares, aunque no soporta
completamente el estándar HTML 3.0. La
ventaja sobre el resto de navegadores,
sin soporte Java, es que puede ejecutar programas Java sobre la
red. La diferencia con Netscape, es que tiene implementado
completamente los sistemas de
seguridad que propone Java, esto significa que puede escribir y
leer en el disco local, aunque esto hace disminuir la seguridad,
ya que se pueden grabar en nuestro disco programas que contengan
código malicioso e introducirnos un virus, por
ejemplo. No obstante, el utilizar esta característica de
HotJava es decisión del usuario.
1.4 Java para aplicaciones
corporativas
Java actualmente está en boca de todos,
Java e Intranet son
las palabras de moda. Pero,
surge la pregunta de si esta es una buena tecnología para
desarrollar aplicaciones corporativas. Y la respuesta es
afirmativa y voy a proponer argumentos para esa
afirmación. En donde la red sea algo crítico, Java
facilita tremendamente la vida de la programación corporativa.
Durante años, las grandes empresas se han
convencido de que la "red" corporativa es la arteria por donde
fluye la sangre que
mantiene vivo su negocio. Desde el gran servidor de sus
oficinas centrales, hasta los servidores de las
delegaciones, las estaciones de trabajo de los programadores y la
marabunta de PCs, la información va fluyendo de unos a otros.
Para muchas compañías, la Red es la
Empresa.
Si esta red no se mantiene sana, los pedidos no
llegan, el inventario no se
actualiza, el software no se desarrolla
adecuadamente, los clientes no
están satisfechos y, fundamentalmente, el dinero no
entra. La necesidad de diagnosticar y reducir la
arterioesclerosis de la red, hace que se estén inyectando
continuamente nuevas metodologías que subsanen este grave
problema.
¿Es Java la medicina?
Está claro que cuando vemos un cepillo animado limpiando
los dientes, cubos moviéndose en 3-D, o una banda de gatos
locos en applets de Java, nos convencemos de que es el lenguaje
idóneo para Internet. Pero, qué
pasa con las aplicaciones corporativas, ¿sería una
buena tecnología allí donde la red es el
punto crítico? Vamos a intentar responder comparando las
capacidades de Java contra la lista de necesidades de la red
corporativa.
Desarrollo rápido de
aplicaciones
Hace años, se decía que los
programadores pronto desaparecerían. Los generadores
automáticos de programas, eliminarían a los
generadores humanos y el mundo sería un lugar mejor para
vivir. Desafortunadamente, quienes decían esto no tuvieron
en cuenta una acelerada demanda de
software de
calidad para
muy diferentes aplicaciones. Sin embargo, la tecnología de
objetos pronto vino a intentar facilitar la tarea, adoptando el
modelo de
"generar parte de un programa", así, generando la parte
básica de un programa (los objetos), se podría
conectar con otras partes para proporcionar diferentes utilidades
al usuario.
El lenguaje C++
es una buena herramienta, pero no cumple totalmente la premisa.
Visual Basic y
NextStep, se acercan cada vez más al poder de los
objetos. Java facilita la creación de entornos de
desarrollo-aplicaciones de modo similar, pero además es
flexible, poderoso y efectivo. Los programadores ahora disponen
de herramientas
de programación de calidad beta, que
apuntan hacia esa meta, como son el Java WorkShop de SunSoft, el
entorno Java de Borland, el Café de Symantec, y pronto,
herramientas
más sofisticadas como Netcode o FutureTense. Esto
proporciona una gran progresión a los entornos de
desarrollo Java.
Aplicaciones efectivas y
eficientes
Las aplicaciones que se crean en grandes empresas deben
ser más efectivas que eficientes; es decir, conseguir que
el programa funcione y el trabajo
salga adelante es más importante que el que lo haga
eficientemente. Esto no es una crítica, es una realidad de
la programación corporativa. Al ser un
lenguaje más simple que cualquiera de los que ahora
están en el cajón de los programadores, Java
permite a éstos concentrarse en la mecánica de la aplicación, en vez de
pasarse horas y horas incorporando APIs para el control de las
ventanas, controlando minuciosamente la memoria, sincronizando
los ficheros de cabecera y corrigiendo los agónicos
mensajes del linker. Java tiene su propio toolkit para
interfaces, maneja por sí mismo la memoria que utilice la
aplicación, no permite ficheros de cabecera separados (en
aplicaciones puramente Java) y solamente usa enlace
dinámico.
Muchas de las implementaciones de Java actuales
son puros intérpretes. Los byte-codes son interpretados
por el sistema run-time de Java, la Máquina Virtual Java
(JVM), sobre el ordenador del usuario. Aunque ya hay ciertos
proveedores
que ofrecen compiladores
nativos Just-In-Time (JIT). Si la Máquina Virtual Java
dispone de un compilador instalado, las secciones (clases) del
byte-code de la aplicación se compilarán hacia la
arquitectura
nativa del ordenador del usuario.
Los programas Java en ese momento
rivalizarán con el rendimiento de programas en C++. Los
compiladores JIT
no se utilizan en la forma tradicional de un compilador; los
programadores no compilan y distribuyen binarios Java a los
usuarios. La compilación JIT tiene lugar a partir del
byte-code Java, en el sistema del usuario, como una parte
(opcional) del entorno run-time local de
Java.
Muchas veces, los programadores corporativos,
ansiosos por exprimir al máximo la eficiencia de su
aplicación, empiezan a hacerlo demasiado pronto en el
ciclo de vida
de la aplicación. Java permite algunas técnicas
innovadoras de optimización. Por ejemplo, Java es
inherentemente multithreaded, a la vez que ofrece posibilidades
de multithread como la clase Thread y mecanismos muy sencillos de
usar de sincronización; Java en sí utiliza threads.
Los desarrolladores de compiladores
inteligentes pueden utilizar esta característica de Java
para lanzar un thread que compruebe la forma en que se
está utilizando la aplicación. Más
específicamente, este thread podría detectar
qué métodos de
una clase se están usando con más frecuencia e
invocar a sucesivos niveles de optimización en tiempo de
ejecución de la aplicación. Cuanto más
tiempo esté corriendo la aplicación o el applet,
los métodos estarán cada vez más optimizados
(Guava de Softway es de este tipo).
Si un compilador JIT está embebido en el
entorno run-time de Java, el programador no se preocupa de hacer
que la aplicación se ejecute óptimamente. Siempre
he pensado que en los Sistemas
Operativos tendría que aplicarse esta
filosofía; un optimizador progresivo es un paso más
hacia esta idea.
Portabilidad para programador y
programa
En una empresa de
relativo tamaño hay una pléyade diferente de
ordenadores. Probablemente nos encontremos con estaciones de
trabajo Sun para el desarrollo de software, hordas de PCs para
cada empleado, algún Mac en el departamento de
documentación, una estación de trabajo HP en
administración y una estación SGI en
la sala de demos. Desarrollar aplicaciones corporativas para un
grupo tan
diferente de plataformas en excesivamente complejo y caro. Hasta
ahora era complicado convencer a los programadores de cada
arquitectura
que utilizasen un API común para reducir el coste de las
aplicaciones.
Con un entorno run-time de Java portado a cada una
de las arquitecturas de las plataformas presentes en la empresa y una
buena librería de clases ("packages" en Java), los
programadores pueden entenderse y encontrar muy interesante
trabajar con Java. Esta posibilidad hará tender a los
programadores hacia Java, justo donde otros intentos anteriores
con entornos universales (como Galaxy o XVT) han fracasado. Estos
APIs eran simplemente inadecuados, no orientados a redes y, verdaderamente,
pesados.
Una vez que los programas estén escritos en
Java, otro lado interesante del asunto es que los programadores
también son portables. El grupo de
programadores de la empresa puede
ahora enfrentarse a un desarrollo para cualquiera de las
plataformas. La parte del cliente y del
servidor de
una aplicación estarán ahora escritas en el mismo
lenguaje. Ya no será necesario tener un grupo que
desarrolle en Solaris en del departamento de I+D, programadores
trabajando sobre Visual Basic en
el departamento de documentación y programadores sobre GNU
en proyectos
especiales; ahora todos ellos podrán estar juntos y formar
el grupo de
software de la
empresa.
Costes de
desarrollo
En contraste con el alto coste de los desarrollos
realizados sobre estaciones de trabajo, el coste de
creación de una aplicación Java es similar al de
desarrollar sobre un PC.
Desarrollar utilizando un software caro para una
estación de trabajo (ahora barata) es un problema en
muchas empresas. La
eficiencia del
hardware y el
poco coste de mantenimiento
de una estación de trabajo Sun, por ejemplo, resulta muy
atractivo para las empresas; pero el
coste adicional del entorno de desarrollo con C++ es prohibitivo
para la gran mayoría de ellas. La llegada de Java e
Intranet
reducen considerablemente estos costes. Las herramientas
Java ya no están en el entorno de precios de
millones de pesetas, sino a los niveles confortables de precio de las
herramientas
de PCs. Y con el crecimiento cada día mayor de la comunidad de
desarrolladores de software freeware y shareware que incluso
proporcionan el código fuente, los programadores
corporativos tienen un amplio campo donde moverse y muchas
oportunidades de aprender y muchos recursos a su
disposición.
El éxito que Internet ha proporcionado a
los equipos de software corporativos es un regalo. El precio del
software es ahora el mismo para un poderoso equipo corriendo
Unix que para un
PC. Incluso Netscape tiene al mismo precio la
versión Unix de su servidor Web SuiteSpot que
la versión PC/NT. Esta es la filosofía de precios que
parece ser será la que se siga con las herramientas
basadas en Java.
Mantenimiento y
soporte
Un problema bien conocido que ocurre con el
software corporativo es la demanda de
cuidados y realimentación. Java no es, ciertamente, la
cura para la enfermedad del mantenimiento,
pero tiene varias características que harán la vida
del enfermero más fácil.
Uno de los componentes del JDK es javadoc. Si se
usan ciertas convenciones en el código fuente Java (como
comenzar un comentario con /** y terminarlo con */), javadoc se
puede fácilmente generar páginas HTML con el
contenido de esos comentarios, que pueden visualizarse en
cualquier navegador. La documentación del API de Java ha
sido creada de este modo. Esto hace que el trabajo de
documentar el código de nuevas clases Java sea
trivial.
Otro gran problema del desarrollador corporativo
es la creación y control de
makefiles. Leerse un makefile es como estar leyendo la historia de empresa.
Normalmente se pasan de programador a programador, quitando la
información que no es esencial, siempre que
se puede. Esto hace que muchos de los makefiles de las
aplicaciones contengan docenas de librerías, una
miríada de ficheros de cabecera y ultra-confusos macros. Es como
mirar en el estómago de la ballena de
Jonás.
Java reduce las dependencia de complejos makefiles
drásticamente. Primero, no hay ficheros de cabecera. Java
necesita que todo el código fuente de una clase se
encuentre en un solo fichero. Java tiene la inteligencia
de make en el propio lenguaje para simplificar la
compilación de byte-codes.
Por ejemplo:
public class pepe { // Fichero:
pepe.java
Guitarra flamenca ;
}
public class guitarra { // Fichero:
guitarra.java
}
% javac -verbose pepe.java
[parsed pepe.java in 720ms]
[loaded
C:JAVABIN..classesjavalangObject.class in
220ms]
[checking class pepe]
[parsed .\Guitarra.java in
50ms]
[wrote pepe.class]
[checking class Guitarra]
[wrote .\Guitarra.class]
[done in 2300ms]
El compilador Java se da cuenta de que necesita
compilar el fichero guitarra.java. Ahora vamos a forzarlo a que
recompile pepe.java sin cambiar guitarra.java, podremos comprobar
que el compilador de byte-code Java no recompila innecesariamente
el fichero guitarra.java.
% javac -verbose pepe.java
[parsed pepe.java in 440ms]
[loaded
C:JAVABIN..classesjavalangObject.class in
160ms]
[checking class pepe]
[loaded .\Guitarra.java in 0ms]
[wrote pepe.class]
[done in 1860ms]
Ahora, si modificamos guitarra.java
(añadiendo, por ejemplo, otro miembro a la clase) y
compilamos pepe.java, el compilador Java se dará cuenta de
que debe recompilar tanto pepe.java como
guitarra.java
% javac -verbose pepe.java
[parsed pepe.java in 710ms]
[loaded
C:JAVABIN..classesjavalangObject.class in
220ms]
[checking class pepe]
[parsed .\Guitarra.java in 0ms]
[wrote pepe.class]
[checking class Guitarra]
[wrote .\Guitarra.class]
[done in 2640ms]
En el libro Just
Java de Peter van der Linden hay un capítulo excelente
acerca del compilador de Java, si tienes oportunidad, no dejes de
leerlo.
Aprendizaje
Si la empresa
está llena de programadores de C++ con alguna experiencia
en el manejo de librería gráficas,
aprenderán rápidamente lo esencial de Java. Si el
equipo de ingenieros no conoce C++, pero maneja cualquier otro
lenguaje de programación
orientada a objetos, les llevará pocas semanas dominar
la base de Java. Lo que sí que no es cierto es que haya
que aprender C++ antes de aprender Java.
Si los ingenieros de la empresa no
conocen ningún lenguaje orientado a objetos, sí que
tienen que aprender los fundamentos de esta tecnología
antes de nada, y luego aplicarlos a la programación con
Java. El análisis y diseño
orientado a objetos debe ser comprendido antes de intentar nada
con Java. Los programadores de Java sin un fondo de conocimientos
de OOA/D producirán código pobre. Además,
los libros sobre
Java crecen como la espuma, ya hay más de 25 publicados, y
si buscas "Progamming in Java" en la Red, encontrarás 312
Web sites, y
30 más dedicados a "Learning Java". Y si esto,
evidentemente, no es el sustituto de un instructor humano, hay ya
varias empresas que
ofrecen enseñanza de Java, entre ellas,
Sun.
2. INSTALACIÓN DEL
JDK
Actualmente ya hay entornos de desarrollo
integrados completos para Java, diferentes del JDK de Sun.
Symantec dispone de un compilador de Java para Windows 95 y
Windows NT,
con las ventajas del aumento de velocidad de
proceso y
capacidades multimedia que
esto proporciona, Symantec Café. Borland también
está trabajando en ello y la nueva versión de su
entorno de desarrollo soporta Java. Sun ha lanzado la
versión comercial de su propio entorno de desarrollo para
Java, el Java Workshop, enteramente escrito en Java. Y Microsoft ha
puesto en el mercado Visual
J++, que sigue el estilo de todas sus herramientas de
desarrollo.
No obstante, trataremos solamente el JDK, que
hasta el momento es lo más conocido. El entorno
básico del JDK de Java que proporciona Sun está
formado por herramientas en modo texto, que
son: java, intérprete que ejecuta programas en byte-code.
javac, compilador de Java que convierte el código fuente
en byte-code. javah, crea ficheros de cabecera para implementar
métodos para cualquier clase. javap, es un descompilador
de byte-code a código fuente Java. javadoc, es un
generador automático de documentos
HTML a partir
del código fuente Java. javaprof, es un profiler para
aplicaciones de un solo thread. HotJava, es un navegador Web
escrito completamente en
Java.
El entorno habitual pues, consiste en un navegador
que pueda ejecutar applets, un compilador que convierta el
código fuente Java a byte-code y el intérprete Java
para ejecutar los programas. Estos son los componenetes
básicos para desarrollar algo en Java. No obstante se
necesita un editor para escribir el código fuente, y no
son estrictamente necesarias otras herramientas como el debugger,
un entorno visual, la documentación o un visualizador de
jerarquía de clases. Tan es así, que disponiendo
del navegador Netscape 2.0 no se necesita ni tan siquiera el JDK
(a petición de varios amigos que disfrutan del uso de
Linux pero no
disponen de soporte ELF para poder utilizar
el JDK portado por Randy Chapman, les indicaré como
conseguir utilizar el compilador embebido en
Netscape).
2.1 Windows
La versión del JDK para Windows es un
archivo
autoextraible. Se necesitan alrededor de 6 Mb de espacio libre en
disco. Ejecutar el fichero, que desempaquetará el
contenido del archivo. El
directorio donde se instale no es importante, pero supondremos
que se instala en el raiz del disco C:, en cuyo caso los archivos
colgarán de c:java. Es necesario añadir
c:javabin a la variable de entorno PATH.
Además de los ficheros java, el JDK incluye
dos librerías dinámicas, MSVCRT20.DLL y MFC30.DLL,
que se instalarán en el directorio de Java. Si tienes
ninguna copia de estos ficheros en tu ordenador (probablemente en
el directorio system de Windows) copia
estos ficheros en el directorio c:javabin. Si estos ficheros ya
están en tu ordenador, elimina las copias extra que
instala el JDK.
2.2 Solaris
La versión del JDK para Solaris es un
fichero tar comprimido. Se necesitan alrededor de 9 Mb de disco
para descomprimir el JDK, aunque el doble de espacio sería
una cifra más cómoda. Ejecutar
los siguientes comandos:
% uncompress
JDK-beta-solaris2-sparc.tar.Z
% tar xvf
JDK-beta-solaris2-sparc-tar
Puedes descomprimir el archivo en tu
directorio home, o, si tienes privilegios de supervisor, en
algún sitio más conveniente de /usr/local para que
todos los usuarios tengan acceso a los ficheros. Sin embargo, los
privilegios del supervisor no son necesarios para instalar y
ejecutar Java. Por simplicidad, supondré que has
descomprimido el JDK en /usr/local, aunque el path completo donde
se haga no tiene relevancia (también es posible colocarlo
en /opt que es donde residen todas las aplicaciones de Solaris).
Si lo has colocado en un sitio diferente, simplemente sustituye
/usr/local por ese directorio (si lo has descomprimido en tu
home, puedes utilizar ~/java y ~/hotjava, en vez del path
completo).
Es necesario añadir /usr/local/java/bin a
la variable de entorno PATH. Utiliza el siguiente comando
(suponiendo que tengas el shell csh o tcsh):
set path=($PATH
/usr/local/java/bin)
También puedes añadir esta
línea al final del fichero .profile y .cshrc, y ya tienes
el sistema listo para ejecutar applets. Si quieres desembarazarte
de la ventana que aparece cada vez que lances el appletviewer con
la licencia de Sun, crea un directorio que se llame .hotjava en
el directorio java/bin y ya no volverás a
verla.
2.3 Linux
Necesitas un kernel que soporte binarios ELF, por
lo tanto tu Linux debe ser la
versión 1.2.13 o superior, las anteriores tienen un bug
que hacen que javac no funcione. Necesitas también
Netscape, versión 2.0b4 o posterior. Sobre la
versión 1.2.13 del kernel de Linux, hay que
seguir los pasos que indico para conseguir que JDK
funcione:
- Bajarse el JDK, linux.jdk-1.0-try4.static-motif.tar.gz
y
linux.jdk-1.0 try1.common.tar.gz
a
/usr/local, descomprimirlo y hacer 'tar
xvf'
- En el fichero .java_wrapper (si no existe,
crearlo) cambiar las variable J_HOME y PRG, para que queden
como:
J_HOME=/usr/local/java
PRG=/usr/local/java/bin
- Bajarse la librería
libc.5.2.18.bin.tar.gz a /, descomprimirla, hacer 'tar
xvf'.
- Asegurarse de que /lib/libc.so.5 es un link
simbólico a este nuevo fichero.Si no lo es, hacer el
/lib 'ln -s libc.so.5.2.18 libc.so.5'
- Bajarse ld-so.1.7.14.tar.gz a un directorio
temporal, descomprimirlo y hacer 'tar xvf'.
- Ejecutar 'instldso.sh' y eliminar el directorio
temporal.
- Añadir /usr/local/java a la variable de
entorno PATH. Si se desea que esté fijada para todos
los usuarios, incorporar el directorio a la varible PATH que
se fija en el fichero /etc/profile.
- Bajarse
netscape-v202-export.i486-unknown-linux.tar.z a
usr/local/netscape, descomprimirlo y hacer 'tar
xvf'
- Crear un link en /usr/local/bin a
/usr/local/netscape/netscape
Esto debería ser suficiente para compilar
cualquier cosa en Java/Linux. En caso de tener problemas, es
el momento de recurrir a las FAQ.
Siguiendo los pasos indicados ya se puede ejecutar
el ejemplo del Tic-Tac-Toe que propone la hoja de
instalación que Sun ha incluido en todas sus versiones y
que en Linux consistiría en cambiarse al directorio de la
demo:
% cd
/usr/local/java/demo/TicTacToe
ejecutar el visualizador de applets sobre la
página html:
% appletviewer example1.html
y a jugar a las tres en raya. Por cierto, que el
algoritmo que
usa el ordenador está falseado por lo que es posible
ganarle.
2.4 Compilación sin
JDK
Parece raro, pero se puede conseguir. Lo
único necesario es el navegador Netscape 2.0. Este
navegador, junto con la máquina virtual Java (JVM) y el
sistema run-time, tiene un compilador Java.
Si no se dispone del Java Development Kit (JDK),
que no está disponible para todas las plataformas, pero
sí de la versión de Netscape para nuestra
plataforma, aquí van los pasos a seguir para utilizar el
compilador de Java embebido en Netscape.
Como necesito partir de algún punto para
tomarlo como referencia, voy a suponer que estamos sobre Linux y
que vamos a prescindir del JDK de Randy Chapman. Lo que
habría que hacer sería lo
siguiente.
- Primero. Instalar Netscape en el ordenador.
Asegurarse de entender perfectamente y leerse hasta el final el
fichero README, para seguir las instrucciones
específicas de la instalación de Netscape en la
plataforma y que Netscape funcione perfectamente. En nuestro
caso, en que vamos a intentar compilar código Java con
Netscape sobre Linux, la pieza clave es la situación del
fichero moz2_0.zip, que en mi máquina está en
/usr/local/netscape/java/classes. - Segundo. Extraer de una copia cualquiera del
JDK (aunque sea de otra plataforma), el fichero
java/lib/classes.zip y guardarlo en el mismo sitio que el
fichero moz2_0.zip; esta localización no es necesaria,
pero simplifica la estructura.
- Tercero. Fijar la variable de entorno CLASSPATH
para que Netscape pueda encontrar sus propias clases
además de las clases del Java de Sun. Asegurarse de
incluir el "directorio actual", para poder
compilar a la vez que se usan los ficheros .zip de Netscape y
Sun. Por ejemplo:
setenv CLASSPATH
.:/usr/local/netscape/java/classes/moz2_0.zip
:
/usr/local/netscape/java/classes/classes.zip
- Cuarto. Compilar el código Java (applet
o aplicación) con el comando: netscape -java
sun.tools.javac.Main [fichero].java (sustituir el nombre del
fichero con el código Java en vez de [fichero]). Esto
convertirá el código fuente Java en byte-code,
generándose el archivo
[fichero].class.
- Quinto. Comprobar si se puede ejecutar la
aplicación con el comando:
netscape -java [clase]
(sustituir el nombre de la clase de la
aplicación -la que contiene la rutina
main-
en vez de [clase]).
- Sexto. Si se ha compilado un applet Java,
construir una página html que lo utilice para
visualizarlo con el navegador en su forma normal. O
también se puede visualizar utilizando el appletviewer,
ejecutando:
netscape -java sun.applet.AppletViewer
[clase]
Desgraciadamente, la sentencia anterior no parece
funcionar en todos los
sistemas. Hay amigos míos que no han sido
capaces de visualizar applets con
este método.
Para aprovechar el tiempo, se puede crear un
script que recoja los pasos 3, 4 y 6. Si estamos utilizando el
csh, el contenido del script sería:
#/bin/csh -f setenv CLASSPATH
.:/usr/local/netscape/java/classes/moz2_0.zip:
/usr/local/netscape/java/classes/classes.zip
netscape -java sun.tools.javac.Main
$1
y lo almacenaríamos como javac. Se ha de
hacer el script ejecutable y cambiar /bin/csh por el path
completo donde esté situado el csh. De forma semejante
podemos definir el intérprete java y el appletviewer,
sustituyendo la línea adecuada de llamada a
Netscape.
3. CONCEPTOS BÁSICOS DE
JAVA
3.1 Programación en
Java
Cuando se programa en Java, se coloca todo el
código en métodos, de la misma forma que se
escriben funciones en
lenguajes como C.
Comentarios
En Java hay tres tipos de
comentarios:
// comentarios para una sola
línea
/* comentarios de una o
más líneas
*/
/** comentario de documentación, de una o
más líneas
*/
Los dos primeros tipos de comentarios son los que
todo programador conoce y se utilizan del mismo modo. Los
comentarios de documentación, colocados inmediatamente
antes de una declaración (de variable o función),
indican que ese comentario ha de ser colocado en la
documentación que se genera automáticamente cuando
se utiliza la herramienta de Java, javadoc. Dichos comentarios
sirven como descripción del elemento declarado permitiendo
generar una documentación de nuestras clases escrita al
mismo tiempo que se genera el código.
En este tipo de comentario para
documentación, se permite la introducción de
algunos tokens o palabras clave, que harán que la
información que les sigue aparezca de forma diferente al
resto en la documentación.
Identificadores
Los identificadores nombran variables,
funciones,
clases y objetos; cualquier cosa que el programador necesite
identificar o usar.
En Java, un identificador comienza con una letra,
un subrayado (_) o un símbolo de dólar ($). Los
siguientes caracteres pueden ser letras o dígitos. Se
distinguen las mayúsculas de las minúsculas y no
hay longitud máxima.
Serían identificadores
válidos:
identificador
nombre_usuario
Nombre_Usuario
_variable_del_sistema
$transaccion
y su uso sería, por
ejemplo:
int contador_principal;
char _lista_de_ficheros;
float $cantidad_en_Ptas;
Palabras clave
Las siguientes son las palabras clave que
están definidas en Java y que no se pueden utilizar como
indentificadores:
abstract continue for new
switch
boolean default goto null
synchronized
break do if package this
byte double implements private
threadsafe
byvalue else import protected
throw
case extends instanceof public
transient
catch false int return true
char final interface short
try
class finally long static
void
const float native super
while
Palabras Reservadas
Además, el lenguaje se reserva unas cuantas
palabras más, pero que hasta ahora no tienen un cometido
específico. Son:
cast future generic inner
operator outer rest var
Literales
Un valor
constante en Java se crea utilizando una representación
literal de él. Java utiliza cinco tipos de elementos:
enteros, reales en coma flotante, booleanos, caracteres y
cadenas, que se pueden poner en cualquier lugar del código
fuente de Java. Cada uno de estos literales tiene un tipo
correspondiente asociado con él.
Enteros:
byte 8 bits complemento a dos
short 16 bits complemento a dos
int 32 bits complemento a dos
long 64 bits complemento a dos
Por ejemplo: 21 077 0xDC00
Reales en coma flotante:
float 32 bits IEEE 754
double 64 bits IEEE 754
Por ejemplo: 3.14 2e12 3.1E12
Booleanos:
true
false
Caracteres:
Por ejemplo: a t u???? [????] es un
número unicode
Cadenas:
Por ejemplo: "Esto es una cadena
literal"
Arrays
Se pueden declarar en Java arrays de cualquier
tipo:
char s[];
int iArray[];
Incluso se pueden construir arrays de
arrays:
int tabla[][] = new int[4][5];
Los límites de los arrays se comprueban en
tiempo de ejecución para evitar desbordamientos y la
corrupción
de memoria.
En Java un array es realmente un objeto, porque
tiene redefinido el operador []. Tiene una función
miembro: length. Se puede utilizar este método
para conocer la longitud de cualquier array.
int a[][] = new int[10][3];
a.length; /* 10 */
a[0].length; /* 3 */
Para crear un array en Java hay dos métodos
básicos. Crear un array vacío:
int lista[] = new int[50];
o se puede crear ya el array con sus valores
iniciales:
String nombres[] = {
"Juan","Pepe","Pedro","Maria"
};
Esto que es equivalente a:
String nombres[];
nombres = new String[4];
nombres[0] = new String( "Juan"
);
nombres[1] = new String( "Pepe"
);
nombres[2] = new String( "Pedro"
);
nombres[3] = new String( "Maria"
);
No se pueden crear arrays estáticos en
tiempo de compilación:
int lista[50]; // generará un error en
tiempo de compilación
Tampoco se puede rellenar un array sin declarar el
tamaño con el operador new:
int lista[];
for( int i=0; i < 9; i++ )
lista[i] = i;
Es decir, todos los arrays en Java son
estáticos. Para convertir un array en el equivalente a un
array dinámico en C/C++, se usa la clase vector, que
permite operaciones de
inserción, borrado, etc. en el array.
Operadores
Los operadores de Java son muy parecidos en estilo
y funcionamiento a los de C. En la siguiente tabla aparecen los
operadores que se utilizan en Java, por orden de
precedencia:
. [] ()
++ —
! ~
instanceof
* / %
+ –
<< >> >>>
< > <= >= == !=
& ^ |
&& ||
? :
= op= (*= /= %= += -= etc.) ,
Los operadores numéricos se comportan como
esperamos:
int + int = int
Los operadores relacionales devuelven un valor
booleano.
Para las cadenas, se pueden utilizar los
operadores relacionales para comparaciones además de + y
+= para la concatenación:
String nombre = "nombre" +
"Apellido";
El operador = siempre hace copias de objetos,
marcando los antiguos para borrarlos, y ya se encargará el
garbage collector de devolver al sistema la memoria ocupada por
el objeto eliminado.
Separadores
Sólo hay un par de secuencias con otros
caracteres que pueden aparecer en el código Java; son los
separadores simples, que van a definir la forma y función
del código. Los separadores admitidos en Java
son:
() – paréntesis. Para contener listas de
parámetros en la definición y llamada a
métodos. También se utiliza para definir
precedencia en expresiones, contener expresiones para control de flujo
y rodear las conversiones de tipo.
{} – llaves. Para contener los valores de
matrices
inicializadas automáticamente. También se utiliza
para definir un bloque de código, para clases,
métodos y ámbitos locales.
[ ] – corchetes. Para declarar tipos matriz.
También se utiliza cuando se referencian valores de
matriz.
; – punto y coma. Separa
sentencias.
, – coma. Separa identificadores consecutivos en
una declaración de variables.
También se utiliza para encadenar sentencias dentro de una
sentencia for.
. – punto. Para separar nombres de paquete de
subpaquetes y clases. También se utiliza para separar una
variable o método de
una variable de referencia.
3.2 Control de Flujo
Muchas de las sentencias de control del flujo del
programa se han tomado del C:
Sentencias de
Salto
if/else
if( Boolean ) {
sentencias;
}
else {
sentencias;
}
switch
switch( expr1 ) {
case expr2:
sentencias;
break;
case expr3:
sentencias;
break;
default:
sentencias;
break;
}
Sentencias de
Bucle
Bucles for
for( expr1 inicio; expr2 test; expr3
incremento ) {
sentencias;
}
El siguiente trocito de código Java que
dibuja varias líneas en pantalla alternando sus colores entre
rojo, azul y verde. Este fragmento sería parte de una
función Java (método):
int contador;
for( contador=1; contador <= 12; contador++ )
{
switch( contador % 3 ) {
case 0:
setColor( Color.red
);
break;
case 1:
setColor( Color.blue
);
break;
case 2:
setColor( Color.green
);
break;
}
g.drawLine( 10,contador*10,80,contador*10
);
}
También se soporta el operador coma (,) en
los bucles for
for( a=0,b=0; a < 7; a++,b+=2
)
Bucles while
while( Boolean ) {
sentencias;
}
Bucles do/while
do {
sentencias;
}while( Boolean );
Excepciones
try-catch-throw
try {
sentencias;
} catch( Exception ) {
sentencias;
}
Java implementa excepciones para facilitar la
construcción de código robusto.
Cuando ocurre un error en un programa, el código que
encuentra el error lanza una excepción, que se puede
capturar y recuperarse de ella. Java proporciona muchas
excepciones predefinidas.
Control General del
Flujo
break [etiqueta]
continue [etiqueta]
return expr;
etiqueta: sentencia;
En caso de que nos encontremos con bucles
anidados, se permite el uso de etiquetas para poder salirse
de ellos, por ejemplo:
uno: for( )
{
dos: for( )
{
continue; // seguiría en el bucle
interno
continue uno; // seguiría en el bucle
principal
break uno; // se saldría del bucle
principal
}
}
En el código de una función siempre
hay que ser consecuentes con la declaración que se haya
hecho de ella. Por ejemplo, si se declara una función para
que devuelva un entero, es imprescindible que se coloque un
return final para salir de esa función, independientemente
de que haya otros en medio del código que también
provoquen la salida de la función. En caso de no hacerlo
se generará un Warning, y el código Java no se
puede compilar con Warnings.
int func()
{
if( a == 0 )
return 1;
return 0; // es imprescindible porque se retorna
un entero
}
3.3 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. Todos los métodos se definen dentro del bloque de
la clase, Java no soporta funciones o
variables
globales. Esto puede despistar a los programadores de C++, que
pueden definir métodos fuera del bloque de la clase, pero
esta posibilidad es más un intento de no separarse mucho y
ser compatible con C, que un buen diseño
orientado a objetos. 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.
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:
abstract
Una clase abstract tiene al menos un método
abstracto. Una clase abstracta no se instancia, sino que se
utiliza como clase base para la herencia.
final
Una clase final se declara como la clase que
termina una cadena de herencia. No se
puede heredar de una clase final. Por ejemplo, la clase Math es
una clase final.
public
Las clases public son accesibles desde otras
clases, bien sea directamente o por herencia. Son
accesibles dentro del mismo paquete en el que se han declarado.
Para acceder desde otros paquetes, primero tienen que ser
importadas.
synchronizable
Este modificador especifica que todos los
métodos definidos en la clase son sincronizados, es decir,
que no se puede acceder al mismo tiempo a ellos desde distintos
threads; el sistema se encarga de colocar los flags necesarios
para evitarlo. Este mecanismo hace que desde threads diferentes
se puedan modificar las mismas variables sin
que haya problemas de
que se sobreescriban.
3.4 Variables y Métodos de
Instancia
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 MiClase {
int i;
public MiClase() {
i = 10;
}
public void Suma_a_i( int j ) {
i = 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 ).
Ambito de una
variable
Los bloques de sentencias compuestas en Java se
delimitan con dos llaves. Las variables de Java sólo son
válidas desde el punto donde están declaradas hasta
el final de la sentencia compuesta que la engloba. Se pueden
anidar estas sentencias compuestas, y cada una puede contener su
propio conjunto de declaraciones de variables locales. Sin
embargo, no se puede declarar una variable con el mismo nombre
que una de ámbito exterior.
El siguiente ejemplo intenta declarar dos
variables separadas con el mismo nombre. En C y C++ son
distintas, porque están declaradas dentro de
ámbitos diferentes. En Java, esto es
ilegal.
Class Ambito {
int i = 1; // ámbito
exterior
{ // crea un nuevo ámbito
int i = 2; // error de
compilación
}
}
Métodos y
Constructores
Los métodos son funciones que pueden ser
llamadas dentro de la clase o por otras clases. El constructor es
un tipo específico de método que siempre tiene el
mismo nombre que la clase.
Cuando se declara una clase en Java, se pueden
declarar uno o más constructores opcionales que realizan
la inicialización cuando se instancia (se crea una
ocurrencia) un objeto de dicha clase.
Utilizando el código de ejemplo anterior,
cuando se crea una nueva instancia de MiClase, se crean
(instancian) todos los métodos y variables, y se llama al
constructor de la clase:
MiClase mc;
mc = new MiClase();
La palabra clave new se usa para crear una
instancia de la clase. Antes de ser instanciada con new no
consume memoria, simplemente es una declaración de tipo.
Después de ser instanciado un nuevo objeto mc, el valor de i en
el objeto mc será igual a 10. Se puede referenciar la
variable (de instancia) i con el nombre del
objeto:
mc.i++; // incrementa la instancia de i de
mc
Al tener mc todas las variables y métodos
de MiClase, se puede usar la primera sintaxis para llamar al
método Suma_a_i() utilizando el nuevo nombre de clase
mc:
mc.Suma_a_i( 10 );
y ahora la variable mc.i vale
21.
Finalizadores
Java no utiliza destructores (al contrario que
C++) 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();
}
3.5 Alcance de Objetos y Reciclado de
Memoria
Los objetos tienen un tiempo de vida y consumen
recursos durante
el mismo. Cuando un objeto no se va a utilizar más,
debería liberar el espacio que ocupaba en la memoria de
forma que las aplicaciones no la agoten (especialmente las
grandes).
En Java, la recolección y liberación
de memoria es responsabilidad de un thread llamado automatic
garbage collector (recolector automático de basura). Este
thread monitoriza el alcance de los objetos y marca los objetos
que se han salido de alcance. Veamos un
ejemplo:
String s; // no se ha asignado
todavia
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.
3.6 Herencia
La Herencia es el
mecanismo por el que se crean nuevos objetos definidos en
términos de objetos ya existentes. Por ejemplo, si se
tiene la clase Ave, se puede crear la subclase Pato, que es una
especialización de Ave.
class Pato extends Ave {
int numero_de_patas;
}
La palabra clave extends se usa para generar una
subclase (especialización) de un objeto. Una Pato es una
subclase de Ave. Cualquier cosa que contenga la definición
de Ave será copiada a la clase Pato, además, en
Pato se pueden definir sus propios métodos y variables de
instancia. Se dice que Pato deriva o hereda de
Ave.
Además, se pueden sustituir los
métodos proporcionados por la clase base. 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 );
En Java no se puede hacer herencia
múltiple. Por ejemplo, de la clase aparato con motor y de la
clase animal no se puede derivar nada, sería como obtener
el objeto toro mecánico a partir de una máquina
motorizada (aparato con motor) y un toro
(aminal). En realidad, lo que se pretende es copiar los
métodos, es decir, pasar la funcionalidad del toro de
verdad al toro mecánico, con lo cual no sería
necesaria la herencia múltiple sino simplemente la
compartición de funcionalidad que se encuentra
implementada en Java a través de
interfaces.
3.7 Control de
Acceso
Cuando se crea una nueva clase en Java, se puede
especificar el nivel de acceso que se quiere para las variables
de instancia y los métodos definidos en la
clase:
public
public void
CualquieraPuedeAcceder(){}
Cualquier clase desde cualquier lugar puede
acceder a las variables y métodos de instacia
públicos.
protected
protected void SoloSubClases(){}
Sólo las subclases de la clase y nadie
más puede acceder a las variables y métodos de
instancia protegidos.
private
private String
NumeroDelCarnetDeIdentidad;
Las variables y métodos de instancia
privados sólo pueden ser accedidos desde dentro de la
clase. No son accesibles desde las subclases.
friendly (sin declaración
específica)
void MetodoDeMiPaquete(){}
Por defecto, si no se especifica el control de
acceso, las variables y métodos de instancia se declaran
friendly (amigas), lo que significa que son accesibles por todos
los objetos dentro del mismo paquete, pero no por los externos al
paquete. Es lo mismo que protected.
Los métodos protegidos (protected) pueden
ser vistos por las clases derivadas, como
en C++, y también, en Java, por los paquetes (packages).
Todas las clases de un paquete pueden ver los métodos
protegidos de ese paquete. Para evitarlo, se deben declarar como
private protected, lo que hace que ya funcione como en C++ en
donde sólo se puede acceder a las variables y
métodos protegidos de las clases derivadas.
3.8 Variables y Métodos
Estáticos
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. Se usará
para ello 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.
De la misma forma se puede declarar un
método como estático, lo que evita que el
método pueda acceder a las variables de instancia no
estáticas:
class Documento extends Pagina {
static int version = 10;
int numero_de_capitulos;
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. Las funciones estáticas se usan
para acceder solamente a variables
estáticas.
class UnaClase
{
int var;
UnaClase()
{
var = 5;
}
UnaFuncion()
{
var += 5;
}
}
En el código anterior, si se llama a la
función UnaFuncion a través de un puntero a
función, no se podría acceder a var, porque al
utilizar un puntero a función no se pasa
implícitamente el puntero al propio objeto (this). Sin
embargo, sí se podría acceder a var si fuese
estática, porque siempre estaría en
la misma posición de memoria para todos los objetos que se
creasen de UnaClase.
3.9 this Y super
Al acceder a variables de instancia de una clase,
la palabra clave this hace referencia a los miembros de la propia
clase. Volviendo al ejemplo de MiClase, se puede añadir
otro constructor de la forma siguiente:
public class MiClase {
int i;
public MiClase() {
i = 10;
}
// Este constructor establece el valor de
i
public MiClase( int valor ) {
this.i = valor; // i = valor
}
public void Suma_a_i( int j ) {
i = i + j;
}
}
Aquí this.i se refiere al entero i en la
clase MiClase.
Si se necesita llamar al método padre
dentro de una clase que ha reemplazado ese método, se
puede hacer referencia al método padre con la palabra
clave super:
import MiClase;
public class MiNuevaClase extends MiClase
{
public void Suma_a_i( int j ) {
i = i + ( j/2 );
super.Suma_a_i( j );
}
}
En el siguiente código, el constructor
establecerá el valor de i a 10, después lo
cambiará a 15 y finalmente el método Suma_a_i() de
la clase padre (MiClase) lo dejará en
25:
MiNuevaClase mnc;
mnc = new MiNuevaClase();
mnc.Suma_a_i( 10 );
3.10 Clases Abstractas
Una de las características más
útiles de cualquier lenguaje orientado a objetos es la
posibilidad de declarar clases que definen como se utiliza
solamente, sin tener que implementar métodos. Esto es muy
útil cuando la implementación es específica
para cada usuario, pero todos los usuarios tienen que utilizar
los mismos métodos. Un ejemplo de clase abstracta en Java
es la clase Graphics:
public abstract class Graphics {
public abstract void drawLine( int x1,int y1,int
x2,
int y2 );
public abstract void drawOval( int x,int y,int
width,
int height );
public abstract void drawArc( int x,int y,int
width,
int height,int startAngle,int arcAngle
);
. . .
}
Los métodos se declaran en la clase
Graphics, pero el código que ejecutará el
método está en algún otro
sitio:
public class MiClase extends Graphics
{
public void drawLine( int x1,int y1,int x2,int y2
) {
<código para pintar líneas
-específico de
la arquitectura->
}
}
Cuando una clase contiene un método
abstracto tiene que declararse abstracta. No obstante, no todos
los métodos de una clase abstracta tienen que ser
abstractos. Las clases abstractas no pueden tener métodos
privados (no se podrían implementar) ni tampoco
estáticos. Una clase abstracta tiene que derivarse
obligatoriamente, no se puede hacer un new de una clase
abstracta.
Una clase abstracta en Java es lo mismo que en C++
virtual func() = 0; lo que obliga a que al derivar de la clase
haya que implementar forzosamente los métodos de esa clase
abstracta.
3.11 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.
Un interface 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 interface proporciona un mecanismo de
encapsulación de los protocolos de los
métodos sin forzar al usuario a utilizar la
herencia.
Por ejemplo:
public interface VideoClip {
// comienza la reproduccion
del video
void play();
// reproduce el clip en un bucle
void bucle();
// detiene la reproduccion
void stop();
}
Las clases que quieran utilizar el interface
VideoClip utilizarán la palabra implements y
proporcionarán el código necesario para implementar
los métodos que se han definido para el
interface:
class MiClase implements VideoClip
{
void play() {
<código>
}
void bucle() {
<código>
}
void stop() {
<código>
}
Al utilizar implements para el interface es como
si se hiciese una acción de copiar-y-pegar del
código del interface, con lo cual no se hereda nada,
solamente se pueden usar los métodos.
La ventaja principal del uso de interfaces es que
una clase interface puede ser implementada por cualquier
número de clases, permitiendo a cada clase compartir el
interfaz de programación sin tener que ser consciente de
la implementación que hagan las otras clases que
implementen el interface.
class MiOtraClase implements VideoClip
{
void play() {
<código nuevo>
}
void bucle() {
<código nuevo>
}
void stop() {
<código nuevo>
}
3.12 Métodos
Nativos
Java proporciona un mecanismo para la llamada a
funciones C y C++ desde nuestro código fuente Java. Para
definir métodos como funciones C o C++ se utiliza la
palabra clave native.
public class Fecha {
int ahora;
public Fecha() {
ahora = time();
}
private native int time();
static {
System.loadLibrary( "time" );
}
}
Una vez escrito el código Java, se
necesitan ejecutar los pasos siguientes para poder integrar el
código C o C++:
- Utilizar javah para crear un fichero de
cabecera (.h) - Utilizar javah para crear un fichero de stubs,
es decir, que contiene la declaración de las
funciones - Escribir el código del método
nativo en C o C++, es decir, rellenar el código de la
función, completando el trabajo
de javah al crear el fichero de stubs - Compilar el fichero de stubs y el fichero .c en
una librería de carga dinámica (DLL en Windows '95
o libXX.so en Unix) - Ejecutar la aplicación con el
appletviewer
Más adelante trataremos en profundidad los
métodos nativos, porque añaden una gran potencia a Java,
al permitirle integrar a través de librería
dinámica cualquier algoritmo
desarrollado en C o C++, lo cual, entre otras cosas, se utiliza
como método de protección contra la
descompilación completa del código
Java.
3.13 Paquetes
La palabra clave package permite agrupar clases e
interfaces. Los nombres de los paquetes son palabras separadas
por puntos y se almacenan en directorios que coinciden con esos
nombres. Por ejemplo, los ficheros siguientes, que contienen
código fuente Java:
Applet.java, AppletContext.java, AppletStub.java,
AudioClip.java
contienen en su código la
línea:
package java.applet;
Y las clases que se obtienen de la
compilación de los ficheros anteriores, se encuentran con
el nombre nombre_de_clase.class, en el
directorio:
java/applet
Import
Los paquetes de clases se cargan con la palabra
clave import, especificando el nombre del paquete como ruta y
nombre de clase (es lo mismo que #include de C/C++). Se pueden
cargar varias clases utilizando un asterisco.
import java.Date;
import java.awt.*;
Si un fichero fuente Java no contiene
ningún package, se coloca en el paquete por defecto sin
nombre. Es decir, en el mismo directorio que el fichero fuente, y
la clase puede ser cargada con la sentencia
import:
import MiClase;
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, los paquetes Java que se incluyen son:
java.applet
Este paquete contiene clases diseñadas para
usar con applets. Hay una clase Applet y tres interfaces:
AppletContext, AppletStub y AudioClip.
java.awt
El paquete Abstract Windowing Toolkit (awt)
contiene clases para generar widgets y componentes GUI (Interfaz
Gráfico de Usuario). Incluye las clases Button, Checkbox,
Choice, Component, Graphics, Menu, Panel, TextArea y
TextField.
java.io
El paquete de entrada/salida contiene las clases
de acceso a ficheros: FileInputStream y
FileOutputStream.
java.lang
Este paquete incluye las clases del lenguaje Java
propiamente dicho: Object, Thread, Exception, System, Integer,
Float, Math, String, etc.
java.net
Este paquete da soporte a las conexiones del
protocolo
TCP/IP y,
además, incluye las clases Socket, URL y
URLConnection.
java.util
Este paquete es una miscelánea de clases
útiles para muchas cosas en programación. Se
incluyen, entre otras, Date (fecha), Dictionary (diccionario),
Random (números aleatorios) y Stack (pila
FIFO).
3.14 Referencias
Java se asemeja mucho a C y C++. Esta similitud,
evidentemente intencionada, es la mejor herramienta para los
programadores, ya que facilita en gran manera su
transición a Java. Desafortunadamente, tantas similitudes
hacen que no nos paremos en algunas diferencias que son vitales.
La terminología utilizada en estos lenguajes, a veces es
la misma, pero hay grandes diferencias subyacentes en su
significado.
C tiene tipos de datos
básicos y punteros. C++ modifica un poco este panorama y
le añade los tipos referencia. Java también
especifica sus tipos primitivos, elimina cualquier tipo de
punteros y tiene tipos referencia mucho más
claros.
Todo este maremágnum de terminología
provoca cierta consternación, así que vamos a
intentar aclarar lo que realmente significa.
Conocemos 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.
4. PROGRAMAS BÁSICOS EN
JAVA
4.1 Una mínima aplicación en
java
La aplicación más pequeña
posible es la que simplemente imprimir un mensaje en la pantalla.
Tradicionalmente, el mensaje suele ser "Hola Mundo!". Esto es
justamente lo que hace el siguiente fragmento de
código:
//
// Aplicación HolaMundo de
ejemplo
//
class HolaMundoApp {
public static void main( String args[] )
{
System.out.println( "Hola Mundo!" )
;
}
}
HolaMundo
Vamos ver en detalle la aplicación
anterior, línea a línea. Esas líneas de
código contienen los componenetes mínimos para
imprimir Hola Mundo! en la pantalla.
//
// Aplicación HolaMundo de
ejemplo
//
Estas tres primera líneas son comentarios.
Hay tres tipos de comentarios en Java, // es un comentario
orientado a línea.
class HolaMundoApp {
Esta línea declara la clase HolaMundoApp.
El nombre de la clase especificado en el fichero fuente se
utiliza para crear un fichero nombredeclase.class en el
directorio en el que se compila la aplicación. En nuestro
caso, el compilador creará un fichero llamado
HolaMundoApp.class.
public static void main( String args[] )
{
Esta línea especifica un método que
el intérprete Java busca para ejecutar en primer lugar.
Igual que en otros lenguajes, Java utiliza una palabra clave main
para especificar la primera función a ejecutar. En este
ejemplo tan simple no se pasan argumentos.
public significa que el método main puede
ser llamado por cualquiera, incluyendo el intérprete
Java.
static es una palabra clave que le dice al
compilador que main se refiere a la propia clase HolaMundoApp y
no a ninguna instancia de la clase. De esta forma, si alguien
intenta hacer otra instancia de la clase, el método main
no se instanciaría.
void indica que main no devuelve nada. Esto es
importante ya que Java realiza una estricta comprobación
de tipos, incluyendo los tipos que se ha declarado que devuelven
los métodos.
args[] es la declaración de un array de
Strings. Estos son los argumentos escritos tras el nombre de la
clase en la línea de comandos:
%java HolaMundoApp arg1 arg2 …
System.out.println( "Hola Mundo!"
);
Esta es la funcionalidad de la aplicación.
Esta línea muestra el uso de
un nombre de clase y método. Se usa el método
println() de la clase out que está en el paquete
System.
El método println() toma una cadena como
argumento y la escribe en el stream de salida estándar; en
este caso, la ventana donde se lanza la
aplicación.
}
}
Finalmente, se cierran las llaves que limitan el
método main() y la clase HolaMundoApp.
Compilacion y Ejecucion de
HolaMundo
Vamos a ver a continuación como podemos ver
el resultado de nuestra primera aplicación Java en
pantalla. Generaremos un fichero con el código fuente de
la aplicación, lo compilaremos y utilizaremos el
intérprete java para ejecutarlo.
Ficheros Fuente Java
Los ficheros fuente en Java terminan con la
extensión ".java". Crear un fichero utilizando cualquier
editor de texto ascii que tenga
como contenido el código de las ocho líneas de
nuestra mínima aplicación, y salvarlo en un fichero
con el nombre de HolaMundoApp.java. Para crear los ficheros con
código fuente Java no es necesario un procesador de
textos, aunque puede utilizarse siempre que tenga salida a
fichero de texto plano o
ascii, sino que
es suficiente con cualquier otro editor.
Compilación
El compilador javac se encuentra en el directorio
bin por debajo del directorio java, donde se haya instalado el
JDK. Este directorio bin, si se han seguido las instrucciones de
instalación, debería formar parte de la variable de
entorno PATH del sistema. Si no es así, tendría que
revisar la Instalación del JDK. El compilador de Java
traslada el código fuente Java a byte-codes, que son los
componentes que entiende la Máquina Virtual Java que
está incluida en los navegadores
con soporte Java y en appletviewer.
Una vez creado el fichero fuente
HolaMundoApp.java, se puede compilar con la línea
siguiente:
%javac HolaMundoApp.java
Si no se han cometido errores al teclear ni se han
tenido problemas con
el path al fichero fuente ni al compilador, no debería
aparecer mensaje alguno en la pantalla, y cuando vuelva a
aparecer el prompt del sistema, se debería ver un fichero
HolaMundoApp.class nuevo en el directorio donde se encuentra el
fichero fuente.
Si ha habido algún problema, en Problemas
de compilación al final de esta sección, hemos
intentado reproducir los que más frecuentemente se suelen
dar, se pueden consultar por si pueden aportar un poco de
luz al error
que haya aparecido.
Ejecución
Para ejecutar la aplicación HolaMundoApp,
hemos de recurrir al intérprete java, que también
se encuentra en el directorio bin, bajo el directorio java. Se
ejecutará la aplicación con la
línea:
%java HolaMundoApp
y debería aparecer en pantalla la respuesta
de Java:
%Hola Mundo!
El símbolo % representa al prompt del
sistema, y lo utilizaremos para presentar las respuestas que nos
ofrezca el sistema como resultado de la ejecución de los
comandos que
se indiquen en pantalla o para indicar las líneas de
comandos a
introducir.
Problemas de
compilación
A continuación presentamos una lista de los
errores más frecuentes que se presentan a la hora de
compilar un fichero con código fuente Java, nos basaremos
en errores provocados sobre nuestra mínima
aplicación Java de la sección anterior, pero
podría generalizarse sin demasiados
problemas.
%javac: Command not found
No se ha establecido correctamente la variable
PATH del sistema para el compilador javac. El compilador javac se
encuentra en el directorio bin, que cuelga del directorio java,
que cuelga del directorio donde se haya instalado el JDK (Java
Development Kit).
%HolaMundoApp.java:3: Method
printl(java.lang.String) not found in class
java.io.PrintStream.
System.out.printl( "HolaMundo!);
^
- Error tipográfico, el método es
println no printl.
%In class HolaMundoApp: main must be public and
static
- Error de ejecución, se olvidó
colocar la palabra static en la declaración del
método main de la aplicación.
%Can´t find class
HolaMundoApp
Este es un error muy sutil. Generalmente significa
que el nombre de la clase es distinto al del fichero que contiene
el código fuente, con lo cual el fichero
nombre_fichero.class que se genera es diferente del que
cabría esperar. Por ejemplo, si en nuestro fichero de
código fuente de nuestra aplicación
HolaMundoApp.java colocamos en vez de la declaración
actual de la clase HolaMundoApp, la
línea:
class HolaMundoapp {
se creará un fichero HolaMundoapp.class,
que es diferente del HolaMundoApp.class, que es el nombre
esperado de la clase; la diferencia se encuentra en la a
minúscula y mayúscula.
4.2 El Visor de applets de Sun
(appletviewer)
El visualizador de applets (appletviewer) es una
aplicación que permite ver en funcionamiento applets, sin
necesidad de la utilización de un navegador World-Wide-Web
como HotJava, Microsoft
Explorer o Nescape. En adelante, recurriremos muchas veces a
él, ya que el objetivo del
tutorial es el lenguaje Java.
Applet
La definición más extendida de
applet, muy bien resumida por Patrick Naughton, indica que un
applet es "una pequeña aplicación accesible en un
servidor
Internet, que se transporta por la red, se instala
automáticamente y se ejecuta in situ como parte de un
documento web". Claro que así la definición
establece el entorno (Internet, Web, etc.). En realidad, un
applet es una aplicación pretendidamente corta (nada
impide que ocupe más de un gigabyte, a no ser el pensamiento de
que se va a transportar por la red y una mente sensata) basada en
un formato gráfico sin representación
independiente: es decir, se trata de un elemento a embeber en
otras aplicaciones; es un componente en su sentido
estricto.
Un ejemplo en otro ámbito de cosas
podría ser el siguiente: Imaginemos una empresa, que
cansada de empezar siempre a codificar desde cero, diseña
un formulario con los datos básicos de una persona (nombre,
dirección, etc.). Tal formulario no es un
diálogo por sí mismo, pero se podría
integrar en diálogos de clientes,
proveedores,
empleados, etc.
El hecho de que se integre estática
(embebido en un ejecutable) o dinámicamente
(intérpretes, DLLs, etc.) no afecta en absoluto a la
esencia de su comportamiento
como componente con que construir diálogos con sentido
autónomo.
Pues bien, así es un applet. Lo que ocurre
es que, dado que no existe una base adecuada para soportar
aplicaciones industriales Java en las que insertar nuestras
miniaplicaciones (aunque todo se andará), los applets se
han construido mayoritariamente, y con gran acierto comercial
(parece), como pequeñas aplicaciones interactivas, con
movimiento,
luces y sonido… en
Internet.
Llamadas a Applets con
appletviewer
Un applet es una mínima aplicación
Java diseñada para ejecutarse en un navegador Web. Por
tanto, no necesita preocuparse por un método main() ni en
dónde se realizan las llamadas. El applet asume que el
código se está ejecutando desde dentro de un
navegador.
El appletviewer se asemeja al mínimo
navegador. Espera como argumento el nombre del fichero html que
debe cargar, no se le puede pasar directamente un programa Java.
Este fichero html debe contener una marca que
especifica el código que cargará el
appletviewer:
<HTML>
<APPLET CODE=HolaMundo.class WIDTH=300
HEIGHT=100>
</APPLET>
</HTML>
El appletviewer crear un espacio de
navegación, incluyendo un área gráfica,
donde se ejecutará el applet, entonces llamará a la
clase applet apropiada. En el ejemplo anterior, el appletviewer
cargará una clase de nombre HolaMundo y le
permitirá trabajar en su espacio
gráfico.
Arquitectura de
appletviewer
El appletviewer representa el mínimo
interfaz de navegación. En la figura se muestran los pasos
que seguiría appletviewer para presentarnos el resultado
de la ejecución del código de nuestra
clase.
Esta es una visión simplificada del
appletviewer. La función principal de esta
aplicación es proporcionar al usuario un objeto de tipo
Graphics sobre el que dibujar, y varias funciones para facilitar
el uso del objeto
Graphics.
Ciclo de vida de un
Applet
Cuando un applet se carga en el appletviewer,
comienza su ciclo de vida,
que pasaría por las siguientes fases:
Se crea una instancia de la clase que controla el
applet. En el ejemplo de la figura anterior, sería la
clase HolaMundo.
El applet se incializa.
El applet comienza a ejecutarse.
El applet empieza a recibir llamadas. Primero
recibe una llamada init (inicializar), seguida de un mensaje
start (empezar) y paint (pintar).
Estas llamadas pueden ser recibidas
asíncronamente.
4.3 Escribir Applets Java
Para escribir applets Java, hay que utilizar una
serie de métodos, algunos de los cuales ya se hay
sumariado al hablar de los métodos del appletviewer, que
es el visualizador de applets de Sun. Incluso para el applet
más sencillo necesitaremos varios métodos. Son los
que se usan para arrancar (start) y detener (stop) la
ejecución del applet, para pintar (paint) y
actualizar (update) la pantalla y para capturar la
información que se pasa al applet desde el fichero HTML a
través de la marca
APPLET.
init ( )
Esta función miembro es llamada al crearse
el applet. Es llamada sólo una vez. La clase Applet no
hace nada en init(). Las clases derivadas deben
sobrecargar este método para cambiar el tamaño
durante su inicialización, y cualquier otra
inicialización de los datos que solamente deba realizarse
una vez. Deberían realizarse al menos las siguientes
acciones:
Carga de imágenes y
sonido
El resize del applet para que tenga su
tamaño correcto
Asignación de valores a las
variables globales
Por ejemplo:
public void init() {
if( width < 200 || height < 200
)
resize( 200,200 );
valor_global1 = 0;
valor_global2 = 100;
// cargaremos imágenes
en memoria sin mostrarlas
// cargaremos música de fondo en
memoria sin reproducirla
}
destroy ( )
Esta función miembro es llamada cuando el
applet no se va a usar más. La clase Applet no hace nada
en este método. Las clases derivadas
deberían sobrecargarlo para hacer una limpieza final. Los
applet multithread deberán usar destroy() para "matar"
cuanquier thread del applet que quedase activo.
start ( )
Llamada para activar el applet. Esta
función miembro es llamada cuando se visita el applet. La
clase Applet no hace nada en este método. Las clases
derivadas
deberían sobrecargarlo para comenzar una animación,
sonido,
etc.
public void start() {
estaDetenido = false;
// comenzar la reproducción de la
música
musicClip.play();
}
También se puede utilizar start() para
eliminar cualquier thread que se necesite.
stop ( )
Llamada para detener el applet. Se llama cuando el
applet desaparece de la pantalla. La clase Applet no hace nada en
este método. Las clases derivadas deberían
sobrecargarlo para detener la animación, el sonido,
etc.
public void stop() {
estaDetenido = true;
if( /* ¿se está reproduciendo
música? */
)
musicClip.stop();
}
resize ( int width,int height
)
El método init() debería llamar a
esta función miembro para establecer el tamaño del
applet. Puede utilizar las variables ancho y alto, pero no es
necesario. Cambiar el tamaño en otro sitio que no sea
init() produce un reformateo de todo el documento y no se
recomienda.
En el navegador Netscape, el tamaño del
applet es el que se indica en la marca APPLET del
HTML, no hace caso a lo que se indique desde el código
Java del applet.
width
Variable entera, su valor es el ancho definido en
el parámetro WIDTH de la marca HTML del
APPLET. Por defecto es el ancho del icono.
height
Variable entera, su valor es la altura definida en
el parámetro HEIGHT de la marca HTML del APPLET. Por
defecto es la altura del icono. Tanto width como height
están siempre disponibles para que se puede chequear el
tamaño del applet.
Podemos retomar el ejemplo de
init():
public void init() {
if( width < 200 || height < 200
)
resize( 200,200 );
…
paint( Graphics g
)
Se llama cada vez que se necesita refrescar el
área de dibujo del
applet. La clase Applet simplemente dibuja una caja con sombreado
de tres dimensiones en el área. Obviamente, la clase
derivada debería sobrecargar este método para
representar algo inteligente en la pantalla.
Para repintar toda la pantalla cuando llega un
evento Paint, se pide el
rectángulo sobre el que se va a aplicar paint() y si es
más pequeño que el tamaño real del applet se
invoca a repaint(), que como va a hacer un update(), se
actualizará toda la pantalla.
Podemos utilizar paint() para
imprimir nuestro mensaje de bienvenida:
void public paint( Graphics g )
{
g.drawString( "Hola Java!",25,25
);
// Dibujaremos la imágenes
que necesitemos
}
update( Graphics g
)
Esta es la función que se llama realmente
cuando se necesita actualizar la pantalla. La clase Applet
simplemente limpia el área y llama al método
paint(). Esta funcionalidad es suficiente en la mayoría de
los casos. De cualquier forma, las clases derivadas pueden
sustituir esta funcionalidad para sus
propósitos.
Podemos, por ejemplo, utilizar update() para
modificar selectivamente partes del área gráfica
sin tener que pintar el área completa:
public void update( Graphics g )
{
if( estaActualizado )
{
g.clear(); // garantiza la pantalla
limpia
repaint(); // podemos usar el método padre:
super.update()
}
else
// Información adicional
g.drawString( "Otra información",25,50
);
}
repaint()
A esta función se la debería llamar
cuando el applet necesite ser repintado. No debería
sobrecargarse, sino dejar que Java repinte completamente el
contenido del applet.
Al llamar a repaint(), sin parámetros,
internamente se llama a update() que borrará el
rectángulo sobre el que se redibujará y luego se
llama a paint(). Como a repaint() se le pueden pasar
parámetros, se puede modificar el rectángulo a
repintar.
getParameter ( String attr
)
Este método carga los valores
parados al applet vía la marca APPLET de HTML. El
argumento String es el nombre del parámetro que se quiere
obtener. Devuelve el valor que se le haya asignado al
parámetro; en caso de que no se le haya asignado ninguno,
devolverá null.
Para usar getParameter(), se define una cadena
genérica. Una vez que se ha capturado el parámetro,
se utilizan métodos de cadena o de números para
convertir el valor obtenido al tipo
adecuado.
public void init() {
String pv;
pv = getParameter( "velocidad"
);
if( pv == null )
velocidad = 10;
else
velocidad = Integer.parseInt( pv
);
}
getDocumentBase (
)
Indica la ruta http, o el
directorio del disco, de donde se ha recogido la página
HTML que contiene el applet, es decir, el lugar donde está
la hoja en todo Internet o en el disco.
getCodeBase (
)
Indica la ruta http, o el
directorio del disco, de donde se ha cargado el código
bytecode que forma el applet, es decir, el lugar donde
está el fichero .class en todo Internet o en el
disco.
print ( Graphics g
)
Para imprimir en impresora, al
igual que paint() se puede utilizar print(), que pintará
en la impresora el
mapa de bits del dibujo.
5. EL DEPURADOR DE JAVA –
jdb
El depurador de Java, jdb es un depurador de
línea de comandos, similar
al que Sun proporciona en sus Sistemas, dbx. Es
complicado de utilizar y un tanto críptico, por lo que, en
principio, tiene escasa practicidad y es necesaria una verdadera
emergencia para tener que recurrir a él.
Trataremos por encima los comandos que proporciona
el jdb, pero sin entrar en detalles de su funcionamiento, porque
no merece la pena. Casi es mejor esperar a disponer de
herramientas visuales para poder depurar con cierta comodidad
nuestro código Java.
Para poder utilizar el depurador, las aplicaciones
Java deben estar compiladas con la opción de
depuración activada, -g. Posteriormente se puede lanzar
appletviewer con la opción de depuración, debug, y
habremos puesto en marcha jdb.
5.1 Depurar HolaMundo
Hemos modificado nuestro applet de ejemplo para
utilizarlo en nuestra sesión de ejemplo con el depurador.
Se compilaría con el comando:
%javac -g hm.java
y el contenido de nuestro applet HolaMundo
modificado y guardado en el fichero hm.java sería el
siguiente:
//
// Applet HolaMundo de ejemplo, para
depurar
//
import java.awt.Graphics;
import java.applet.Applet;
public class hm extends Applet {
int i;
public void paint( Graphics g )
{
i = 10;
g.drawString( "Hola Mundo!",25,25
);
}
}
Una vez compilado, iniciamos la sesión
lanzando el visor de applets de Sun con la opción de
depuración, utilizando el comando:
%appletviewer -debug hm.html
El fichero hm.html contiene las líneas
mínimas para poder activar el applet, estas líneas
son las que reproducimos:
<html>
<applet code=hm.class width=100
height=100>
</applet>
</html>
Se inicia pues la sesión con el depurador y
vamos a ir reproduciendo lo que aparece en la pantalla a medida
que vamos introduciendo comandos:
%appletviewer -debug hm.html
Loading jdb…
0xee301bf0:class(sun.applet.AppletViewer)
>
5.2 Comando help
El comando help proporciona una lista de los
comandos que están disponibles en la sesión de jdb.
Esta lista es la que sigue, en donde hemos aprovechado la
presencia de todos los comandos para comentar la acción
que cada uno de ellos lleva a cabo.
>help
** command list **
threads [threadgroup] — lista
threads
thread <thread id> — establece el thread
por defecto
suspend [thread id(s)] — suspende threads (por
defecto, todos)
resume [thread id(s)] — continúa threads
(por defecto, todos)
where [thread id]|all — muestra la pila
de un thread
threadgroups — lista los grupos de
threads
threadgroup <name> — establece el grupo de
thread actual
print <id> [id(s)] — imprime un objeto o
campo
dump <id> [id(s)] — imprime toda la
información del objeto
locals — imprime las variables locales de la pila
actual
classes — lista las clases
conocidas
methods <class id> — lista los
métodos de una clase
stop in <class id>.<method> — fija un
punto de ruptura en un método
stop at <class id>:<line> — establece
un punto de ruptura en una línea
up [n frames] — ascender en la pila de
threads
down [n frames] — descender en la pila de
threads
clear <class id>:<line> — eliminar un
punto de ruptura
step — ejecutar la línea
actual
cont — continuar la ejecución desde el
punto de ruptura
catch <class id> — parar por la
excepción especificada
ignore <class id> — ignorar la
excepción especificada
list [line number] — imprimir código
fuente
use [source file path] — ver o cambiar la ruta
del fichero fuente
memory — informe del uso
de la memoria
load <classname> – carga la clase Java a ser
depurada
run <args> – comienza la ejecución de
la clase cargada
!! – repite el último
comando
help (or ?) – lista los comandos
exit (or quit) – salir del
depurador
>
5.3 Comando threadgroups
El comando threadgroups permite ver la lista de
threads que se están ejecutando. Los grupos system y
main deberían estar siempre corriendo.
>threadgroups
1.(java.lang.ThreadGroup)0xee300068
system
2.(java.lang.ThreadGroup)0xee300a98
main
>
5.4 Comando threads
El comando threads se utiliza para ver la lista
completa de los threads que se están ejecutando
actualmente.
>threads
Group system:
1.(java.lang.Thread)0xee300098 clock handler
cond
2.(java.lang.Thread)0xee300558 Idle thread
run
3.(java.lang.Thread)0xee3005d0 sync Garbage
Collector cond
4.(java.lang.Thread)0xee300620 Finalizer thread
cond
5.(java.lang.Thread)0xee300a20 Debugger agent
run
6.(java.tools.debug.BreakpointHandler)0xee300b58)
Breakpoint handler cond
Group main:
7.(java.lang.Thread)0xee300048 main
suspended
>
5.5 Comando run
El comando run es el que se utiliza para arrancar
el appletviewer en la sesión de depuración. Lo
teclearemos y luego volveremos a listar los threads que hay en
ejecución.
>run
run sun.applet.AppletViewer
hm.html
running…
main[1]threads
threads
Group
sun.applet.AppletViewer.main:
1.(java.lang.Thread)0xee3000c0 AWT-Motif
running
2.(sun.awt.ScreenUpdater)0xee302ed0 ScreenUpdater
cond. Waiting
Group applet-hm.class:
3.(java.lang.Thread)0xee302f38 Thread-6 cond.
Waiting
main[1]
El visor de applets de Sun aparecerá en la
pantalla y mostrará el conocido mensaje de saludo al
Mundo. Ahora vamos a rearrancar el appletviewer con un punto de
ruptura, para detener la ejecución del applet, y podamos
seguir mostrando los comandos disponibles en el
jdb.
main[1]exit
%appletviewer -debug hm.html
Loading jdb…
0xee3009c8:class(sun.applet.AppletViewer)
>stop in hm.paint
Breakpoint set in hm.paint
>run
run sun.applet.AppletViewer
hm.html
running…
Breakpoint hit:
hm.paint(hm.java:9)
AWT-Motif[1]
5.6 Comando where
El comando where mostrará la pila de
ejecución del applet.
AWT-Motif[1]where
[1]hm.paint(hm.java:9)
[2]sun.awt.motif.MComponentPeer.paint(MComponenetPeer.java:109)
[3]sun.awt.motif.MComponentPeer.handleExpose(MComponenetPeer.java:170)
AWT-Motif[1]
5.7 Comando use
El comando use nos informa del camino donde jdb va
a buscar los ficheros fuentes que
contienen el código Java de las clases que se están
depurando. Por defecto, utilizará el camino que se
especifique en la variable de entorno
CLASSPATH.
AWT-Motif[1]use
/usr/local/java/classes:
AWT-Motif[1]
5.8 Comando list
El comando list mostrará el código
fuente actual al comienzo del punto de ruptura que hayamos
fijado.
AWT-Motif[1]list
9 public void paint( Graphics g )
{
10 => i = 10;
11 g.drawString( "Hola Mundo!",25,25 )
;
12 }
13 }
AWT-Motif[1]
5.9 Comando dump
El comando dump nos permitirá ahora ver el
valor del objeto g pasado desde el
appletviewer.
AWT-Motif[1]dump g
g = (sun.awt.motif.X11Graphics)0xee303df8
{
int pData = 1342480
Color foreground = (java.awt.Color)0xee302378
Font font =
(java.awt.Font)0xee302138
int originX = 0
int originY = 0
float scaleX = 1
float scaleY = 1
Image image = null
}
AWT-Motif[1]
5.10 Comando step
El comando step nos porporciona el método
para ejecutar la línea actual, que estará siendo
apuntada por el indicador si hemos utilizado el comando
list.
AWT-Motif[1]step
Breakpoint hit:
hm.paint(hm.java:11)
AWT-Motif[1]list
9 public void paint( Graphics g )
{
10 i = 10;
11 => g.drawString( "Hola Mundo!",25,25
);
12 }
13 }
AWT-Motif[1]
6. AWT
6.1 Introducción al
AWT
AWT es el acrónimo del X Window Toolkit
para Java, donde X puede ser cualquier cosa: Abstract,
Alternative, Awkward, Another o Asqueroso; aunque parece que Sun
se decanta por Abstracto, seriedad por encima de todo. Se trata
de una biblioteca de
clases Java para el desarrollo de Interfaces de Usuario
Gráficas. La versión del AWT que Sun proporciona
con el JDK se desarrolló en sólo dos meses y es la
parte más débil de todo lo que representa Java como
lenguaje. El entorno que ofrece es demasiado simple, no se han
tenido en cuenta las ideas de entornos gráficos novedosos,
sino que se ha ahondado en estructuras
orientadas a eventos, llenas
de callbacks y sin soporte alguno del entorno para la construcción gráfica; veremos que la
simple acción de colocar un dibujo sobre
un botón se vuelve una tarea harto complicada.
Quizá la presión de tener que lanzar algo al
mercado haya tenido mucho que ver en la pobreza de
AWT.
JavaSoft, asegura que esto sólo era el
principio y que AWT será multi-idioma, tendrá
herramientas visuales, etc. En fin, al igual que dicen los
astrólogos, el futuro nos deparará muchas
sorpresas.
La estructura
básica del AWT se basa en Componentes y Contenedores.
Estos últimos contienen Componentes posicionados a su
respecto y son Componentes a su vez, de forma que los eventos pueden
tratarse tanto en Contenedores como en Componentes, corriendo por
cuenta del programador (todavía no hay herramientas de
composición visual) el encaje de todas las piezas,
así como la seguridad de tratamiento de los eventos
adecuados. Nada trivial.
No obstante y pese a ello, vamos a abordar en este
momento la programación con el AWT para tener la base
suficiente y poder seguir profundizando en las demás
características del lenguaje Java, porque como vamos a ir
presentando ejemplos gráficos es imprescindible el
conocimiento del AWT. Mientras tanto, esperemos que JavaSoft
sea fiel a sus predicciones y lo que ahora veamos nos sirva de
base para migrar a un nuevo y maravilloso AWT.
6.2 Interface de Usuario
La interface de usuario es la parte del programa
que permite a éste interactuar con el usuario. Las
interfaces de usuario pueden adoptar muchas formas, que van desde
la simple línea de comandos hasta las interfaces
gráficas que proporcionan las aplicaciones más
modernas.
La interface de usuario es el aspecto más
importante de cualquier aplicación. Una aplicación
sin un interfaz fácil, impide que los usuarios saquen el
máximo rendimiento del programa. Java proporciona los
elementos básicos para construir decentes interfaces de
usuario a través del
AWT.
Al nivel más bajo, el sistema operativo
transmite información desde el ratón y el teclado como
dispositivos de
entrada al programa. El AWT fue diseñado pensando en
que el programador no tuviese que preocuparse de detalles como
controlar el movimiento del
ratón o leer el teclado, ni
tampoco atender a detalles como la escritura en
pantalla. El AWT constituye una librería de clases
orientada a objeto para cubrir estos recursos y
servicios de
bajo nivel.
Debido a que el lenguaje de programación
Java es independiente de la plataforma en que se ejecuten sus
aplicaciones, el AWT también es independiente de la
plataforma en que se ejecute. El AWT proporciona un conjunto de
herramientas para la construcción de interfaces gráficas
que tienen una apariencia y se comportan de forma semejante en
todas las plataformas en que se ejecute. Los elementos de
interface proporcionados por el AWT están implementados
utilizando toolkits nativos de las plataformas, preservando una
apariencia semejante a todas las aplicaciones que se creen para
esa plataforma. Este es un punto fuerte del AWT, pero
también tiene la desventaja de que una interface
gráfica diseñada para una plataforma, puede no
visualizarse correctamente en otra diferente.
6.3 Estructura del
AWT
La estructura de
la versión actual del AWT podemos resumirla en los puntos
que exponemos a continuación:
- Los Contenedores contienen Componentes, que son
los controles básicos - No se usan posiciones fijas de los Componentes,
sino que están situados a través de una
disposición controlada (layouts) - El común denominador de más bajo
nivel se acerca al teclado,
ratón y manejo de eventos - Alto nivel de abstracción respecto al
entorno de ventanas en que se ejecute la aplicación (no
hay áreas cliente, ni
llamadas a X, ni hWnds, etc.) - La arquitectura de la aplicación es
dependiente del entorno de ventanas, en vez de tener un
tamaño fijo - Es bastante dependiente de la máquina en
que se ejecuta la aplicación (no puede asumir que un
diálogo tendrá el mismo tamaño en cada
máquina) - Carece de un formato de recursos. No se puede
separar el código de lo que es propiamente interface. No
hay ningún diseñador de interfaces
(todavía)
6.4 Componentes y
Contenedores
Una interface gráfica está
construida en base a elementos gráficos básicos,
los Componentes. Típicos ejemplos de estos Componentes son
los botones, barras de desplazamiento, etiquetas, listas, cajas
de selección o campos de texto. Los
Componentes permiten al usuario interactuar con la
aplicación y proporcionar información desde el
programa al usuario sobre el estado del
programa. En el AWT, todos los Componentes de la interface de
usuario son instancias de la clase Component o uno de sus
subtipos.
Los Componentes no se encuentran aislados, sino
agrupados dentro de Contenedores. Los Contenedores contienen y
organizan la situación de los Componentes; además,
los Contenedores son en sí mismos Componentes y como tales
pueden ser situados dentro de otros Contenedores. También
contienen el código necesario para el control de eventos, cambiar
la forma del cursor o modificar el icono de la aplicación.
En el AWT, todos los Contenedores son instancias de la clase
Container o uno de sus subtipos.
Los Componentes deben circunscribirse dentro del
Contenedor que los contiene. Esto hace que el anidamiento de
Componentes (incluyendo Contenedores) en Contenedores crean
árboles de elementos, comenzando con un Contenedor en la
raiz del árbol y expandiéndolo en sus
ramas.
7.
GRAFICOS
7.1 Objetos
Gráficos
En páginas anteriores ya se ha mostrado
cómo escribir applets, cómo lanzarlos y los
fundamentos básicos de la presentación de
información sobre ellos. Ahora, pues, querremos hacer
cosas más interesantes que mostrar texto; ya que
cualquier página HTML puede mostrar texto. Para ello, Java
proporciona la clase Graphics, que permite mostrar texto a
través del método drawString(), pero también
tiene muchos otros métodos de dibujo. Para
cualquier programador, es esencial el entendimiento de la clase
Graphics, antes de adentrarse en el dibujo de
cualquier cosa en Java.
Esta clase proporciona el entorno de trabajo para
cualquier operación gráfica que se realice dentro
del AWT. Juega dos importantes papeles: por un lado, es el
contexto gráfico, es decir, contiene la información
que va a afectar a todas las operaciones
gráficas, incluyendo los colores de fondo
y texto, la fuente de caracteres, la localización y
dimensiones del rectángulo en que se va a pintar, e
incluso dispone de información sobre el eventual destino
de las operaciones gráficas (pantalla o imagen). Por otro
lado, la clase Graphics proporciona métodos que permiten
el dibujo de primitivas, figuras y la manipulación de
fonts de caracteres y colores.
También hay clases para la manipulación de
imágenes, doble-buffering, etc.
Para poder pintar, un programa necesita un
contexto gráfico válido, representado por una
instancia de la clase Graphics. Pero, como esta clase es
abstracta, no se puede instanciar directamente; así que
debemos crear un componente y pasarlo al programa como un
argumento a los métodos paint() o
update().
Los dos métodos anteriores, paint() y
update(), junto con el método repaint() son los que
están involucrados en la presentación de
gráficos en pantalla. El AWT, para reducir el tiempo que
necesitan estos métodos para realizar el repintado en
pantalla de gráficos, tiene dos axiomas:
- Primero, el AWT repinta solamente aquellos
Componentes que necesitan ser repintados, bien porque
estuviesen cubiertos por otra ventana o porque se pida su
repintado directamente - Segundo, si un Componente estaba tapado y se
destapa, el AWT repinta solamente la porción del
Componente que estaba oculta
En la ejecución del applet que aparece a
continuación, EjemploGraf.java, podemos observar como se
realiza este proceso.
Ignorar la zona de texto de la parte superior del applet de
momento, y centrar la mirada en la parte coloreada. Utilizando
otra ventana, tapar y destapar parte de la zona que ocupa el
applet. Se observará que solamente el trozo de applet que
estaba cubierto es el que se repinta. Yendo un poco más
allá, solamente aquellos componentes que estén
ocultos y se vuelvan a ver serán los que se repinten, sin
tener en cuenta su posición dentro de la jerarquía
de componentes.
La pantalla en Java se incrementa de izquierda a
derecha y de arriba hacia abajo, tal como muestra la
figura:
Los pixels de la pantalla son pues:
posición 0 + ancho de la pantalla – 1.
En los textos, el punto de inserción se
encuentra en la línea base de la primera
letra.
7.2 Métodos para
Dibujos
Vamos a presentar métodos para dibujar
varias figuras geométricas. Como estos métodos
funcionan solamente cuando son invocados por una instancia
válida de la clase Graphics, su ámbito de
aplicación se restringe a los componentes que se utilicen
en los métodos paint() y update(). Normalmente los
métodos de dibujo de primitivas gráficas funcionan
por pares: un método pinta la figura normal y el otro
pinta la figura rellena.
drawLine( x1,y1,x2,y2 )
drawRect( x,y,ancho,alto )
fillRect( x,y,ancho,alto )
clearRect( x,y,ancho.alto )
drawRoundRect( x,y,ancho,alto,anchoArco,altoArco
)
fillRoundRect( x,y,ancho,alto,anchoArco,altoArco
)
draw3DRect( x,y,ancho,alto,boolean elevado
)
fill3DRect( x,y,ancho,alto,boolean elevado
)
drawOval( x,y,ancho,alto )
fillOval( x,y,ancho,alto )
drawArc( x,y,ancho,alto,anguloInicio,anguloArco
)
fillArc( x,y,ancho,alto,anguloInicio,anguloArco
)
drawPolygon( int[] puntosX,int[]
puntosY[],numPuntos )
fillPolygon( int[] puntosX,int[]
puntosY[],numPuntos )
drawString( string s,x,y )
drawChars( char data[],offset,longitud,x,y
)
drawBytes( byte data[],offset,longitud,x,y
)
copyArea( xSrc,ySrc,ancho,alto,xDest,yDest
)
7.3 Métodos para
Imagenes
Los objetos Graphics pueden mostrar
imágenes a través del
método:
drawImage( Image img,int x,int y,ImageObserver
observador );
Hay que tener en cuenta que el método
drawImage() necesita un objeto Image y un objeto ImageObserver.
Podemos cargar una imagen desde un
fichero de dibujo (actualmente sólo se soportan formatos
GIF y JPEG) con el método getImage():
Image img = getImage(
getDocumentBase(),"fichero.gif" );
La forma de invocar al método getImage() es
indicando un URL donde se encuentre el fichero que contiene la
imagen que
queremos presentar y el nombre de ese fichero:
getImage( URL directorioImagen,String
ficheroImagen );
Un URL común para el método
getImage() es el directorio donde está el fichero HTML. Se
puede acceder a esa localización a través del
método getDocumentBase() de la clase Applet, como ya se ha
indicado.
Normalmente, se realiza el getImage() en el
método init() del applet y se muestra la
imagen cargada
en el método paint(), tal como se muestra en el
ejemplo siguiente:
public void init() {
img = getImage( getDocumentBase(),"pepe.gif"
);
}
public void paint( Graphics g )
{
g.drawImage( img,x,y,this );
}
En el applet Imagen.java, podemos ver el ejemplo
completo. Su ponemos en él la existencia del fichero
"Imagenes/pepe.gif":
import java.awt.*;
import
sun.awt.image.URLImageSource;
import java.applet.Applet;
public class Imagen extends Applet
{
Imagen pepe;
public void init() {
pepe = getImage(
getDocumentBase(),"Imagenes/pepe.gif" );
}
public void paint( Graphics g )
{
g.drawString( pepe,25,25,this );
}
}
7.4 Sonido en Java
Java también posee métodos
predefinidos para reproducir sonido. El ordenador remoto no
necesita tener un reproductor de audio; Java realizará la
reproducción (evidentemente, el ordenador remoto, en donde
se ejecuta el applet, necesitará disponer de hardware de
sonido).
Reproducción de
sonido
La forma más fácil de reproducir
sonido es a través del método
play():
play( URL directorioSonido,String ficheroSonido
);
o, simplemente:
play( URL unURLdeSonido );
Un URL común para el método play()
es el directorio donde está el fichero HTML. Se puede
acceder a esa localización a través del
método getDocumentBase() de la clase
Applet:
play( getDocumentBase(),"sonido.au"
);
para que esto funcione, el fichero de la clase y
el fichero sonido.au deberían estar en el mismo
directorio.
Reproducción
Repetitiva
Se puede manejar el sonido como si de
imágenes se tratara. Se pueden cargar y reproducir
más tarde.
Para cargar un clip de sonido, se utiliza el
método getAudioClip():
AudoClip sonido;
sonido = getAudioClip(
getDocumentBase(),"risas.au" );
Una vez que se carga el clip de sonido, se pueden
utilizar tres métodos:
sonido.play();
para reproducir el clip de
sonido.
sonido.loop();
para iniciar la reproducción del clip de
sonido y que entre en un blucle de reproducción, es decir,
en una repetición automática del
clip.
sonido.stop();
para detener el clip de sonido que se encuentre en
ese instante en reproducción.
7.5 Entrada por
Ratón
Una de las características más
útiles que ofrece Java es el soporte directo de la
interactividad. La aplicación puede reaccionar a los
cambios producidos en el ratón, por ejemplo, sin necesidad
de escribir ninguna línea de código para su
control, solamente indicando qué se quiere hacer cuando el
ratón haga algo.
El evento más común en el
ratón es el click. Este evento es gobernado por dos
métodos: mouseDown() (botón pulsado) y mouseUp()
(botón soltado). Ambos métodos son parte de la
clase Applet, pero se necesita definir sus acciones
asociadas, de la misma forma que se realiza con init() o con
paint().
8. EXCEPCIONES EN
JAVA
8.1 Manejo de Excepciones
Vamos a mostrar como se utilizan las excepciones,
reconvirtiendo nuestro applet de saludo a partir de la
versión iterativa de
HolaIte.java:
import java.awt.*;
import java.applet.Applet;
public class HolaIte extends Applet
{
private int i = 0;
private String Saludos[] = {
"Hola Mundo!",
"HOLA Mundo!",
"HOLA MUNDO!!"
};
public void paint( Graphics g )
{
g.drawString( Saludos[i],25,25
);
i++;
}
}
Normalmente, un programa termina con un mensaje de
error cuando se lanza una excepción. Sin embargo, Java
tiene mecanismos para excepciones que permiten ver qué
excepción se ha producido e intentar recuperarse de
ella.
Vamos a reescribir el método paint() de
nuestra versión iterativa del saludo:
public void paint( Graphics g )
{
try {
g.drawString( Saludos[i],25,25
);
} catch( ArrayIndexOutOfBoundsException e )
{
g.drawString( "Saludos desbordado",25,25
);
} catch( Exception e ) {
// Cualquier otra
excepción
System.out.println( e.toString()
);
} finally {
System.out.println( "Esto se imprime siempre!"
);
}
i++;
}
La palabra clave finally define un bloque de
código que se quiere que sea ejecutado siempre, de acuerdo
a si se capturó la excepción o no. En el ejemplo
anterior, la salida en la consola, con i=4
sería:
Saludos desbordado
¡Esto se imprime siempre!
8.2 Generar Excepciones en
Java
Cuando se produce un error se debería
generar, o lanzar, una excepción. Para que un
método en Java, pueda lanzar excepciones, hay que
indicarlo expresamente.
void MetodoAsesino() throws
NullPointerException,CaidaException
Se pueden definir excepciones propias, no hay por
qué limitarse a las predefinidas; bastará con
extender la clase Exception y proporcionar la funcionalidad extra
que requiera el tratamiento de esa
excepción.
También pueden producirse excepciones no de
forma explícita como en el caso anterior, sino de forma
implícita cuando se realiza alguna acción ilegal o
no válida.
Las excepciones, pues, pueden originarse de dos
modos: el programa hace algo ilegal (caso normal), o el programa
explícitamente genera una excepción ejecutando la
sentencia throw (caso menos normal). La sentencia throw tiene la
siguiente forma:
throw ObtejoExcepction;
El objeto ObjetoException es un objeto de una
clase que extiende la clase Exception. El siguiente código
de ejemplo origina una excepción de división por
cero:
class melon {
public static void main( String[] a )
{
int i=0, j=0, k;
k = i/j; // Origina un error de
division-by-zero
}
}
Si compilamos y ejecutamos esta aplicación
Java, obtendremos la siguiente salida por
pantalla:
> javac melon.java
> java melon
java.lang.ArithmeticException: / by
zero
at melon.main(melon.java:5)
Las excepciones predefinidas, como
ArithmeticException, se conocen como excepciones
runtime.
Actualmente, como todas las excepciones son
eventos runtime, sería mejor llamarlas excepciones
irrecuperables. Esto contrasta con las excepciones que generamos
explícitamente, que suelen ser mucho menos severas y en la
mayoría de los casos podemos recuperarnos de ellas. Por
ejemplo, si un fichero no puede abrirse, preguntamos al usuario
que nos indique otro fichero; o si una estructura de
datos se encuentra completa, podremos sobreescribir
algún elemento que ya no se necesite.
8.3 Excepciones
Predefinidas
Las excepciones predefinidas y su jerarquía
de clases es la que se muestra en la figura:
Las siguientes son las excepciones predefinidas
más frecuentes que se pueden encontrar:
ArithmeticException
Las excepciones aritméticas son
típicamente el resultado de una división por
0:
int i = 12 / 0;
NullPointerException
Se produce cuando se intenta acceder a una
variable o método antes de ser definido:
class Hola extends Applet {
Image img;
paint( Graphics g ) {
g.drawImage( img,25,25,this );
}
}
IncompatibleClassChangeException
El intento de cambiar una clase afectada por
referencias en otros objetos, específicamente cuando esos
objetos todavía no han sido
recompilados.
ClassCastException
El intento de convertir un objeto a otra clase que
no es válida.
y = (Prueba)x; // donde
x no es de tipo Prueba
NegativeArraySizeException
Puede ocurrir si hay un error aritmético al
intentar cambiar el tamaño de un array.
OutOfMemoryException
¡No debería producirse nunca! El
intento de crear un objeto con el operador new ha fallado por
falta de memoria. Y siempre tendría que haber memoria
suficiente porque el garbage collector se encarga de
proporcionarla al ir liberando objetos que no se usan y
devolviendo memoria al sistema.
NoClassDefFoundException
Se referenció una clase que el sistema es
incapaz de encontrar.
ArrayIndexOutOfBoundsException
Es la excepción que más
frecuentemente se produce. Se genera al intentar acceder a un
elemento de un array más allá de los límites
definidos inicialmente para ese array.
UnsatisfiedLinkException
Se hizo el intento de acceder a un método
nativo que no existe. Aquí no existe un método
a.kk
class A {
native void kk();
}
y se llama a a.kk(), cuando debería llamar
a A.kk().
InternalException
Este error se reserva para eventos que no
deberían ocurrir. Por definición, el usuario nunca
debería ver este error y esta excepción no
debería lanzarse.
8.4 Crear Excepciones
Propias
También podemos lanzar nuestras propias
excepciones, extendiendo la clase
System.exception.
Por ejemplo, consideremos un programa cliente/servidor. El
código cliente se
intenta conectar al servidor, y durante 5 segundos se espera a
que conteste el servidor. Si el servidor no responde, el servidor
lanzaría la excepción de
time-out:
Cualquier método que lance una
excepción también debe capturarla, o declararla
como parte de la interface del método. Cabe preguntarse
entonces, el porqué de lanzar una excepción si hay
que capturarla en el mismo método. La respuesta es que las
excepciones no simplifican el trabajo del
control de errores. Tienen la ventaja de que se puede tener muy
localizado el control de errores y no tenemos que controlar
millones de valores de
retorno, pero no van más allá.
8.5 Capturar Excepciones
Las excepciones lanzadas por un método que
pueda hacerlo deben recoger en bloque try/catch
o
try/finally.
try
Es el bloque de código donde se
prevé que se genere una excepción. Es como si
dijésemos "intenta estas sentencias y mira a ver si se
produce una excepción". El bloque try tiene que ir
seguido, al menos, por una cláusula catch o una
cláusula finally
catch
Es el código que se ejecuta cuando se
produce la excepción. Es como si dijésemos
"controlo cualquier excepción que coincida con mi
argumento". En este bloque tendremos que asegurarnos de colocar
código que no genere excepciones. Se pueden colocar
sentencias catch sucesivas, cada una controlando una
excepción diferente. No debería intentarse capturar
todas las excepciones con una sola cláusula. Esto
representaría un uso demasiado general, podrían
llegar muchas más excepciones de las esperadas. En este
caso es mejor dejar que la excepción se propague hacia
arriba y dar un mensaje de error al usuario.
Se pueden controlar grupos de
excepciones, es decir, que se pueden controlar, a través
del argumento, excepciones semejantes.
La cláusula catch comprueba los argumentos
en el mismo orden en que aparezcan en el
programa.
Si hay alguno que coincida, se ejecuta el bloque.
El operador instanceof se utiliza para identificar exactamente
cual ha sido la identidad de
la excepción.
finally
Es el bloque de código que se ejecuta
siempre, haya o no excepción. Hay una cierta controversia
entre su utilidad, pero,
por ejemplo, podría servir para hacer un log o un
seguimiento de lo que está pasando, porque como se ejecuta
siempre puede dejarnos grabado si se producen excepciones y nos
hemos recuperado de ellas o no.
Este bloque finally puede ser útil cuando
no hay ninguna excepción. Es un trozo de código que
se ejecuta independientemente de lo que se haga en el bloque
try.
Cuando vamos a tratar una excepción, se nos
plantea el problema de qué acciones vamos
a tomar. En la mayoría de los casos, bastará con
presentar una indicación de error al usuario y un mensaje
avisándolo de que se ha producido un error y que decida si
quiere o no continuar con la ejecución del
programa.
8.6 Propagacion de
Excepciones
La cláusula catch comprueba los argumentos
en el mismo orden en que aparezcan en el
programa.
Si hay alguno que coincida, se ejecuta el bloque y
sigue el flujo de control por el bloque finally (si lo hay) y
concluye el control de la excepción.
Si ninguna de las cláusulas catch coincide
con la excepción que se ha producido, entonces se
ejecutará el código de la cláusula finally
(en caso de que la haya). Lo que ocurre en este caso, es
exactamente lo mismo que si la sentencia que lanza la
excepción no se encontrase encerrada en el bloque
try.
El flujo de control abandona este método y
retorna prematuramente al método que lo llamó. Si
la llamada estaba dentro del ámbito de una sentencia try,
entonces se vuelve a intentar el control de la excepción,
y así continuamente.
Veamos lo que sucede cuando una excepción
no es tratada en la rutina en donde se produce. El sistema Java
busca un bloque try..catch más allá de la llamada,
pero dentro del método que lo trajo aquí. Si la
excepción se propaga de todas formas hasta lo alto de la
pila de llamadas sin encontrar un controlador específico
para la excepción, entonces la ejecución se
detendrá dando un mensaje. Es decir, podemos suponer que
Java nos está proporcionando un bloque catch por defecto,
que imprime un mensaje de error y sale.
No hay ninguna sobrecarga en el sistema por
incorporar sentencias try al código. La sobrecarga se
produce cuando se genera la excepción.
Hemos dicho ya que un método debe capturar
las excepciones que genera, o en todo caso, declararlas como
parte de su llamada, indicando a todo el mundo que es capaz de
generar excepciones. Esto debe ser así para que cualquiera
que escriba una llamada a ese método esté avisado
de que le puede llegar una excepción, en lugar del valor
de retorno normal. Esto permite al programador que llama a ese
método, elegir entre controlar la excepción o
propagarla hacia arriba en la pila de llamadas.
9. Threads y
Multithreading
Considerando el entorno multithread, cada thread
(hilo, flujo de control del programa) representa un proceso
individual ejecutándose en un sistema. A veces se les
llama procesos
ligeros o contextos de ejecución. Típicamente, cada
thread controla un único aspecto dentro de un programa,
como puede ser supervisar la entrada en un determinado
periférico o controlar toda la entrada/salida del disco.
Todos los threads comparten los mismos recursos, al contrario que
los procesos en
donde cada uno tiene su propia copia de código y datos
(separados unos de otros). Gráficamente, los threads se
parecen en su funcionamiento a lo que muestra la figura
siguiente:
9.1 Flujo en Programas
Programas de flujo
único
Un programa de flujo único o mono-hilvanado
(single-thread) utiliza un único flujo de control (thread)
para controlar su ejecución. Muchos programas no necesitan
la potencia o
utilidad de
múltiples flujos de control. Sin necesidad de especificar
explícitamente que se quiere un único flujo de
control, muchos de los applets y aplicaciones son de flujo
único. Programas de flujo
múltiple
En nuestra aplicación de saludo, no vemos
el thread que ejecuta nuestro programa. Sin embargo, Java
posibilita la creación y control de threads
explícitamente. La utilización de threads en Java,
permite una enorme flexibilidad a los programadores a la hora de
plantearse el desarrollo de aplicaciones. La simplicidad para
crear, configurar y ejecutar threads, permite que se puedan
implementar muy poderosas y portables aplicaciones/applets que no
se puede con otros lenguajes de tercera generación. En un
lenguaje orientado a Internet como es Java, esta herramienta es
vital.
Si se ha utilizado un navegador con soporte Java,
ya se habrá visto el uso de múltiples threads en
Java. Habrá observado que dos applet se pueden ejecutar al
mismo tiempo, o que puede desplazarla página del navegador
mientras el applet continúa ejecutándose. Esto no
significa que el applet utilice múltiples threads, sino
que el navegador es multithreaded.
Las aplicaciones (y applets) multithreaded
utilizan muchos contextos de ejecución para cumplir su
trabajo. Hacen uso del hecho de que muchas tareas contienen
subtareas distintas e independientes. Se puede utilizar un thread
para cada subtarea.
Mientras que los programas de flujo único
pueden realizar su tarea ejecutando las subtareas
secuencialmente, un programa multithreaded permite que cada
thread comience y termine tan pronto como sea posible. Este
comportamiento
presenta una mejor respuesta a la entrada en tiempo
real.
9.2 Creacion y Control de
threads
Creación de un
Thread
Hay dos modos de conseguir threads en Java. Una es
implementando la interface Runnable, la otra es extender la clase
Thread.
La implementación de la interface Runnable
es la forma habitual de crear threads. Las interfaces
proporcionan al programador una forma de agrupar el trabajo de
infraestructura de una clase. Se utilizan para diseñar los
requerimientos comunes al conjunto de clases a implementar. La
interface define el trabajo y la clase, o clases, que implementan
la interface realizan ese trabajo. Los diferentes grupos de clases
que implementen la interface tendrán que seguir las mismas
reglas de funcionamiento.
Hay una cuantas diferencias entre interface y
clase. Primero, una interface solamente puede contener
métodos abstractos y/o variables estáticas y
finales (constantes). Las clases, por otro lado, pueden
implementar métodos y contener variables que no sean
constantes. Segundo, una interface no puede implementar cualquier
método. Una clase que implemente una interface debe
implementar todos los métodos definidos en esa interface.
Una interface tiene la posibilidad de poder extenderse de otras
interfaces y, al contrario que las clases, puede extenderse de
múltiples interfaces. Además, una interface no
puede ser instanciada con el operador new; por ejemplo, la
siguiente sentencia no está permitida:
Runnable a = new Runnable(); // No se
permite
El primer método de crear un thread es
simplemente extender la clase Thread:
class MiThread extends Thread {
public void run() {
. . .
}
El ejemplo anterior crea una nueva clase MiThread
que extiende la clase Thread y sobrecarga el método
Thread.run() por su propia implementación. El
método run() es donde se realizará todo el trabajo
de la clase. Extendiendo la clase Thread, se pueden heredar los
métodos y variables de la clase padre. En este caso,
solamente se puede extender o derivar una vez de la clase padre.
Esta limitación de Java puede ser superada a través
de la implementación de
Runnable:
public class MiThread implements Runnable
{
Thread t;
public void run() {
// Ejecución del thread una vez
creado
}
}
En este caso necesitamos crear una instancia de
Thread antes de que el sistema pueda ejecutar el proceso como
un thread. Además, el método abstracto run()
está definido en la interface Runnable tiene que ser
implementado. La única diferencia entre los dos
métodos es que este último es mucho más
flexible. En el ejemplo anterior, todavía tenemos
oportunidad de extender la clase MiThread, si fuese necesario. La
mayoría de las clases creadas que necesiten ejecutarse
como un thread , implementarán la interface Runnable, ya
que probablemente extenderán alguna de su funcionalidad a
otras clases.
No pensar que la interface Runnable está
haciendo alguna cosa cuando la tarea se está ejecutando.
Solamente contiene métodos abstractos, con lo cual es una
clase para dar idea sobre el diseño
de la clase Thread. De hecho, si vemos los fuentes de
Java, podremos comprobar que solamente contiene un método
abstracto:
package java.lang;
public interface Runnable {
public abstract void run() ;
}
Y esto es todo lo que hay sobre la interface
Runnable. Como se ve, una interface sólo proporciona un
diseño
para las clases que vayan a ser implementadas. En el caso de
Runnable, fuerza a la
definición del método run(), por lo tanto, la mayor
parte del trabajo se hace en la clase Thread. Un vistazo un poco
más profundo a la definición de la clase Thread nos
da idea de lo que realmente está
pasando:
public class Thread implements Runnable
{
…
public void run() {
if( tarea != null )
tarea.run() ;
}
}
…
}
De este trocito de código se desprende que
la clase Thread también implemente la interface Runnable.
tarea.run() se asegura de que la clase con que trabaja (la clase
que va a ejecutarse como un thread) no sea nula y ejecuta el
método run() de esa clase. Cuando esto suceda, el
método run() de la clase hará que corra como un
thread.
Arranque de un
Thread
Las aplicaciones ejecutan main() tras arrancar.
Esta es la razón de que main() sea el lugar natural para
crear y arrancar otros threads. La línea de
código:
t1 = new TestTh( "Thread
1",(int)(Math.random()*2000) );
crea un nuevo thread. Los dos argumentos pasados
representan el nombre del thread y el tiempo que queremos que
espere antes de imprimir el mensaje.
Al tener control directo sobre los threads,
tenemos que arrancarlos explícitamente. En nuestro ejemplo
con:
t1.start();
start(), en realidad es un método oculto en
el thread que llama al método run().
Manipulación de un
Thread
Si todo fue bien en la creación del thread,
t1 debería contener un thread válido, que
controlaremos en el método run().
Una vez dentro de run(), podemos comenzar las
sentencias de ejecución como en otros programas. run()
sirve como rutina main() para los threads; cuando run() termina,
también lo hace el thread. Todo lo que queramos que haga
el thread ha de estar dentro de run(), por eso cuando decimos que
un método es Runnable, nos obliga a escribir un
método run().
En este ejemplo, intentamos inmediatamente esperar
durante una cantidad de tiempo aleatoria (pasada a través
del constructor):
sleep( retardo );
El método sleep() simplemente le dice al
thread que duerma durante los milisegundos especificados. Se
debería utilizar sleep() cuando se pretenda retrasar la
ejecución del thread. sleep() no consume recursos del
sistema mientras el thread duerme. De esta forma otros threads
pueden seguir funcionando. Una vez hecho el retardo, se imprime
el mensaje "Hola Mundo!" con el nombre del thread y el
retardo.
Suspensión de un
Thread
Puede resultar útil suspender la
ejecución de un thread sin marcar un límite de
tiempo. Si, por ejemplo, está construyendo un applet con
un thread de animación, querrá permitir al usuario
la opción de detener la animación hasta que quiera
continuar. No se trata de terminar la animación, sino
desactivarla. Para este tipo de control de thread se puede
utilizar el método suspend().
t1.suspend();
Este método no detiene la ejecución
permanentemente. El thread es suspendido indefinidamente y para
volver a activarlo de nuevo necesitamos realizar una
invocación al método resume():
t1.resume();
Parada de un
Thread
El último elemento de control que se
necesita sobre threads es el método stop(). Se utiliza
para terminar la ejecución de un thread:
t1.stop();
Esta llamada no destruye el thread, sino que
detiene su ejecución. La ejecución no se puede
reanudar ya con t1.start(). Cuando se desasignen las variables
que se usan en el thread, el objeto thread (creado con new)
quedará marcado para eliminarlo y el garbage collector se
encargará de liberar la memoria que
utilizaba.
En nuestro ejemplo, no necesitamos detener
explícitamente el thread. Simplemente se le deja terminar.
Los programas más complejos necesitarán un control
sobre cada uno de los threads que lancen, el método stop()
puede utilizarse en esas situaciones.
Si se necesita, se puede comprobar si un thread
está vivo o no; considerando vivo un thread que ha
comenzado y no ha sido detenido.
t1.isAlive();
Este método devolverá true en caso
de que el thread t1 esté vivo, es decir, ya se haya
llamado a su método run() y no haya sido parado con un
stop() ni haya terminado el método run() en su
ejecución.
9.3 Estados de un thread
Durante el ciclo de vida
de un thread, éste se puede encontrar en diferentes
estados. La figura siguiente muestra estos estados y los
métodos que provocan el paso de un estado a otro.
Este diagrama no es
una máquina de estados finita, pero es lo que más
se aproxima al funcionamiento real de un thread
.
Nuevo
Thread
Cuando un thread está en este estado, es
simplemente un objeto Thread vacío. El sistema no ha
destinado ningún recurso para él. Desde este
estado
solamente puede arrancarse llamando al método start(), o
detenerse definitivamente, llamando al método stop(); la
llamada a cualquier otro método carece de sentido y lo
único que provocará será la
generación de una excepción de tipo
IllegalThreadStateException.
Ejecutable
La llamada al método start() creará
los recursos del sistema necesarios para que el thread puede
ejecutarse, lo incorpora a la lista de procesos
disponibles para ejecución del sistema y llama al
método run() del thread. En este momento nos encontramos
en el estado
"Ejecutable" del diagrama. Y
este estado es
Ejecutable y no En Ejecución, porque cuando el thread
está aquí no esta corriendo. Muchos ordenadores
tienen solamente un procesador lo que
hace imposible que todos los threads estén corriendo al
mismo tiempo. Java implementa un tipo de scheduling o lista de
procesos, que permite que el procesador sea
compartido entre todos los procesos o threads que se encuentran
en la lista. Sin embargo, para nuestros propósitos, y en
la mayoría de los casos, se puede considerar que este
estado es realmente un estado "En Ejecución", porque la
impresión que produce ante nosotros es que todos los
procesos se ejecutan al mismo tiempo.
Cuando el thread se encuentra en este estado,
todas las instrucciones de código que se encuentren dentro
del bloque declarado para el método run(), se
ejecutarán secuencialmente.
Parado
El thread entra en estado "Parado" cuando alguien
llama al método suspend(), cuando se llama al
método sleep(), cuando el thread está bloqueado en
un proceso de entrada/salida o cuando el thread utiliza su
método wait() para esperar a que se cumpla una determinada
condición. Cuando ocurra cualquiera de las cuatro cosas
anteriores, el thread estará
Parado.
Para cada una de los cuatro modos de entrada en
estado Parado, hay una forma específica de volver a estado
Ejecutable. Cada forma de recuperar ese estado es exclusiva; por
ejemplo, si el thread ha sido puesto a dormir, una vez
transcurridos los milisegundos que se especifiquen, él
solo se despierta y vuelve a estar en estado Ejecutable. Llamar
al método resume() mientras esté el thread
durmiendo no serviría para nada.
Los métodos de recuperación del
estado Ejecutable, en función de la forma de llegar al
estado Parado del thread, son los siguientes:
- Si un thread está dormido, pasado el
lapso de tiempo - Si un thread está suspendido, luego de
una llamada al método resume() - Si un thread está bloqueado en una
entrada/salida, una vez que el comando E/S concluya su
ejecución - Si un thread está esperando por una
condición, cada vez que la variable que controla esa
condición varíe debe llamarse a notify() o
notifyAll()
Muerto
Un thread se puede morir de dos formas: por causas
naturales o porque lo maten (con stop()). Un thread muere
normalmente cuando concluye de forma habitual su método
run(). Por ejemplo, en el siguiente trozo de código, el
bucle while es un bucle finito -realiza la iteración 20
veces y termina-:
El método
isAlive()
La interface de programación de la clase
Thread incluye el método isAlive(), que devuelve true si
el thread ha sido arrancado (con start()) y no ha sido detenido
(con stop()). Por ello, si el método isAlive() devuelve
false, sabemos que estamos ante un "Nuevo Thread" o ante un
thread "Muerto". Si nos devuelve true, sabemos que el thread se
encuentra en estado "Ejecutable" o "Parado". No se puede
diferenciar entre "Nuevo Thread" y "Muerto", ni entre un thread
"Ejecutable" o "Parado".
9.4 Comunicacion entre
threads
Otra clave para el éxito y la ventaja de la
utilización de múltiples threads en una
aplicación, o aplicación multithreaded, es que
pueden comunicarse entre sí. Se pueden diseñar
threads para utilizar objetos comunes, que cada thread puede
manipular independientemente de los otros
threads.
El ejemplo clásico de comunicación de threads es un modelo
productor/consumidor. Un
thread produce una salida, que otro thread usa (consume), sea lo
que sea esa salida. Vamos entonces a crear un productor, que
será un thread que irá sacando caracteres por su
salida; crearemos también un consumidor que
ira recogiendo los caracteres que vaya sacando el productor y un
monitor que
controlará el proceso de sincronización entre los
threads.
Funcionará como una tubería,
insertando el productor caracteres en un extremos y leyéndolos
el consumidor en el
otro, con el monitor siendo
la propia tubería.
11.METODOS
NATIVOS
Un método nativo es un método Java
(una instancia de un objeto o una clase) cuya
implementación se ha realizado en otro lenguaje de
programación, por ejemplo, C. Vamos a ver cómo se
integran métodos nativos en clases Java. Actualmente, el
lenguaje Java solamente proporciona mecanismos para integrar
código C en programas Java.
Veamos pues los pasos necesarios para mezclar
código nativo C y programas Java. Recurriremos
(¡Cómo no!) a nuestro saludo; en este caso, el
programa HolaMundo tiene dos clases Java: la primera implementa
el método main() y la segunda, HolaMundo, tiene un
método nativo que presenta el mensaje de saludo. La
implementación de este segundo método la
realizaremos en C.
10.1 Escribir Código
Java
En primer lugar, debemos crear una clase Java,
HolaMundo, que declare un método nativo. También
debemos crear el programa principal que cree el objeto HolaMundo
y llame al método nativo.
Las siguientes líneas de código
definen la clase HolaMundo, que consta de un método y un
segmento estático de código:
class HolaMundo {
public native void
presentaSaludo();
static {
System.loadLibrary( "hola" );
}
}
Podemos decir que la implementación del
método presentaSaludo() de la clase HolaMundo está
escrito en otro lenguaje, porque la palabra reservada native
aparece como parte de la definición del método.
Esta definición, proporciona solamente la
definición para presentaSaludo() y no porporciona ninguna
implementación para él. La implementación la
proporcionaremos desde un fichero fuente separado, escrito en
lenguaje
C.
La definición para presentaSaludo()
también indica que el método es un método
público, no acepta argumentos y no devuelve ningún
valor. Al igual que cualquier otro método, los
métodos nativos deben estar definidos dentro de una clase
Java.
El código C que implementa el método
presentaSaludo() debe ser compilado en una librería
dinámica y cargado en la clase Java que lo
necesite. Esta carga, mapea la implementación del
método nativo sobre su
definición.
El siguiente bloque de código carga la
librería dinámica, en este caso hola. El sistema
Java ejecutará un bloque de código estático
de la clase cuando la cargue.
Todo el código anterior forma parte del
fichero HolaMundo.java, que contiene la clase HolaMundo. En un
fichero separado, Main.java, vamos a crear una aplicación
Java que instancie a la clase HolaMundo y llame al método
nativo presentaSaludo().
class Main {
public static void main( String args[] )
{
new
HolaMundo().presentaSaludo();
}
}
Como se puede observar, llamamos al método
nativo del mismo modo que a cualquier otro método Java;
añadimos el nombre del método al final del nombre
del objeto con un punto ("."). El conjunto de paréntesis
que sigue al nombre del método encierra los argumentos que
se le pasen. En este caso, el método presentaSaludo() no
recibe ningún tipo de argumento.
10.2 Compilar el Código
Java
Utilizaremos ahora el compilador javac para
compilar el código Java que hemos desarrollado.
Compilaremos los dos ficheros fuentes de
código Java que hemos creado, tecleando los siguientes
comandos:
> javac HolaMundo.java
> javac Main.java
10.3 Crear el Fichero de
Cabecera
Ahora debemos utilizar la aplicación javah
para conseguir el fichero de cabecera .h. El fichero de cabecera
define una estructura que
representa la clase HolaMundo sobre código C y proporciona
la definición de una función C para la
implementación del método nativo presentaSaludo()
definido en ese clase.
Ejecutamos javah sobre la clase HolaMundo, con el
siguiente comando:
> javah HolaMundo
Por defecto, javah creará el nuevo fichero
.h en el mismo directorio en que se encuentra el fichero .class,
obtenido al compilar con javac el código fuente Java
correspondiente a la clase. El fichero que creará,
será un fichero de cabecera del mismo nombre que la clase
y con extensión .h. Por ejemplo, el comando anterior
habrá creado el fichero HolaMundo.h, cuyo contenido
será el siguiente:
/* DO NOT EDIT THIS FILE – it is machine generated
*/
#include <native.h>
/* Header for class HolaMundo */
#ifndef _Included_HolaMundo
#define _Included_HolaMundo
typedef struct ClassHolaMundo {
char PAD; /* ANSI C requires structures to have a
least one member */
} ClassHolaMundo;
HandleTo(HolaMundo);
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void
HolaMundo_presentaSaludo(struct HHolaMundo *);
#ifdef __cplusplus
}
#endif
#endif
Este fichero de cabecera contiene la
definición de una estructura llamada ClassHolaMundo. Los
miembros de esta estructura son paralelos a los miembros de la
clase Java correspondiente; es decir, los campos en la estructura
corresponden a las variables de la clase. Pero como HolaMundo no
tiene ninguna variable, la estructura se encuentra vacía.
Se pueden utilizar los miembros de la estructura para referenciar
a variables instanciadas de la clase desde las funciones C.
Además de la estructura C similar a la clase Java, vemos
que la llamada de la función C está declarada
como:
extern void HolaMundo_presentaSaludo( struct
HHolaMundo *);
Esta es la definición de la función
C que deberemos escribir para implementar el método nativo
presentaSaludo() de la clase HolaMundo. Debemos utilizar esa
definición cuando lo implementemos. Si HolaMundo llamase a
otros métodos nativos, las definiciones de las funciones
también aparecerían aquí.
El nombre de la función C que implementa el
método nativo está derivado del nombre del paquete,
el nombre de la clase y el nombre del método nativo.
Así, el método nativo presentaSaludo() dentro de la
clase HolaMundo es HolaMundo_presentaSaludo(). En este ejemplo,
no hay nombre de paquete porque HolaMundo se considera englobado
dentro del paquete por defecto.
La función C acepta un parámetro,
aunque el método nativo definido en la clase Java no
acepte ninguno. Se puede pensar en este parámetro como si
fuese la variable this de C++. En nuestro caso, ignoramos el
parámetro this.
10.4 Crear el Fichero de
stubs
Volvemos a utilizar la aplicación javah
para crear el fichero de stubs, que contiene todas las
declaraciones de métodos, con sus llamadas y argumentos,
listos para que nosotros rellenemos el cuerpo de los
métodos con los algoritmos que
necesitemos implementar. Proporciona la unión entre la
clase Java y su estructura C paralela.
Para generar este fichero, debemos indicar el
parámetro .stubs al ejecutar la aplicación javah
sobre la clase HolaMundo, de la siguiente
forma:
> javah -stubs HolaMundo
Del mismo modo que se generaba el fichero .h; el
nombre del fichero de stubs será el nombre de la clase con
la extensión .c. En nuestro ejemplo, será
HolaMundo.c, y su contenido será el
siguiente:
/* DO NOT EDIT THIS FILE – it is machine generated
*/
#include <StubPreamble.h>
/* Stubs for class HolaMundo */
/* SYMBOL:
"HolaMundo/presentaSaludo()V",
Java_HolaMundo_presentaSaludo_stub
*/
__declspec(dllexport) stack_item
*Java_HolaMundo_presentaSaludo_stub(stack_item
*_P_,struct execenv *_EE_) {
extern void HolaMundo_presentaSaludo(void
*);
(void)
HolaMundo_presentaSaludo(_P_[0].p);
return _P_;
}
10.5 Escribir la funcion
C
Escribiremos la función C para el
método nativo en un fichero fuente de código C. La
implementación será una función habitual C,
que luego integraremos con la clase Java. La definición de
la función C debe ser la misma que la que se ha generado
con javah en el fichero HolaMundo.h. La implementación que
proponemos la guardaremos en el fichero HolaImp.c, y
contendrá las siguientes línea de
código:
#include <StubPreamble.h>
#include "HolaMundo.h>
#include <stdio.h>
void HolaMundo_presentaSaludo( struct HHolaMundo
*this ) {
printf( "Hola Mundo, desde el Tutorial de Javan"
);
return;
}
Como se puede ver, la implementación no
puede ser más sencilla: hace una llamada a la
función printf() para presentar el saludo y
sale.
En el código se incluyen tres ficheros de
cabecera:
StubsPreamble.h
Proporciona la información para que el
código C pueda interactuar con el sistema Java. Cuando se
escriben métodos nativos, siempre habrá que incluir
este fichero en el código fuente C.
HolaMundo.h
Es el fichero de cabecera que hemos generado para
nuestra clase. Contiene la estructura C que representa la clase
Java para la que estamos escribiendo el método nativo y la
definición de la función para ese método
nativo.
stdio.h
Es necesario incluirlo porque utilizamos la
función printf() de la librería estándar de
C, cuya declaración se encuentra en este fichero de
cabecera.
10.6 Crear la Libreria
Dinámica
Utilizaremos el compilador C para compilar el
fichero .h, el fichero de stubs y el fichero fuente .c; para
crear una librería dinámica. Para crearla,
utilizaremos el compilador C de nuestro sistema, haciendo que los
ficheros HolaMundo.c y HolaImp.c generen una librería
dinámica de nombre hola, que será la que el sistema
Java cargue cuando ejecute la aplicación que estamos
construyendo.
Vamos a ver cómo generamos esta
librería en Unix y en Windows '95.
Unix
Teclearemos el siguiente
comando:
% cc -G HolaMundo.c HolaImp.c -o
libhola.so
En caso de que no encuentre el compilador los
ficheros de cabecera, se puede utilizar el flag -I para indicarle
el camino de búsqueda, por ejemplo:
% cc -G -I$JAVA_HOME/include HolaMundo.c HolaImp.c
-o libhola.so
donde $JAVA_HOME es el directorio donde se ha
instalado la versión actual del Java Development
Kit.
Windows '95
El comando a utilizar en este caso es el
siguiente:
c:>cl HolaMundo.c HolaImp.c -Fhola.dll -MD -LD
javai.lib
Este comando funciona con Microsoft
Visual C++ 2.x
y posteriores. Si queremos indicar al compilador donde se
encuentran los ficheros de cabecera y las librerías,
tendremos que fijar dos variables de entorno:
c:>SET
INCLUDE=%JAVAHOME%include;%INCLUDE%
c:>SET
LIB=%JAVAHOME%lib;%LIB%
donde %JAVAHOME% es el directorio donde se ha
instalado la versión actual del Java Development
Kit.
10.7 Ejecutar el Programa
Y, por fin, utilizaremos el intérprete de
Java, java, para ejecutar el programa que hemos construido
siguiendo todos los pasos anteriormente descritos. Si tecleamos
el comando:
> java Main
obtendremos el resultado
siguiente:
% Hola Mundo, desde el Tutorial de
Java
Si no aparece este mensaje de saludo y lo que
aparece en pantalla son expresiones como UnsatisfiedLinkError, es
porque no tenemos fijado correctamente el camino de la
librería dinámica que hemos generado. Este camino
es la lista de directorios que el sistema Java utilizará
para buscar las librerías que debe cargar. Debemos
asegurarnos de que el directorio donde se encuentra nuestra
librería hola recién creada, figura entre
ellos.
Si fijamos el camino correcto y ejecutamos de
nuevo el programa, veremos que ahora sí obtenemos el
mensaje de saludo que esperábamos.
Con ello, hemos visto como integrar código
C en programas Java. Quedan muchas cuestiones por medio, como la
equivalencia de tipos entre Java y C, el paso de
parámetros, el manejo de cadenas, etc. Pero eso
supondría entrar en mucha más profundidad dentro de
Java de la que aquí pretendemos (por
ahora).
11. Entrada/Salida
Estándar
Los usuarios de Unix, y aquellos familiarizados
con las líneas de comandos de otros sistemas como DOS, han
utilizado un tipo de entrada/salida conocida comúnmente
por entrada/salida estándar. El fichero de entrada
estándar (stdin) es simplemente el teclado. El
fichero de salida estándar (stdout) es típicamente
la pantalla (o la ventana del terminal). El fichero de salida de
error estándar (stderr) también se dirige
normalmente a la pantalla, pero se implementa como otro fichero
de forma que se pueda distinguir entre la salida normal y (si es
necesario) los mensajes de
error.
11.1 La clase System
Java tiene acceso a la entrada/salida
estándar a través de la clase System. En concreto, los
tres ficheros que se implementan son:
Stdin
System.in implementa stdin como una instancia de
la clase InputStream. Con System.in, se accede a los
métodos read() y skip(). El método read() permite
leer un byte de la entrada. skip( long n ), salta n bytes de la
entrada.
Stdout
System.out implementa stdout como una instancia de
la clase PrintStream. Se pueden utilizar los métodos
print() y println() con cualquier tipo básico Java como
argumento.
Stderr
System.err implementa stderr de la misma forma que
stdout. Como con System.out, se tiene acceso a los métodos
de PrintStream.
Vamos a ver un pequeño ejemplo de
entrada/salida en Java. El código siguiente, miType.java,
reproduce, o funciona como la utilidad cat de
Unix o type de DOS:
import java.io.*;
class miType {
public static void main( String args[] ) throws
IOException {
int c;
int contador = 0;
while( (c = System.in.read() ) != 'n'
)
{
contador++;
System.out.print( (char)c );
}
System.out.println(); // Línea en
blanco
System.err.println( "Contados "+ contador +" bytes
en total." );
}
}
11.2 Clases comunes de
Entrada/Salida
Además de la entrada por teclado y
salida por pantalla, se necesita entrada/salida por fichero, como
son:
FileInputStream
DataInputStream
FileOutputStream
DataOutputStream
También existen otras clases para
aplicaciones más específicas, que no vamos a
tratar, por ser de un uso muy concreto:
PipedInputStream
BufferedInputStream
PushBackInputStream
StreamTokenizer
PipedOutputStream
BufferedOutputStream
12. FICHEROS EN
JAVA
Todos los lenguajes de
programación tienen alguna forma de interactuar con
los sistemas de ficheros locales; Java no es una
excepción.
Cuando se desarrollan applets para utilizar en
red, hay que tener en cuenta que la entrada/salida directa a
fichero es una violación de seguridad de acceso. Muchos
usuarios configurarán sus navegadores
para permitir el acceso al sistema de ficheros, pero otros
no.
Por otro lado, si se está desarrollando una
aplicación Java para uso interno, probablemente
será necesario el acceso directo a
ficheros.
Antes de realizar acciones sobre
un fichero, necesitamos un poco de información sobre ese
fichero. La clase File proporciona muchas utilidades relacionadas
con ficheros y con la obtención de información
básica sobre esos ficheros.
12.1 Creación de un objeto
File
Para crear un objeto File nuevo, se puede utilizar
cualquiera de los tres constructores
siguientes:
File miFichero;
miFichero = new File( "/etc/kk"
);
o
miFichero = new File( "/etc","kk"
);
o
File miDirectorio = new File( "/etc"
);
miFichero = new File( miDirectorio,"kk"
);
El constructor utilizado depende a menudo de otros
objetos File necesarios para el acceso. Por ejemplo, si
sólo se utiliza un fichero en la aplicación, el
primer constructor es el mejor. Si en cambio, se
utilizan muchos ficheros desde un mismo directorio, el segundo o
tercer constructor serán más cómodos. Y si
el directorio o el fichero es una variable, el segundo
constructor será el más
útil.
12.2 Ficheros de Acceso
Aleatorio
A menudo, no se desea leer un fichero de principio
a fin; sino acceder al fichero como una base de datos,
donde se salta de un registro a otro;
cada uno en diferentes partes del fichero. Java proporciona una
clase RandomAccessFile para este tipo de
entrada/salida.
Creación de un Fichero de Acceso
Aleatorio
Hay dos posibilidades para abrir un fichero de
acceso aleatorio:
Con el nombre del fichero:
miRAFile = new RandomAccessFile( String
nombre,String modo );
Con un objeto File:
miRAFile = new RandomAccessFile( File
fichero,String modo );
El argumento modo determina si se tiene acceso de
sólo lectura (r) o
de lectura/escritura
(r/w). Por ejemplo, se puede abrir un fichero de una base de datos
para actualización:
RandomAccessFile miRAFile;
miRAFile = new RandomAccessFile(
"/tmp/kk.dbf","rw" );
Acceso a la
Información
Los objetos RandomAccessFile esperan
información de lectura/escritura de
la misma manera que los objetos DataInput/DataOutput. Se tiene
acceso a todas las operaciones read() y write() de las clases
DataInputStream y DataOutputStream.
También se tienen muchos métodos
para moverse dentro de un fichero:
long getFilePointer();
Devuelve la posición actual del puntero del
fichero
void seek( long pos );
Coloca el puntero del fichero en una
posición determinada. La posición se da como un
desplazamiento en bytes desde el comienzo del fichero. La
posición 0 marca el comienzo de ese
fichero.
long length();
Devuelve la longitud del fichero. La
posición length() marca el final de ese
fichero.
Actualización de
Información
Se pueden utilizar ficheros de acceso aleatorio
para añadir información a ficheros
existentes:
miRAFile = new RandomAccessFile(
"/tmp/kk.log","rw" );
miRAFile.seek( miRAFile.length()
);
// Cualquier write() que hagamos a partir de este
punto del código
// añadirá información al
fichero
Vamos a ver un pequeño ejemplo, Log.java,
que añade una cadena a un fichero
existente:
import java.io.*;
// Cada vez que ejecutemos este programita, se
incorporara una nueva
// linea al fichero de log que se crea la primera
vez que se ejecuta
//
//
class Log {
public static void main( String args[] ) throws
IOException {
RandomAccessFile miRAFile;
String s = "Informacion a incorporarnTutorial de
Javan";
// Abrimos el fichero de acceso
aleatorio
miRAFile = new RandomAccessFile(
"/tmp/java.log","rw" );
// Nos vamos al final del
fichero
miRAFile.seek( miRAFile.length()
);
// Incorporamos la cadena al
fichero
miRAFile.writeBytes( s );
// Cerramos el fichero
miRAFile.close();
}
}
13. COMUNICACIONES
EN JAVA
13.1 Modelo de
Comunicaciones
con Java
En Java, crear una conexión socket
TCP/IP se realiza
directamente con el paquete java.net. A continuación
mostramos un diagrama de lo
que ocurre en el lado del cliente y del
servidor:
El modelo de
sockets más simple es:
El servidor establece un puerto y espera durante
un cierto tiempo (timeout segundos), a que el cliente establezca
la conexión. Cuando el cliente solicite una
conexión, el servidor abrirá la conexión
socket con el método accept().
El cliente establece una conexión con la
máquina host a través del puerto que se designe en
puerto# .
El cliente y el servidor se comunican con
manejadores InputStream y OutputStream.
Hay una cuestión al respecto de los
sockets, que viene impuesta por la implementación del
sistema de seguridad de Java. Actualmente, los applets
sólo pueden establecer conexiones con el nodo desde el
cual se transfirió su código. Esto está
implementado en el JDK y en el intérprete de Java de
Netscape. Esto reduce en gran manera la flexibilidad de las
fuentes de
datos disponibles para los applets. El problema si se permite que
un applet se conecte a cualquier máquina de la red, es que
entonces se podrían utilizar los applets para inundar la
red desde un ordenador con un cliente Netscape del que no se
sospecha y sin ninguna posibilidad de rastreo.
13.2 Clases Utiles en
Comunicaciones
Vamos a exponer otras clases que resultan
útiles cuando estamos desarrollando programas de comunicaciones, aparte de las que ya se han visto.
El problema es que la mayoría de estas clases se prestan a
discusión, porque se encuentran bajo el directorio sun.
Esto quiere decir que son implementaciones Solaris y, por tanto,
específicas del Unix Solaris. Además su API no
está garantizada, pudiendo cambiar. Pero, a pesar de todo,
resultan muy interesantes y vamos a comentar un grupo de ellas
solamente que se encuentran en el paquete
sun.net.
Socket
Es el objeto básico en toda comunicación a través de Internet,
bajo el protocolo TCP.
Esta clase proporciona métodos para la entrada/salida a
través de streams que hacen la lectura y
escritura a
través de sockets muy sencilla.
ServerSocket
Es un objeto utilizado en las aplicaciones
servidor para escuchar las peticiones que realicen los clientes
conectados a ese servidor. Este objeto no realiza el servicio, sino
que crea un objeto Socket en función del cliente para
realizar toda la
comunicación a través de
él.
DatagramSocket
La clase de sockets datagrama puede ser utilizada
para implementar datagramas o fiables (sockets UDP), no
ordenados. Aunque la
comunicación por estos sockets es muy rápida
porque no hay que perder tiempo estableciendo la conexión
entre cliente y servidor.
DatagramPacket
Clase que representa un paquete datagrama
conteniendo información de paquete, longitud de paquete,
direcciones Internet y números de
puerto.
MulticastSocket
Clase utilizada para crear una versión
multicast de las clase socket datagrama. Múltiples
clientes/servidores pueden
transmitir a un grupo multicast (un grupo de direcciones IP
compartiendo el mismo número de puerto).
NetworkServer
Una clase creada para implementar métodos y
variables utilizadas en la creación de un servidor
TCP/IP.
NetworkClient
Una clase creada para implementar métodos y
variables utilizadas en la creación de un cliente
TCP/IP.
SocketImpl
Es un Interface que nos permite crearnos nuestro
propio modelo de
comunicación. Tendremos que implementar sus
métodos cuando la usemos. Si vamos a
desarrollar
una aplicación con requerimientos
especiales de comunicaciones, como pueden se la
implementación de un cortafuegos (TCP es un protocolo no
seguro), o
acceder a equipos especiales (como un lector de código de
barras o un GPS diferencial),
necesitaremos nuestra propia clase Socket.
14. ARQUITECTURA
Modelo/Vista/Controlador
La arquitectura MVC (Model/View/Controller) fue
introducida como parte de la versión Smalltalk-80 del
lenguaje de programación Smalltalk. Fue diseñada
para reducir el esfuerzo de programación necesario en la
implementación de sistemas múltiples y
sincronizados de los mismos datos. Sus características
principales son que el Modelo, las Vistas y los Controladores se
tratan como entidades separadas; esto hace que cualquier cambio
producido en el Modelo se refleje automáticamente en cada
una de las Vistas.
Además del programa ejemplo que hemos
presentado al principio y que posteriormente implementaremos,
este modelo de arquitectura se puede emplear en sistemas de
representación gráfica de datos, como se ha citado,
o en sistemas CAD, en donde se presentan partes del diseño
con diferente escala de
aumento, en ventanas separadas.
En la figura siguiente, vemos la arquitectura MVC
en su forma más general. Hay un Modelo, múltiples
Controladores que manipulan ese Modelo, y hay varias Vistas de
los datos del Modelo, que cambian cuando cambia el estado de
ese Modelo.
Este modelo de arquitectura presenta varias
ventajas:
Hay una clara separación entre los
componentes de un programa; lo cual nos permite implementarlos
por separado.
Hay un API muy bien definido; cualquiera que use
el API, podrá reemplazar el Modelo, la Vista o el
Controlador, sin aparente dificultad.
La conexión entre el Modelo y sus Vistas es
dinámica; se produce en tiempo de ejecución, no en
tiempo de compilación.
Al incorporar el modelo de arquitectura MVC a un
diseño, las piezas de un programa se pueden construir por
separado y luego unirlas en tiempo de ejecución. Si uno de
los Componentes, posteriormente, se observa que funciona mal,
puede reemplazarse sin que las otras piezas se vean afectadas.
Este escenario contrasta con la aproximación
monolítica típica de muchos programas Java. Todos
tienen un Frame que contiene todos los elementos, un controlador
de eventos, un montón de cálculos y la
presentación del resultado. Ante esta perspectiva, hacer
un cambio
aquí no es nada trivial.
14.1 Definición de las
partes
El Modelo es el objeto que representa los datos
del programa. Maneja los datos y controla todas sus
transformaciones. El Modelo no tiene conocimiento
específico de los Controladores o de las Vistas, ni
siquiera contiene referencias a ellos. Es el propio sistema el
que tiene encomendada la responsabilidad de mantener enlaces entre el
Modelo y sus Vistas, y notificar a las Vistas cuando cambia el
Modelo.
La Vista es el objeto que maneja la
presentación visual de los datos representados por el
Modelo. Genera una representación visual del Modelo y
muestra los datos al usuario. Interactúa con el Modelo a
través de una referencia al propio
Modelo.
El Controlador es el objeto que proporciona
significado a las ordenes del usuario, actuando sobre los datos
representados por el Modelo. Cuando se realiza algún
cambio, entra
en acción, bien sea por cambios en la información
del Modelo o por alteraciones de la Vista. Interactúa con
el Modelo a través de una referencia al propio
Modelo.
Vamos a mostrar un ejemplo concreto.
Consideremos como tal el sistema descrito en la
introducción a este capítulo, una pieza
geométrica en tres dimensiones, que representamos en la
figura siguiente:
En este caso, la pieza central de la escena en
tres dimensiones es el Modelo. El Modelo es una
descripción matemática
de los vértices y las caras que componen la escena. Los
datos que describen cada vértice o cara pueden modificarse
(quizás como resultado de una acción del usuario, o
una distorsión de la escena, o un algoritmo de
sombreado). Sin embargo, no tiene noción del punto de
vista, método de presentación, perspectiva o fuente
de luz. El Modelo es
una representación pura de los elementos que componen la
escena.
La porción del programa que transforma los
datos dentro del Modelo en una presentación gráfica
es la Vista. La Vista incorpora la visión del Modelo a la
escena; es la representación gráfica de la escena
desde un punto de vista determinado, bajo condiciones de
iluminación determinadas.
El Controlador sabe que puede hacer el Modelo e
implementa el interface de usuario que permite iniciar la
acción. En este ejemplo, un panel de datos de entrada es
lo único que se necesita, para permitir añadir,
modificar o borrar vértices o caras de la
figura.
Autor:
Lucas
email: