DESCARGA PARA ESCUCHAR LA RADIO METRO
Listen with Winamp/iTunes
Listen with Windows Media Player
![]() |
||||||||
Introducción a Java
![]() En cuanto salió Java fue acogido con gran entusiasmo por la comunidad mundial de los diseñadores de programas y de los proveedores de servicios internet. Esto porque Java permitía a los usuarios de Internet utilizar aplicaciones seguras e independientes de la plataforma, y que se pueden encontrar en cualquier punto de la red. Java nació pues como lenguaje para la red, para sostener el Hyper Text Markup Language (HTML), que no es un lenguaje de programación propiamente dicho, y para darle la seguridad que el HTML no tiene. Desde que apareció Java en la red se ha empezado a hablar de números de tarjetas de crédito y de informaciones seguras, lo cual ha entusiasmado a las mayores sociedades mundiales que han transformado la vieja Internet, prerrogativa de las universidades y centros de investigación, en el medio actual de comunicación abierto a todos. El lenguaje de programación Java se creó a mediados de los noventa, es el más reciente entre sus semejantes, y por eso se encuentra en fase de evolución, hasta el punto que todos los años sale una nueva versión. Creado como lenguaje exclusivamente para la red se ha convertido en un auténtico lenguaje de programación paragonable, desde el punto de vista de la funcionalidad, al famoso C++. Java y la mayor parte de los otros lenguajes pueden compararse sólo desde el punto de vista funcional; y esto porque son fundamentalmente diferentes, ya que Java recopila las fuentes de sus programas en un código llamado Bytecode distinto del aparato en el que se ha compilado, mientras que lenguajes como C++ recopilan las fuentes de los programas en un código que es a su vez código del aparato (como aparato se entiende ordenador + sistema operativo) en el que se ha realizado. Por lo tanto, para poner en marcha un programa Java es necesario contar con un instrumento llamado Java Virtual Machine que interpreta el bytcode generado por el recopilador Java y lo ejecuta en el aparato en el que se ha instalado. Gracias al Java Virtual, Java es independiente de la plataforma, es decir, que el programa recopilado Java está unido a la JVM y no al sistema operativo, por eso será posible poner en marcha el mismo programa Java, recopilado una sola vez en un aparato cualquiera con un recopilador Java versión X, en una plataforma Windows y en una plataforma Linux, sin embargo para hacer eso se necesita que Windows y Linux instalen un aparato Java Virtual que apoye la versión X de Java. Las dos JVM, instaladas en las dos plataformas distintas, son el mismo programa recopilado una vez por Windows y otra vez por Linux, como ocurría con los programas escritos en lenguajes como el C/C++. Un aparato Java Virtual está instalado también en los distintos Browser (como Netscape y Explorer) para poder poner en marcha los programas Java que se encuentran en la red, los apliques. Pero esto, debido a que Java sigue evolucionando, provoca obvios problemas de compatibilidad, siempre ocurre que el Browser más moderno contiene una versión precedente de Java con respecto a la última versión de la Sun Microsystem. Además, hay que tener en cuenta que no todos los usuarios de Internet navegan usando la última versión de Netscape o de Explorer. Por eso, cuando queremos crear un aplique e insertarlo en un documento HTML, hay que tener en cuenta estos problemas e intentar diseñar un programa que sea compatible con la mayor parte de los JVM implementados en los distintos browser. Otro problema al que hay que enfrentarse es el de la elección del recopilador Java a utilizar, ya que existen varios ambientes integrados para editar, recopilar, depurar y ejecutar programas Java, como los de la Broland, de la Microsoft, de la Symantec. Todos estos ambientes ofrecen unas herramientas de desarrollo excelentes, como editores gráficos de ventanas, depuradores muy interesantes, pero presentan dos problemas, el primero que valen mucho dinero, el segundo es el de la compatibilidad, ya que muy a menudo se encuentran tras la relase de la Sun y, además, añaden unas clases que los JVM en los browser no tienen. Mi consejo es usar los JDK ( Java Development Kit ) de la Sun, que comprenden tanto el compilador como el aparato Java Virtual Machine, para poner en marcha los programas que recopilamos, además son freeware (no cuestan nada) y se pueden bajar de la red y los browser se ajustan a esta versión de Java. Si queréis escribir apliques para los viejos browser tenéis que bajar la versión 1.1 de Java, pero esta versión no os sirve mucho porque existe otra más cotizada, es decir la 1.2.2 ( se llama, por alguna razón que desconozco Java 2, se puede bajar en la dirección http://java.sun.com/products/jdk/1.2/index.html ), a la que mencionaré yo también en este curso y por la que Netscape versioón 4.0x y Explorer 5 implementaron el Java Virtual Machine (casi todo, yo tuve problemas con las Swing, que son librerías estándar de Java 2). Si queréis bajar Java 2 os aconsejo que lo hagáis entre las nueve y las once de la mañana porque el sitio está muy a menudo colapsado, además el documento que hay que bajar es de unos veinte megas. Si queréis podéis bajar también la documentación pues es muy útil, sin embargo, ésta también pesa otros veinte megas. Yo acabo de bajar e instalar la próxima versión, la Relase Candidate del Java 2 Software Development Kit versión 1.3 Relase Candidate 1, y os aseguro que si la 1.2.2 era extraordinaria ésta es increíble. He descubierto también que ha salido la Relase Candidate 2 del JDK 1.3 y que a finales de Abril saldrá por fin la relase (yo la espero, no tengo ganas de pasarme otra noche en blanco para bajarme algo que será sustituido por la relase dentro de menos de un mes). Para bajaros el producto tendréis que registraros, hacedlo, la registración es gratuita. Un último problema que tiene Java es la lentitud, ya que, como ya hemos señalado, el Java tiene que ser interpretado, así las instrucciones Java antes de ser ejecutadas por el aparato tienen que ser interpretadas por la JVM; es decir, que para ejecutar cada instrucción el ordenador realizará un número de instrucciones que es el doble de las instrucciones que realizaría si la misma instrucción estuviera escrita en C. Por lo tanto, necesitaréis ordenadores rápidos para ejecutar cómodamente los programas Java, y de esto os habréis percatado seguramente navegando por la red. También la memoria es importante, se compila y se ejecuta también sólo con 32MB de RAM, pero para hacer las cosas con rapidez son necesarios al menos 64, tened en cuenta que el último ambiente integrado de la Borland, el Jbuilder 3, tiene como requisito mínimo de RAM 96 Megabyte. Para acabar vuelvo al Java Development Kit de la Sun Microsystem, con éste es posible producir todo el software que se quiera sin tener que pagar los derechos de uso del producto, como sucede con el Borland Jbuilder, el Symantec Cafe, y el Microsoft Visual Java. Os aconsejo que leais la licencia de uso que encontraréis al bajar el JDK antes de empezar a producir el software. Llegados a este punto podemos empezar. La programación Java La principal diferencia entre Java y los demás lenguajes de programación por objetos es que con estos últimos es posible programar por objetos, mientras que con Java se tiene absolutamente que programar por objetos. Por eso es necesario explicar qué quiere decir programar por objetos. En concreto la programación se desarrolla de la misma forma que los lenguajes "normales", pero tanto los datos como las funciones que los trabajan se organizan en estructuras llamadas clases. Las clases son prototipos de objetos, es decir, son estructuras abstractas (no demasiado, como veremos) que se pueden instalar y, por eso, crear un objeto (pero también más de uno). La clase define todas las propiedades de los objetos que pertenecen a aquella clase, llamadas atributos, y las funciones que se usarán para actuar sobre ellos, llamados métodos. Por ejemplo es posible definir una clase de las personas como sigue: Inicio clase personas Atributo añodenacimiento Método calculaedad (añocorriente) Fin clase personas La clase de las personas establecida de esta forma tiene un atributo que es añodenacimiento, que es seguramente un número entero, y un método que, basándose en el año corriente que le pongamos, calcula la edad de la persona. Usando el formalismo de Java, para definir la clase personas tendremos que escribir: clase personas { public ent añodenacimiento; public ent calcula edad ( ent añocorriente ) { envía ( añocorriente - añodenacimiento ); } } Como acabamos de ver explicamos tanto el método cuanto el atributo como public. Veremos ahora lo que significa y veremos también la clase empieza por { y acaba por }, lo mismo pasa con los métodos. Esto nos recuerda mucho al C, y hay que decir que la sintaxis de Java es muy similar, casi igual, a la de C, mientras que para los que no conocen el C, los corchetes representan el empezar y el terminar del lenguaje pascal. La clase tendrá un llamado constructor (uno o más) que es un método peculiar que normalmente se utiliza para inicializar los atributos cuando se establece la clase de un objeto. Es una función que no tiene ningún tipo de return y tiene el mismo nombre que la clase. Decimos que puede haber más de un constructor, sin embargo, el nombre del constructor tiene que ser el mismo que el de la clase. A los que están acostumbrados a programar con lenguajes no orientados a los objetos, todo esto les puede resultar algo raro, sin embargo es posible porque Java realiza el llamado overloading de funciones, es decir funciones con el mismo nombre que tienen parámetros diferentes (en el lenguaje informático se llaman parámetros formales) son distintas, y a la hora de establecerlas se elige la función basándose en el parámetro (llamado parámetro actual). Esto vale para todos los métodos, no sólo para los constructores. clase personas { public ent añodenacimiento; public String Apellidos=new String(); // Constructores public personas(ent añodenacimiento) { this("No sé"); this.añodenacimiento=añodenacimiento; } public personas(String Apellidos) { this(0); this.Apellidos=Apellidos; } public personas(ent añodenacimiento , String Apellidos) { añodenacimiento=añodenacimiento; this.Apellidos=Apellidos; } // Función que calcula la edad del sujeto; public int calculaedad ( ent añocorriente ) { return ( añocorriente - añodenacimiento ); } } Las líneas que empiezan por // son comentarios, el ordenador los ignora, pero hay otros dos tipos de comentarios, los que se ponen entre /* y / que permiten definir comentarios sobre más de una línea. Se llaman comentarios de documentación y se tienen que encontrar antes de la declaración de las clases, de los miembros de las clases (atributos o métodos) o de los constructores, y hay que incluírlos en la posible documentación del código que se genera automáticamente. En el ejemplo vemos que hay tres constructores, distintos por parámetros formales, que tienen el mismo nombre. Además vemos un nuevo atributo que son los Apellidos, ésa es una cadena, que se define como public String Apellidos=new String(); la parte que está delante del signo igual resulta clara, la parte que se encuentra a la derecha del mismo signo un poco menos, ese new String() crea un nuevo objeto de la clase String y llama a su vez al constructor que no tiene parámetros. Esto es el procedimiento tipo que usa Java para crear los objetos de una clase. No hay que sorprenderse de que el tipo de dato cadena sea una clase, porque con Java es posible usar objetos que representen todos los tipos de datos del lenguaje, insertadoos para completar el lenguaje, y llamados confecciones que a veces resultan muy útiles. Si embargo, es posible usar también los valores. Así, por ejemplo, trabajaremos tanto con enteros como con objetos que representan enteros. Por último, resulta bastante evidente en el ejemplo que los constructores tienen voluntariamente unos parámetros que tienen a su vez el mismo nombre de los atributos. Esto también es posible en Java y quiere decir que cuando hay un valor a la izquierda del signo igual tiene que encontrase el atributo, y a la derecha el parámetro. De todos modos si no queremos confundirnos podemos usar la referencia this; escribiendo, por ejemplo, this.añodenacimiento, esto quiere decir atributo. This se refiere al objeto, y en el ejemplo anterior lo encontramos también como llamada a la función this(0), en este caso se refiere a un constructor del objeto y se llamará constructor personas (ent añodenacimiento), con el valor 0. Es por tanto posible en un constructor llamar a un constructor distinto de la misma clase, con tal que la llamada sea la primera instrucción del constructor y que el constructor sea diferente del actual. Llegados a este punto, estamos listos para crear objetos que pertenezcan a la clase que acabamos de definir. Tenemos tres formas de hacerlo porque hemos creado tres constructores que son: personas Pietro=nuevas personas(1974); o personas Pietro=nuevas personas("Castellucci"); o personas Pietro=nuevas personas(1974,"Castellucci"); ahora queremos crear otro objeto de la clase personas personas Lina=nuevas personas(1975); o personas Lina=nuevas personas("Marucci"); o personas Lina=nuevas personas(1975,"Marucci"); a este punto he creado dos objetos de la clase personas que tienen que ver con la llamada clase inst_of, los objetos se llaman uno Pietro y el otro Lina. También es posible copiar las referencias de los objetos, por ejemplo es posible escribir: personas Pietro2=Pietro; Construidos los objetos puedo crear los métodos, indicando Objeto.Método. Por ejemplo es posible crear los métodos: Pietro.calculaedad(2000); Pietro2.calculaedad(2000); Lina.calculaedad(2000); Ahora introduzcamos unos atributos y unos métodos particulares, los llamados miembros estáticos. Por como hemos definido los miembros de la clase, no nos resulta posible hacer una referencia directa de la clase atributos y métodos ( personas.añodenacimiento es un error), y esto porque estos trabajan sobre una instancia de la clase, es decir sobre un objeto, pero a veces puede ser útil escribir métodos y atributos que se puedan crear sin tener que llamar al objeto, y que puedan ser llamados directamente desde la clase. Para hacer esto necesitamos declararlos estáticos, por ejemplo: clase TítulosEmpresaEquis { public static int TipodeInterés=3; public String Propietario=new String(); public static float InteresesDevengados (int Período) { return((Período * TipodeInterés )/100) } TítulosEmpresaExis(String nombre) { Propietario=nombre; } } Entonces podremos decidir, por ejemplo, llamar a un objeto de la clase TítulosEmpresaExis sólo si nos conviene, por ejemplo haciendo: if (TítulosEmpresaEquis.InteresesDevengados(12)>1000) CompraAcciones(10000); Donde CompraAcciones (int X) es una función que llama X TítulosEmpresaEquis. Introduzcamos ahora la relación is_a entre clases. Establecida una clase, es posible crear una nueva clase partiendo de ésta haciendo, como se dice en nuestra jerga, una especialización de la primera clase. La nueva clase que creamos está relacionada is_a con la primera. Creada una clase estudiante de la clase (llamada superclase) personas, la nueva clase hereda de la primera todos los métodos y los atributos, con la posibilidad de definir unos atributos y unos métodos nuevos o de volver a definir otros. En Java, la estensión de una clase se manifiesta con la palabra clave extends. clase estudiante extends personas { ent inscripción; // Constructores public estudiante(ent añodenacimiento) { super(añodenacimiento,"No Conocido"); } public estudiante (String Apellidos) { super(0,Apellidos); } public estudiante(int añodenacimiento , String Apellidos) { super(añodenacimiento,Apellidos); } } Como vemos en el ejemplo, la clase estudiante hereda todos los métodos y los atributos de la clase personas, define un nuevo atributo inscripción, y en sus constructores llama a los constructores de la clase personas con el nombre de super.(). Super() puede ser, como this(), una llamada de otro constructor (entre parántesis van los parámetros posibles) o una referencia a la clase (a la superclase en este caso). Entonces, super.añodenacimiento representa el atributo añodenacimiento de la superclase personas. Las relaciones is_a y inst_of son las dos relaciones más importantes de los modelos por objetos. Ahora, en conclusión, ponemos un ejemplo que comentaremos a continuación para explicar el significado del public que introducimos anteriormente, y del private y protected (los atributos y los métodos pueden llamarse public, private y protected). clase A { public int a; private int b; protected int c; // Sus métodos, atributos y constructores } clase B extends A { public float a; private float b; protected float c; // Sus métodos, atributos y constructores } clase C { // Sus métodos, atributos y constructores } Hemos definido tres clases, A,B y C, B se define partiendo de A volviendo a definir los tres atributos, de enteros a reales. En A, en sus constructores y en sus métodos, tenemos aceso a los mismos atributos de tipo entero, sin limitaciones, es decir, podemos escribirlos y leerlos a nuestro antojo. En B y C pasa lo mismo con los propios atributos, pero vamos a ver lo que sucede para los de las demás clases. Por ejemplo, en B (en uno de sus métodos) si escribimos expresiones con a,b y c, escribiremos unas expresiones pera unos float (en Java es muy importante el tipo de las expresiones). Sin embargo, para referirnos a los atributos homónimos de A de la que se ha heredado, tenemos que escribir, como sabemos, super.a, super.b y super.c (que son unos enteros). Nuestro compilador Java no dará problemas para las primeras dos, pero nos dará error para el tercero. Esto porque el c de A está protegido (protected) que quiere decir que es posible leer y escribir aquel atributo sólo internamente a la calse a la que pertenece, pero no es posible leerlo y escribirlo de clases ajenas o de subclases. Llegados a este punto metámonos en un método de C, establezcamos un objeto de la clase B (lo llamamos b) y escribamos las expresiones b.a, b.b y b.c. Nuestro simpático compilador nos dará sólo la primera porque la tercera está protegida y, por eso, sólo es visible en la clase a la que pertenece. La segunda es privada (private) y esto significa que es visible sólo para la clase a la que pertenece y para sus subclases, y C no es una subclase de B. Anteriormente hicimos una trampa, es decir, hablamos de atributos de clases y de objetos como si fueran la misma cosa, pero realmente lo son. Sólo hay una pequeña diferencia. Si entramos en un atributo de una clase ( NOBRECLASE.NOMBREATRIBUTO ) este será seguramente estático, y podemos entrar sólo para leer, es prácticamente una constante. Cuando estabezcamos la clase en un objeto (NOMBRECLASE NOMBREOBJETO= new NOMBRECLASE (parámetros); ), entrando en el mismo atributo del objeto ( NOMBREOBJETO.NOMBREATRIBUTO ) entramos en el mismo valor de antes y no podemos modificarlo (es static). En cambio, si intentamos entrar en un atributo no estático de una clase, tendremos un error. Realmente este atributo se creará en el mismo momento de la creación del objeto de la clase, y la mayoría de las veces será inicializado por el constructor del objeto. Espero haber tratado el argumento con claridad, pero hay que decir que los modelos por objetos son bastantes complicados y es imposible explicarlos en detalle en un solo párrafo, se necesitaría un entero curso. Obviamente lo que acabamos de decir no es todo sobre los modelos por objetos. Simplemente introducimos unos conceptos que nos valdrán para hacer nuestros programas en Java. Los puntos importantes que hemos olvidado tratar en este apartado, serán introducidos más adelante cuando se presente la ocasión. Al lector que quiera profundizar el argumento no le puedo aconsejar ningún libro en concreto porque hay varios sobre este tema y todos son buenos. Sin embargo, si os conformáis con una introducción al tema, podéis mirar los capítulos iniciales de cada buen manual de programación para principiantes de Lenguajes por objetos (C++ o Java), como, por ejemplo, Java: Didáctica y Programación de K.Arnold e J.Gosling (Ed. Addison-Wesley) Capítulos 2,3 y 4. Os pido sobre todo que sea un manual para principiantes porque en los libros de programación avanzada el tema se da por descontado. Qué son los paquetes informáticos de Java Los paquetes informáticos son colecciones de clases, contenidas en una colección que las une. Prácticamente son bibliotecas a las que el usuario puede acceder y que ofrecen varias funciones. Los usuarios pueden también crear paquetes informáticos, por ejemplo, haciendo que contengan todas las clases que ha definido para poner en marcha algunas funciones que luego usará en varios programas. Sin embargo, esto no nos interesa porque lo que queremos es ver los paquetes informáticos más interesantes en Java. Esta es la auténtica potencia de Java, el número de clases ya establecidas que realizan las más variadas tareas, y es también la parte que sigue creciendo más y más, y que sigue poniendose al día con las nuevas versiones de Java (JDK 1.3 bien contiene 18Mb). Los paquetes informáticos de Java son tantos que sólo describiré algunos por encima en un capítulo entero (el capítulo 3) del curso. Veremos de forma detallada más o menos dos, es decir, los que nos sirven para definir interfaces gráficas y apliques. Obviamente todos los paquetes informáticos del lenguaje están descritos en la mastodóntica guía en línea del JDK que se puede bajar junto al paquete del compilador (La JDK Documentation 1.3 beta es de 105 Megabyte, comprimida es de unos treinta Megas). Ésta tambien es gratuita y, si os apetece pasar un par de horas bajándola, os aconsejo que la cojáis junto al compilador. De todas formas está a vuestra disposición en línea (tenéis el enlace en la página www.javasoft.com o www.java.sun.com , es prácticamente igual, son primos hermanos). El núcleo del lenguaje Java contiene sólo las palabras claves para la construcción de las clases, los comentarios, las construcciones normales en if, switch, while, do-while, for, etiquetas, break, continue e return (falta el goto), que veremos en detalle en el próximo capítulo. Todo lo demás está contenidos en los paquetes informáticos del lenguaje, incluidas las normales primitivas de Entrada y de Salida. En este párrafo veremos la lista de los paquetes informáticos de Java y veremos uno en particular, el que nos interesa para escribir nuestra primera aplicación Java aún antes de haber visto las construcciones, es decir, en las que podemos encontrar las instrucciones de entrada y salida. La lista completa de los paquetes informáticos de Java 1.2.1 en orden alfabético es el siguiente: com.sun.image.codec.jpeg, com.sun.java.swing.plaf.windows,com.sun.java.swing.plaf.motif, java.applet, java.awt, java.awt.color, java.awt.datatransfer, java.awt.dnd, java.awt.event, java.awt.font, java.awt.geom, java.awt.im, java.awt.image, java.awt.image.renderable, java.awt.print, java.beans, java.beans.beancontext, java.io, java.lang, java.lang.ref, java.lang.reflect, java.math, java.net, java.rmi, java.rmi.activation, java.rmi.dgc, java.rmi.registry, java.rmi.server, java.security, java.security.acl, java.security.cert, java.security.interfaces, java.security.spec, java.sql, java.text, java.util, java.util.jar, java.util.zip, javax.accessibility, javax.swing, javax.swing.border, javax.swing.colorchooser, javax.swing.event, javax.swing.filechooser, javax.swing.plaf, javax.swing.plaf.basic, javax.swing.plaf.metal, javax.swing.plaf.multi, javax.swing.table, javax.swing.text, javax.swing.text.html, javax.swing.text.html.parser, javax.swing.text.rtf, javax.swing.tree, javax.swing.undo, org.omg.CORBA, org.omg.CORBA.DynAnyPackage, org.omg.CORBA.ORBPackage, org.omg.CORBA.portable, org.omg.CORBA.TypeCodePackage, org.omg.CosNaming, org.omg.CosNaming.NamingContextPackage, sun.tools.ttydebug, sunw.io, sunw.util. Realmente parecen pocos, parece que haya dicho una tontería cuando me refería a la potencia de Java, sin embargo si pensáis que sólo el paquete informático Java.io comprende 50 clases y 10 interfaces, comprenderéis que los de antes son una colección de clases consistente. Llegados a este punto nos gustaría hacer la entrada y la salida de consolle y para hacerlo debemos usar el paquete informático java.lang . para usar un paquete informático en una de nuestras clases, antes de definir la clase, tenemos que introducir la instrucción import. Por ejemplo, si queremos usar el paquete informático java.awt tenemos que introducir al comienzo de nuestro archivo: import java.awt.*; El * indica que queremos usar todas las clases, en cambio si queremos usar sólo una clase lo podemos especificar, por ejemplo, import java.awt.Frame; podremos usar la clase Frame del awt. En nuestro caso, en el que queremos hacer una operación de entrada, tendremos que declarar al empezar import java.lang.*; o, sabiendo que la clase del paquete informático java.lang que contiene los métodos para hacerlo es System, podremos escribir import java.lang.System; La clase System, a su vez, contendrá en su interior una import java.io (que es el paquete informático para la entrada y la salida) para acceder a las clases de la entrada y de la salida, y las usará para mandar lo que queramos en la pantalla. Vimos un ejemplo de paquete informático que en su interior llama a otro paquete informático para que lleve a cabo unas tareas. En estas bibliotecas Java ocurre eso a menudo y es precisamente este fenómeno que hace de Java un lenguaje incomprensible para la mayoría. Sin embargo, superado este inconveniente que se refiere a este aspecto del lenguaje, la programación se hace fácil e inmediata. He introducido el paquete informático java.lang. Ahora os digo también que este es el más importante de Java, en éste están contenidos las clases fundamentales del lenguaje, hasta el punto de que no hace falta declarara el import, porque Java lo introduce automáticamente. Además me gustaría detenerme en un aspecto fundamental de la introducción de los paquetes informáticos. Si importo en mi archivo el paquete informático java.lang, éste importará a su vez el paquete informático java.io, y yo desde mi archivo no podré usar las clases de java.io; para hacerlo tengo que importarlo explícitamente. Esto sucede aunque programe una aplicación en más de un archivo (con más clases), en cada archivo tengo que importar los paquetes informáticos que necesito para la clase que estoy estableciendo, no basta con importarlos en una sola. Veamos pues las clases que contiene este paquete informático java.lang tan importante para el lenguaje: Boolean hemos dicho que los tipos de datos se pueden representar también con objetos dichos; contenedores es una clase que genera los contenedores para los tipos de datos boolean (verdadero, falso). Byte es la clase que genera los contenedores para los tipos de datos enteros cortos (de un byte). Character es una clase que genera los contenedores para los tipos de datos caracteres. Character.Subset es la clase que genera los contenedores para los tipos de datos caracteres con codificación unicode. Character.UnicodeBlock es la clase que genera los contenedores para los tipos de caracteres con codificación unicode 2.0. Class representa las clases y las interfaces a runtime (en ejecución), en Java es posible, en tiempo de ejecución, crear, ejecutar y compilar clases. El compilador y el ejecutor se mezclan de una forma rara, sin embargo a nosostros no nos sirven estas clases "especiales" porque escribiremos unas aplicaciones fáciles. ClassLoader cargador de clases a tiempo de ejecución. Compiler compilador de clases a tiempo de ejecución. Double es la clase que genera los contenedores para los tipos de datos reales en coma móvil de 64 bit. Float es la clase que genera los contenedores para los tipos de datos reales de 32 bit en coma móvil. Integer es la clase que genera los contenedores para los tipos de datos enteros. Long es la clase que genera los contenedores para los tipos de datos enteros dobles. Math contiene métodos que simulan funciones matemáticas. Number clase de objetos contenedores de números genéricos. Object es la clase de la que derivan todas las clases del lenguaje Java, es decir, cada clase es subclase (no necesariamente directa) de ésta. Package contiene métodos para extrapolar informaciones en los paquetes informáticos de Java. Process Java es un lenguaje que permite gestionar Thread, es decir pequeños programas que corren en paralelo. Esta clase se ocupa de ellos, como las demás clases de java.lang: Runtime, RuntimePermission, Thread, ThreadGroup, ThreadLocal, Throwable SecurityManager para la seguridad Short es la clase que genera los contenedores para los tipos de datos enteros cortos. String es la clase que genera los contenedores para los tipos de datos cadenas de caracteres. StringBuffer es la clase que genera los contenedores para los tipos de datos cadenas de caracteres modificables. System es la clase que interactúa con el sistema y contiene muchos métodos útiles, y unas referencias a otras clases (llamadas class field), es la que usaremos, por ejemplo, para crear una salida sobre la pantalla a caracteres. Void es la clase que genera los contenedores para los tipos de datos void, es decir, sin tipo. Éste parece un tipo de dato inútil, sin embargo veremos que no lo es en absoluto, además es utilísimo. En realidad, es útil cuando queremos definir un método-procedimiento (un método que no tiene valor de vuelta), en Java existen sólo dos funciones que obligan a devolver un valor del tipo que declaran. Declarando un método como void, se simula el procedimiento, es decir, no se necesita la vuelta. Escritura de apliques "a consola" Me doy cuenta de que toda la parte descriptiva hasta ahora pudo resultar un poco aburrida y estoy de acuerdo con vosotros pero, estoy seguro de que me váis a disculpar porque los conceptos introducidos son FUNDAMENTALES para el apartado menos aburrido que está a punto de empezar, es decir, la programación. Sin embargo, antes de empezar tengo que decir una última cosa: con los actuales lenguajes de programación es posible hacer dos tipos de programas, excluidas las bibliotecas dinámicas (.DLL) o estáticas (.LIB) y varias, es decir, las aplicaciones a consola y las aplicaciones a ventanas. Las primeras son las más simples de construir porque no tienen gráfica y no tienen interfaz de caracteres: son las que se parecen a las viejas aplicaciones DOS, o javac.exe mismo ( el compilador Java ). Las segundas son las Windows o X-Windows, con todos los botones, menús, etc., más complicadas de hacer, pero extremadamente más agradabledes de ver. Con Java es posible hacer, además de éstas dos, una tercera aplicación, la que trabaja en Red, es decir el aplique. Al principio Java nació sólo como un lenguaje para la Red, pero sucesivamente se vió que era un lenguaje completo con el que era fácil escribir también aplicaciones a consola y a ventanas. Yo también estoy preparando mi tesis de licenciatura en informática usando Java y es una aplicación a ventanas. Las primeras aplicaciones que vamos a ver son, por cierto, las más simples, aunque las menos sorprendentes, es decir, las aplicaciones a consola. Una aplicación puede estar compuesta por una o más clases, todas incluidas en homónimos archivos, simplemente por una cuestión de simplicidad de búsqueda, aunque es posible definir más de una en un archivo y llamar los archivos con nombres distintos de las clases que se encuentran en ellos. Pero, si escribimos una aplicación a consola o una a ventanas, hay una clase especial que un archivo del mismo nombre tiene que contener. Esta clase que llamaré, por ejemplo, ´´hola´´ tendrá un método, que se llama main, que representa el punto de entrada del programa, es decir, es el punto desde el que se iniciará la ejecución del programa. Cuando un programa en Java se redacta, dará uno o más archivos .class, entre ellos, en nuestro caso, estará también nuestro hola.class. Para poner en marcha el programa tendremos que teclear: java hola [ENVÍO] Hay que notar que los viables Java no son .EXE, sino .class, esto porque para que se pongan en marcha necesitan la ya citada Java Virtual Machine que es, precisamente, el programa java.exe. Pongamos un ejemplo: class hola { public static void main(String[] args) { // Comentario, este programa no hace nada. } } Vemos que main se declara static void, static porque se acude a él al principio de la JVM, antes de que ´´hola´´ se ponga automáticamente en un objeto, void porque no devuelve nada. Esta clase la escribiré en un archivo que llamamos hola.java y la redactaré escribiendo javac ciao.java NB: Es absolutamente obligatorio llamar a los archivos fuentes de Java con la extensión .java, sino el compilador no los reconoce. Además si creé hola.java TENGO que teclear javac hola.java y no javac hola para compilarlo. Como parámetro el método main tiene una cadena que se llama args que representa los parámetros con los que se pone en marcha el programa, por ejemplo, si escribo, después de redactarlo, hola en hola.class java hola Pietro Castellucci args[0] será igual a Pietro args[1] será igual a Castellucci Esto cuando se pone en marcha. Hay que notar también que la JVM java.exe pone en marcha hola.class especificando simplemente java ciao. A diferencia del compilador javac.exe, si tecleamos java hola.class nos da error, si queréis podéis intentarlo. Nuestro primer programa en Java Lo que estamos a punto de hacer, no es realmente nuestro primer programa en Java: algunos los hicimos precedentemente y éste es el primer programa que Java puede trabajar. Vamos a usar lo que vimos en el precedente párrafo para crear una aplicación a consola y las del párrafo anterior para escribir algo en la pantalla. Empezamos por editar un archivo que llamaremos HolaMundo.java: cuidado con las mayúsculas y las minúsculas porque en Java son muy importantes, no sólo para el programa, sino también para los archivos. Además espero que tengáis un editor que apoye Java porque, si editáis con el edit de DOS, no tendréis ninguna ayuda a la hora de escribir los programas. En cambio, con el editor especializado tendréis las indentificaciones hechas ad hoc, distintos colores para las palabras claves, para los comentarios, etc.… Podéis bajar gratuittamente unos editores creados precisamente para Java o adaptados a Java en red. Podéis buscar en el sitio http://omega.di.unipi.it de la facultad de Informática de Pisa, en Resources - ftp, o en los varios sitios de la tucows ( www.tucows.com ), en el que encontraréis el bluette (una versión demo) que tiene un buen editor. Simplemente os recomiendo que tengáis cuidado porque en éste caso el bluette es un compilador Java, pero no sé si es totalmente compatible con el Java de la SDK que estoy tratando yo. En nuestro archivo HolaMundo.java escribiremos: class HolaMundo { public static void main(String[] args) { System.out.print ("Hola mundo, soy el primer programa en Java"); System.out.println ("di "+args[0]+" "+args[1]); } } Redactamos el programa escribiendo, como siempre, java HolaMundo.java y ahora podemos ponerlo en marcha. Escribiremos: java HolaMundo TUNOMBRE TUSAPELLIDOS y veremos una salida parecida a ésta: Hola mundo, soy el primer programa en Java de TUNOMBRE TUS APELLIDOS Si no ponemos el nombre y los apellidos, el programa dará una excepción, que es parecido a un error a runtime, debido a que en el main se citan arg[0] y arg[1] que no aparecen. estas excepciones sí son errores a runtime, pero se pueden prever y gestionar precisamente porque Java es un lenguaje "seguro" y, más que interrumpirse y dar el normal RUNTIME ERROR que dan los demás lenguajes, da una excepción que, si prevista por el programador, no interrumpe el programa. En el siguiente capítulo veremos como gestionar posibles excepciones y, también, como utilizarlas para nuestros fines. Pensemos, por ejemplo, en un programa que lee de los archivos y que no encuentra este archivo: Java dará su excepción, nosotros la leemos y haremos que lea otro archivo. La versión sin riesgos de excepciones y más espectacular es la siguiente: class HolaMundo2 { public static void main(String[] args) { System.out.println ("*************************************************"); System.out.println ("** Hola mundo, soy el primer programa en Java **"); System.out.println ("*************************************************"); System.out.println (" |||||"); System.out.println ("0/ x x "); System.out.println (" | o |"); System.out.println (" |___/|"); System.out.println (" |_____|"); } } Llegados a este punto, sólo me queda felicitaros: acabáis de escribir vuestro primer programa en Java. Si os parece poco, no os procupéis: a partir del captítulo sucesivo veremos las construcciones y entonces nos desahogaremos escribiendo todos los programas que queramos. Tipos primitivos de Java y valores Llamamos tipos primitivos de un lenguaje cuando hablamos de tipos de datos ya definidos en el lenguaje y de los que se puede partir para la construcción de expresiones o tipos de compuestos. Los tipos son muy importantes en todos estos lenguajes de programación y son fundamentales en Java que es un lenguaje que se basa mucho en los tipos. El concepto de tipo es muy natural: si veo un número, por ejemplo, 15.4 puedo decir en seguida el conjunto de números al que pertenece, en este caso al conjunto de los números reales. Si veo una operación 4 / 5 puedo decir: 4 es de entero entero 5 es de tipo entero y / es una función de dos enteros y que me devuelve un número real. Se escribe así: / : ent x ent à float Entonces, puedo decir que toda la expresión 4/5 es de tipo entero. Lo que acabamos de hacer es, más o menos, una inferencia de tipos que es una práctica muy utilizada en informática. Para explicarla toda se necesitaría, como con los modelos por objetos, un curso entero. Los problemas no surgen con estas expresiones, que se llaman cerradas, que no tienen variables, sino con las que tienen variables. Cada lenguaje emplea una técnica suya: algunas usan los tipos para las expresiones, otras establecen el tipo partiendo del contexto de la expresión y da un tipo en secuencia incluso por cada una de las expresiones, y los que, como Java, obligan a declarar todos los tipos de todas las variables desde el principio. Los tipos primitivos de Java son los mismos de los demás lenguajes de programación, sólo que difieren un poco en los valores y son. boolean es decir, valores que pueden ser verdaderos o falsos char los caracteres son de 16 bit y están codificados en Unicode 1.1.5; en los demás lenguajes son ASCII solamente de 8 bit. byte enteros de 8 bit con signo, es decir, número entre menos (dos a la séptima) y dos a la octava. short enteros de 16 bit con signo. int enteros de 32 bit con signo. long enteros de 64 bit con signo. float reales de 32 bit con coma móvil (IEEE 754-1985). double reales de 32 bit con coma móvil (IEEE 754-1985). Obviamente, a partir de éstos es posible definir nuevos tipos de datos que se llaman compuestos. Por ejemplo, una cadena es un vector de caracteres y es un tipo compuesto. Como vimos el la lección precedente, en el paquete java.lang están los contenedores para estos tipos de base, es decir, que si escribo ent, me refiero a un número entero, mientras que si escribo Enteger me refiero a las clases enteros. Si uso las clases, tendré también una serie de atributos y métodos útiles para trabajar con ellos. Por ejemplo, en alguans se definen las constantes MIN_VALUE y MAX_VALUE (Mínimo y máximo valor), en las clases Float y Double encontraremos las constantes NEGATIVE_INFINITY y POSITIVE_INFINITY, NaN para indicar un valor que no es válido y el método isNan() para controlar si un valor es válido. Por ejemplo, se puede utilizar después de una división para controlar que no se haya hecho una división por cero que resultaría catastrófica. Todo esto no existe en los demás lenguajes de programación. Ahora vamos a ver los literales de cada tipo de Java, es decir los valores constantes que cada tipo puede tener. El único literal para referirnos a los objetos es null, se puede utilizar cada vez que nos esperamos una referencia a un objeto, null es un objeto que no se ha creado y no es válido: no es de ningún tipo. Los literales booleani son verdaderos y falsos e indican, respectivamente, el valor cero y el valor falso. Los literales enteros son cadenas de cifras octales, decimales o exadecimales. El inicio de la constante sirve para escribir la base del número: un cero inicial indica base ocho, 0x o 0X indica base 16 y nada indica los decimales. Por ejemplo, el número 15 en base diez puede representarse como: 15 como decimal 017 como octal 0xf o 0XF como esadecimal. Las constantes enteras que terminan por l o L se consideran long. Es mejor que terminen por L porque l se confunde fácilmente con la unidad (no se equivoca el compilador, pero sí nosostros que leemos los programas) y si no acaban por una letra se consideran como ent. Si un leteral ent se escribe como un short o como un byte, éste se porta como si lo fuera. Los literales con coma móvil se escriben como números decimales, con un punto opcional seguido, posiblemente, por un exponente que empieza por e, según la práctica corriente para indicar mantisa-exponente. El número puede llevar detrás una f (F) o una d (D) para indicar que es, con precisión, simple o doble, para default es doble. 35, 35. , 3.5y1, .35y2 representan el mismo número. El cero puede ser positivo o negativo, si los comparamos obtenemos la equivalencia. Sin embargo, es útil en los cálculos, pensemos en 1d/0d y 1d/-0d. No es posible conceder una constante doble a una float aunque esté en la serie. Si queremos hacer esta operación, hay que hacer un cambio que se llama casting que analizaremos a continuación. Los caracteres se ponen entre comilla como, por ejemplo, '2', y los caracteres especiales están representados por unas secuencias, llamadas secuencias de escape. Son: n newline, a parte t tab b backspace, borra a la izquierda r return, representa el carácter especial ENVIO f form feed es el carácter backslash ' comilla " comillas ddd es un char que emplea el valor octal (d son cifras octales, por ejemplo 329, tienen que ser de tres o menos cifras y menores de 377), se puede dar también la representación exadecimal, siempre de cuatro cifras. Aquí ponemos en orden los caracteres citados antes con su número exadecimal: u000A, u0009, u0008, u000D, u000C, u005C, u0027 y u0022. Las cadenas se escriben entre comillas, como por ejemplo "Pietro" y todas las secuencias de escape válidas se pueden introducir en una larga cadena, creando cosas interesantes como, por ejemplo: System.out.println("tNombre:tPietrontApellido:tCastellucci" ); dará este resultado: Nombre: Reinaldo Apellido: Huarayo Variables Las variables son valores modificables, es decir, son nombres que representan un valor de cierto tipo y el valor asociado al nombre se puede variar. Por ejemplo, si digo que X es una variable de tipo entero y después digo que tiene valor 10, escribiendo la expresión 5 + X es como si escribiera la expresión 5 + 10. De todas formas creo que todos vosotros tenéis familiaridad con las variables porque son lo principal de cada lenguaje de programación. Dicho esto, vamos a ver como Java trata las variables. En primer lugar, antes de utilizarla, una variable se tiene que escribir. En Java la escritura de una variable está compuesta por tres partes: modificadores, tipo e indentificadores. Los modificadores son una opción y son los que establecen el acceso a las variables, es decir, a los public particulares y protegidos que vimos en el capítulo anterior. Éstos se llaman modificadores de acceso y los hay estáticos, sincronizados y finales. No es importante ponerlos en órden, aunque sí es importante utilizar siempre el mismo órden para que sea legible el código. Estátic también lo vimos: nos permite escribir atributos y métodos constantes que se pueden invocar incluso antes de que un objeto se escriba. Final, en cambio, quiere decir que, cuando se utiliza un valor, éste se puede unir sólo una vez a un campo porque es también una constante. El modificador sincronizado lo dejaremos de lado: basta con indicar que sirve para decir que a un método, en el caso de una multiprogramación (con los thread), se puede acceder a través de una exclusión múltiple, es decir, un thread a la vez. Los identificadores pueden ser uno o más, están separados por una coma y son nombres que tienen variables. Por ejemplo, la escritura de X en el ejemplo anterior se convertirá en: ent X; pero, también puedo escribir más de uno. Por ejemplo, la siguiente escritura se considera válida: ent X,Y,Z; doble W=3.12; Como son atributos de las clases tienen que escribirse dentro de los mismos; escribirlos fuera de ellos se considera un error. Como se puede ver en el ejemplo, también es posible inicializar una variable en el momento en el que la creamos: símplemente hay que añadir, después del nombre, el signo igual y el valor que toma. Después de la escritura de la variable y su inicialización, el identificador de la variable, en cualquier lugar que se haya escrito, se entiende como su valor actual, excepto si le damos un nuevo valor. Por ejemplo, si escribo X = 10 me refiero a la variable X y no a su valor, y con el signo igual 10 me refiero a que el nuevo valor de la variable es 10. Si después escribo: X = X+1; con la X a la izquierda del signo igual me refiero a que la variable se llama X, mientras que con la X a la derecha me refiero al valor actual de la variable X. La expresión X+1, entonces, en este caso será 10 + 1 y entonces 11, es decir por X = X + 1, he dicho X = 11; Provad este pequeño programa que hace unos calculos: class p { ent X,Y,Z; double W = 3.12; public double A = 15; static int B = 101; private final int C = 2; protected static boolean D = true; public p() { X= 10 ; Y= X ; Z= X + Y ; System.out.println ("Al'principio tengo: X="+X+", Y="+Y+", Z="+Z); X= X + 1 ; Y= Z - X; System.out.println ("Hago las operaciones: nX= X + 1 ;nY= Z - X;ned resultado:"); System.out.println ("X="+X+", Y="+Y+", Z="+Z); } } class decl { public static void main(String[] a) { p Prueba=new p(); } } Como sabéis, visto que decl es la clase que incluye el main, tendréis que editar el programa en un archivo que se llama decl.java; después, ponedlo en marcha y a ver lo que ocurre. Hay también otros atributos para mostrar el uso de los modificadores. En el ejemplo vemos que yo creé una clase p y le puse el objeto Prueba, llamando al constructor. Hice esto porque las variables son atributos y, por eso, si no son estáticos, son atributos de un objeto. Tuve que crear un objeto para poder trabajar con sus variables. Hay que acostumbrarse a programar desde esta perspectiva porque los programas Java son todos así. Como el main del programa TIENE que declararse static (y tiene que tener una cadena como parámetro), éste se pone en marcha en ausencia del objeto y, por eso, algunas variables no son estáticas. Si hubiera querido crear solo una clase, hubiese tenido que escribir algo parecido a: class decl2 { int X,Y,Z; // Main public static void main(String [] a) { decl2 Prueba=new decl2(); } // Constructor public decl2() { X= 10 ; Y= X ; Z= X + Y ; System.out.println ("Al'inicio tengo: X="+X+", Y="+Y+", Z="+Z); X= X + 1 ; Y= Z - X; System.out.println ("Hago las operaciones: nX= X + 1 ;nY= Z - X;ned resultado:"); System.out.println ("X="+X+", Y="+Y+", Z="+Z); } } En este segundo ejemplo, editado y redactado en decl2.java, no se entiende bien cuándo vemos decl2 como clase y cuándo como objeto. Realmente el main está en la clase y en él se crea el objeto. Por tanto se invoca el constructor y dentro de él razonamos en términos de objeto creado. De todas formas, es posible y veréis que el programa funciona también en este caso. Algún día, cuando tengáis más familiaridad con el lenguaje, podréis hacerlo vosostros también. De momento no os lo aconsejo porque es fundamental, llegados a este punto, distinguir bien entre clases y objeto, y esto se consigue fácilmente con mucha experiencia. Los identificadores Java tienen que empezar por una letra (o con _ o $) seguida por una secuencia larga de caracteres y cifras elegidas a vuestro antojo. Las cifras, codificadas en Unicode, son mucho más numerosas que las letras y que las cifras ASCII utilizadas por otros lenguajes. Lo siento, pero no tengo un mapa completo de los caracteres unicode que dejaros, pero puedo deciros que están incluidos los caracteres griegos, cirílicos, árabes, etc. Ahora vamos a ver cómo escribir e inicializar un array. La escritura se hace poniéndo un par de corchetes después del tipo: ent [] vectorDeEnteros; Los array en Java son casi objetos, sólo que no pueden extenderse para añadirles nuevos métodos. Lo que acabamos de citar es una referencia a un array que, sin embargo, rl lenguaje no ha asignado. Si quiero inicializar el array elemento por elemento, como se hace con los demás lenguajes, tengo que escribir las dimensiones: ent [] vectorDeEnteros = nuevo ent[100]; A partir de ahora puedo empezar a poner los elementos en el vector, en las posiciones de 0 a 99. Si salgo de estos números, el aparato nos dará unmensaje de excepción del tipo out of range. Cuando creo, puedo además inicializar un array, basta con escribir: ent [] vectorDeEnteros = { 1,2,3,10,100,555,0, ….otros valores…}; Además puedo crear unos vectores cuyos elementos son ellos mismos vectores, etc. Esto se consigue de esta forma: double[] [] matrizIdentidadDeÓrden3 = { { 1.0 , 0.0 , 0.0 } , { 0.0 , 1.0 , 0.0 } , { 0.0 , 0.0 , 1.0 } }; El siguiente programa utiliza los array y lo editaremos en un archivo que se llama vect.java: clase vect { static ent NOMBRE = 0; static ent APELLIDOS = 1; static ent PATODÓNALD=0; static ent GILBERTO=1; static ent JORGITO=2; static ent JAIMITO=3; static ent JUANITO=4; static ent GUS=5; static ent COMISARIO=6; static ent RATÓN=7; static ent SUPER PATO=8; static ent PLUTO=9; static ent ABUELA=10; static ent GILITO=11; static Cadena personajes[][] = { {"Paolino","Pato Donald"}, {"Pietro","Gilberto"}, {"Jorgito","No especificado"}, {"Jaimito","No especificado"}, {"Juanito","No especificado"}, {"Ciccio","De Abuela Donald"}, {"No especificado","Comisario"}, {"No especificado","Ratón Micky"}, {"Tribilín","No especificado"}, {"Pluto","No especificado"}, {"No especificado","Daisy"}, {"No especificado","Gilito"}, }; public static void main(String [] a) { ent PersonajeTmp; ent Número=1; System.out.println ("Unos personajes de Walt Disney"); PersonajeTmp=PATODÓNALD; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=GILBERTO; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=JORGITO; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=JAIMITO; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=JUANITO; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=GUS; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=COMISARIO; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=PLUTO; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=TRIBILÍN; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=GILITO; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); Número=Número+1; PersonajeTmp=RATÓN; System.out.println("Número:"+Número+"tNombre:"+personajes[PersonajeTmp][NOMBRE]+" Apellidos:"+personajes[PersonajeTmp][APELLIDOS]); System.out.println("HiCe los cálculos "+Número+" personajes, y eran muchos más"); // Éste sí es un programa realmente inútil. El autor. } } Un pequeño comentario sobre el programa. Os habéis dado cuanta de que en el main se establecen unas variables (es posible hacerlo): normalmente se usan sin redactarlas como static porque las variables del main, o de una función, por lo general, no son atributos y, por eso, no están relacionados con el objeto o con la clase, sino sólamente con la función en la que se escriben. En estas definiciones no están incluidos los modificadores porque estos se asocian a las clases. Llegados a este punto nos queda por ver los tipos compuestos que se pueden fácilmente identificar ahora con las clases. Por ejemplo, si queréis definir el tipo compuesto Complejos, escribiremos: clase Complejos { private double ParteReal; private double ParteImaginaria; Complejos (double Re, double Im) { ParteReal=Re; ParteImaginaria=Im; } // ahora estableceremos todos nuestros obvios métodos en el nuevo tipo double démeParteReal() { return ParteReal; }; double démeParteImaginaria() { return ParteImaginaria; }; double calculaMódulo() { return (java.lang.Math.sqrt((ParteImaginaria*ParteImaginaria)+ (ParteReal*ParteReal))); }; } Para probar, podéis escribir los siguiente(pruebaComplejos.java): clase pruebaComplejos { public static void main (Cadena[] arg) { Complejos N=nuevo Complejos(3.0,1.1); double Re=N.démeParteReal(); double Im=N.démeParteImaginaria(); double mod=N.calculaMódulo(); System.out.println("Número complejo:"+Re+"+i"+Im+" tiene "+mod); } } Operadores Los operadores del enguaje Java son los mismos que los de los demás lenguajes, además cada nuevo tipo tendrá su proprio método que incluyen nuevos operadores, como vimos en el ejemplo de la clse Complejos. Analizamos los operadores de acceso a las clases y a los objetos, es decir, simplemente el punto. Si queremos entrar en el campo X del objeto x, simplemente tendremos que escribir x.X, aunque x tenga un método Y(ent valor), nosostros entramos o, mejor en este caso, lo solicitamos escribiendo x.Y(39); Tenemos los operadores aritméticos de siempre: +,-,*,/ y %. Hay que tener en cuenta también las operaciones son tipadas, es decir, si sumo dos enteros el resultado es un valor entero y así para todas las operaciones, de forma que si tenemos una expresión siempre es posible establecer el tipo de resultado. Java ejerce un fuerte control sobre los tipos. De hecho, no es posible asignar un carácter e un entero, lo que es posible en el lenguaje C, y además es imposible asignar un double a un float sin un casting explícito. El casting obliga a un valor de un tipo a tener un tipo distinto. Si escribo 5, me refiero a un entero, mientras que si hago un cast a float me refiero a la versión de 5 real y para hacerlo tendré que escribir(float) 5. De esta forma puedo asignar los enteros a reales y de reales double a relaes float. Pensemos, por ejemplo en: double v1=10.0; float v2; ent v3=5; y pensemos en las siguientes asignaciones equivocadas: v2=v1; v1=v3; Si hacemos unos cast, se convierten en exactas también para Java: v2=(float) v1; v1=(double) v3; Hay que notar la división de los enteros que en otros lenguajes da un entero. Por lo tanto, si queremos el número real que resulta de la división de los dos números enteros x e y, antes hay que transformarlos en números reales: float resultado= (float ) x / (float) y; Para las cadenas hay un operador de encadenamiento: cadena a="Pietro " ; Cadena b= a + "Castellu"; Cadena b+="cci" El resultado será Pietro Castellucci. El operador es el signo +. En un momento dado, utilizamos una asignación particular, es decir, += que es una abreviación. Si tengo que escribir a=a+1; puedo abreviar y escribir a+=1, que es lo mismo. Lo mismo vale para cada operador binario. El operador + cuelga de una cadena también caracteres y números, pensemos en las salidas de los ejemplos anteriores en los que escribíamos, por ejemplo: System.out.println("Número complejo:"+Re+"+i"+Im+" tiene módulo "+mod); esto os puede parecer raro, sobre todo si consideramos que dijimos que las expresiones Java están bien caracterizadas por los tipos, a diferencia del C. Esto ocurre, sin embargo, porque el sistema hace unas conversiones implícitas de tipos primitivos y objetos en cadenas. Por ejemplo, si establecemos cualquier objeto y lo hacemos con un método toString(), el sistema tendrá que trabajar con cadenas y con este objeto solicitará automáticamente el método toString(). Además Java ofrece otros cuatro operadores que son unas abreviaciones: dos son de incremento de variables y dos de incremento. Si X es una variable puedo escribir: X++ ++X X-- --X Esto quiere decir: primero evalua X y luego incrementa X, primero incrementa X y luego evalua X, primero evalua X y luego disminuye X y , por último, primero disminuye X y luego evalúa X. Esto quiere decir que la expresión X++ es un mando de asignación, pero también una expresión que devuelve un resultado que, en este caso, es X antes de que se incremente. Por ejemplo: X=10; Y=X++; dará como resultado X=11 e Y=10. Intendad editar y poner en marcha lo siguiente clase incdec { public static void main(Cadena [] a) { ent X,Y,Z,W,V; X=10; System.out.println("X="+X); Y=X++; System.out.println("Y=X++: tengo X="+X+",Y="+Y); Z=++Y; System.out.println("Z=++Y: tengo Z="+Z+",Y="+Y); W=Z--; System.out.println("W=Z--: tengo W="+W+",Z="+Z); V=--W; System.out.println("V=--W: tengo V="+V+",W="+W); } } Otros operadores son los de comparación, es decir: > mayor de, x>y nos da verdadero si x es mayor que y, en caso contrario, nos da falso >= mayor o igual a, x>=y nos da verdadero si x es mayor o igual a y, en caso contrario nos da falso < menor de, x<y nos da verdadero si x es menor de y, en caso contrario, nos da falso <= menor o igual a, x<=y nos da verdadero si x es menor o igual a y, en caso contrario, nos da falso == igual a, x==y nos da verdadero si x es igual a y, en caso contrario, nos da falso != diferente, x!=y nos da verdadero si x es diferente de y, en caso contrario, nos da falso Tenemos también los operadores lógicos &&, || y !, que representan la conjunción y, la disjuntiva o y el no: Establecidos los valores booleanos x e y , x && y será verdadera si x e y lo son también; será falsa si no lo son (si las dos son falsas o si una es verdadera y la otra falsa). x || y sará vedadera si x o y o los dos son verdaderas (será falsa sólo si los dos son falsos). !x será verdadera si x es falsa, mientras será falsa si x es verdadera. Tenemos los operadores binarios orientados hacia los bit: &, AND bit a bit | , OR bit a bit ^, OR EXCLUSIVO, bit a bit y los shift, >>, <<, >>>. Estas operaciones binarias son muy útiles para construir las matrices. El último operador es el condicional que nos permite definir expresiones con dos diferentes resultados según un valor booleano. Pongamos el siguiente ejemplo: ent ValorAbsoluto = ( a<0 ? -a : a ); ValorAbsoluto tendrá el valor -a si a es negativo o si a es positivo o nulo. Llegados a este punto, podemos escribir todas las expresiones que queramos en lenguaje Java. Instrucciones Estamos preparados para ver los constructores principales del lenguaje Java. Ya vimos los comentarios, que no se pueden ocultar, es decir, que el siguiente comentario no es posible: /* +Éste es seguramente un comentario /* Éste también lo es */ */ Sin embargo, se pueden introducir comentarios de una línea en los comentarios de más de una línea porque son distintos. Por ejemplo, es posible escribir lo siguiente: /* Éste es seguramente un comentario // Éste también lo es */ aunque realmente resulte bastante inútil. Las primeras instrucciones de Java que vamos a ver son las instrucciones de declaración, que son las declaraciones de variables locales. Se utilizan para declarar variables y darles un posible valor inicial. Estas instrucciones ya las vimos y se parecen a las declaraciones de los atributos, excepto por la falta de los modificadores. Las variables locales, antes de ser utilizadas, se tienen que inicializar. Las variables locales se pueden declarar en un punto cualquiera de un bloque y no necesariamente al inicio, además existen sólo en los bloques en los que se declaran. Un bloque es como una instrucción, lo único es que empieza por { y acaba por }, y en él puede tener muchas instrucciones, incluso posiblemente de otros bloques. { instrucción 1; instrucción 2; …. instrucción j-1; { subinstrucción1; subinstrucción2; ….. subinstrucción; }; // es la instrucción j instrucción j+1; …. instrucción n; }// Este bloque puede ser el cuerpo de un método o un bloque Pensad en el método: void tribilín() { ent j=0; { ent k=0; k++; // instrucción A }; j++; // instrucción B k++; // instrucción C } La instrucción A no da problemas porque usa una variable local que está inicialidada, declarada en su mismo bloque. Con la instrucción B ocurre lo mismo, mientras la instrucción C está equivocada porque la variable que utiliza se ha declarado en un bloque entero y no se puede ver externamente. Si en el bloque interno hubiera escrito j++; no tendría ningún problema porque la variable j se habría declarado en un bloque externo. (Dentro se ve lo que hay fuera, pero fuera no se puede ver lo que hay dentro, lo cual ocurre en todos los lenguajes de programación por bloques). Acabamos de ver en el ejemplo que también j++ es tanto una instrucción como una expresión, como su otra forma e ++j, y es su imagen especular de disminución. Hay otras operaciones que se pueden tener en cuenta a la hora de analizar las instrucciones. Se trata tanto de la implementación de los métodos que pueden devolver o no un valor, como de las instrucciones de creación de objetos, las que llevan new. Por ejemplo, new Object(), es una instrucción correcta. Otra instrucción muy parecida a una expresión es la instrucción de asignación, la del tipo: NombreVariable = expresión; Por lo tanto, hasta ahora hemos visto las seis instrucciones: Declaración de una variable local; Bloque; Formas de incremento y disminución de las variables, prefijas y postfijas; Creación de los objetos; Solicitud de los métodos; Asignación de las variables; Todas las instrucciones terminan por ; Las demás instrucciones se llaman constructos y son muy parecidas a las de los demás lenguajes de programación. Las vamos a ver detalladamente. Instrucción condicional if (EXPBOOL) IST Esta instrucción pone en marcha la instrucción llamada IST que puede ser una cualquier instrucción del lenguaje sólo si la expresión booleana EXPBOOL es verdadera, de lo contrario se salta. Una variedad es la en que pone en marcha otra instrucción si EXPBOOL es falsa, es: if (EXPBOOL) IST else IST2 Fijaos que IST y IST2 son instrucciones generales, por lo tanto también los bloques o las demás instrucciones condicionales. En este último caso hablamos de if ocultos. Instrucción switch Switch (EXP) { case CST11: …. :case CST1n: IST1 case CST21: …. : case CST2n: IST2 …. case CSTm1: …. : case CSTmn: ISTm default ISTm+1; }; Si EXP es una expresión, CSTij son unas constantes del mismo tipo que la expresión. EXP y ISTi son unas instrucciones. La instrucción evalúa EXP, y compara el resultado con las constantes de los case; si encuentra otro igual, pone en marcha la instrucción correspondiente. Si, en cambio, no encuentra ninguna constante igual, pone en marcha la instrucción después del default, lo que es una opción. En el caso de que esté ausente y ningún case tenga la constante igual al valor de EXP, se salta el switch. Analizamos, por ejemplo, el siguente mando: switch (5+1) { case 6 : System.out.println ("Muy bien"); default : System.out.println ("Burrp, es seis"); }; Instrucción for for (exp de inicialización; exb booleana; exp de incremento) ISTR pone en marcha la instrucción ISTR que es un número de veces igual a los valores contenidos en un intervalo. Normalmente la instrucción de inicialización pone en marcha una variable en un valor; la variable se incrementa (o disminuye) a partir de la instrucción de incremento y en la expresión booleana se controla que la variable tenga los valores que queremos. En efecto, si la expresión booleana es falsa, enseguida se sale del ciclo for. Voy a poner un ejemplo: quiero inicializar los valores de un vector de 100 enteros todos a cero. En lugar de escribir 100 asignaciones, escribiré: ent v = new ent[100]; for (ent e = 0 ; e<100; e++) v[e] = 0 ; Como acabamos de ver la variable también se puede declarar en la instrucción de inicialización. De esta forma, sólo se puede ver en el for. Instrucción while while (EXPBOOL) IST Pone en marcha IST simulando que EXPBOOL es verdadera, por ejemplo, la inicialización del ejemplo anterior se puede llevar a cabo con el while de la siguiente forma: ent v = nuevo ent[100]; ent e=0; while (e<100) { v[e]=0; e+=1; }; o de forma comprimida, aprovechando el incremento de forma comprimida: ent v = nuevo ent[100]; ent e=0; while (e<100) v[e++]=0; Instrucción do-while do IST while (EXPBOOL); ES como el while, sólo que antes ejecuta la instrucción y luego controla el EXPBOOL, de nuevo la inicialización precedente se convierte en: ent v = nuevo ent[100]; ent e=0; do { v[e]=0; e+=1; } while (e<100); o también de forma todavía más comprimida: ent v = nuevo ent[100]; ent e=0; do v[e++] = 0 while (e<100); Etiquetas Es posible poner etiquetas a las instrucciones, como ocurre con el código assembler. Esto resulta muy útil en combinación con las instrucciones de break y de continue: etiqueta: instrucción; instrucción de break La instrucción de break se utiliza para salir de un bloque o de un switch; cuando se encuentra un break se sale del bloque más interno, mientras que si se pone una etiqueta, se sale hasta donde la etiqueta se ha declarado. Ejemplo: { while ( COND ) { while (COND2) { if (ALGO) break; } } } En este caso se sale del while interior, mientras: { tribilín: while ( COND ) { while (COND2) { if (ALGO) break tribilín; } } } se sale de los dos while. Instrucción de continue En un ciclo se salta todas las instrucciones que le siguen y evalúa directamente la expresión booleana, por ejemplo: while (Condición) { instrucciones; if (GolpeDeEfecto) continue; otras instrucciones; }; Se entra en el ciclo, se ponen en marcha las instrucciones. Si GolpeDeEfecto es verdadero, salta otras instrucciones y llega a evaluar Condición. Si ésta también es verdadera, continúa el ciclo poniendo en marcha las instrucciones, etc.. Instrucción de return Produce la conclusión de la ejecución del método en el que se encuentra y el retorno a la llamada. Si el método es void basta con escribir return, si no tiene que contener una expresión del mismo tipo del método, por ejemplo ent tribilín() { return 10; } con la instrucción return es posible también bloquear la puesta en marcha de un constructor o de un método static. Con esto acabamos nuestro examen de las instrucciones. Ahora estamos preparados para construir cualquier aplicación a consola. Pongamos un par de ejemplos: empecemos escribiendo un pequeño programa que imprima los primeros números, los menores de 500 de la famosa serie de Fibonacci. Para los que no la conozcan, la serie de Fibonacci es una secuencia infinita de números cuyos dos primeros elementos son: 1, 1, y los demás elementos se calculan como la suma del e-1 ésimo número y del e-2 ésimo, formalmente, reconociendo Fibo come una función: Fibo ( e ) = Fibo ( e - 1 ) + Fibo ( e - 2 ); El programa que hay que escribir en Fibo.java es el siguiente: class Fibo { public static void main (Cadena[] args) { ent bajo=1, alto=1; // ent alto=1; System.out.println (bajo); while (alto<500) { System.out.println(alto); alto+=bajo; bajo = alto - bajo; }; } } Para que os entrengáis, podéis intentar hacer un programa que calcula 10 ! , sabiendo que 1! = 1 y que establecido n entero, n! = n * ((n-1)!). La solución se encuentra en el archivo Fatt.java. Ahora, intentamos escribir nuetra criba de Eratóstenes hecho en casa, es decir, una forma para calcular todos los números primos hasta un número establecido, digamos 100; El algoritmo procede así, 1 es un número primo. 2 es un número primo, le quito sus múltiplos 3 es un número primo, le quito sus múltiplos etcétera hasta encontrar mi máximo. clase Eratóstenes { static ent MÁX=1000; ent [] números = nuevo ent[MÁX]; boolean [] primos = nuevo boolean[MÁX]; public Eratóstenes() { ent e; for (e=0;i<MAX;e++) { números[e]=e+1; primos[e]=verdaderos; }; } void elMul(ent a) { for (ent j=2;j*a<=MÁX;j++) primos[(j*a)-1]=falso; } ent siguiente(ent n) { ent tmp=n; while (!primos[tmp]) { tmp++; if (tmp>=MÁX) break; } return tmp+1; } void calculaPrimos() { ent núm=2; while (núm<=MÁX) { elMúl(núm); núm=siguiente(núm); }; } void escribaPrimos() { System.out.println("Los números primos hasta "+MÁX+" son:"); for (ent e=0; e < MÁX ; e++) if (primos[e]) System.out.print(números[e]+" "); } public static void main(Cadena[] args) { Eratóstenes e = nuevo Eratóstenes(); e.calculaPrimos(); e.escribaPrimos(); } } Indicaciones sobre las excepciones y sobre los thread Las excepciones son una forma clara para controlar los errores sin confundir el código con muchas instrucciones de control del error. Cuando se verifica un error se pone en marcha una excepción que, si se recibe enseguida, nos permite gestionar un error. De lo contrario, el soporte lleva a cabo como ejecución de Java, un íter de default. En Java hay una clase que se llama Exception, y las excepciones son objetos que pertenecen a esta clase. Por eso, se pueden trabajar como verdaderos componentes del lenguaje. Vamos a ver cómo es posible crear una excepción. Hay que crear una clase que amplía Throwable, o mejor Exception , que es una extensión de Throwable. Supongamos que queremos crear una excepción para decir que utilizamos una cadena vacía: tendremos que escribir: public clase ErrorCadenaVacía amplía Exception { ErrorCadenaVacía() { super("Cuidado, estás utilizando una cadena que no ha sido inicializada"); } } Esta clase tiene que estar en un archivo que se llama como la clase, es decir, ErrorCadenaVacía.java Después de haberla creado, necesitaremos una forma para hacerla trabajar, es decir, para evidenciar la situación de error que, ya que estamos, espero que alguien recoja. Las excepciones trabajan con la instrucción throw que tiene que estar en un método que, a su vez, se tiene que declarar con un cláusola throws. Por ejemplo, en nuestro caso, si quiero crear un método para imprimir la cadena que controla incluso el error, escribiré en mi clase: public void Imprimir (Cadena a) throws ErrorCadenaVacía /* ß Esto quiere decir que esta función puede poner en marcha una excepción del tipo ErrorCadenaVacía*/ { if (a==null) throw nuevo ErrorCadenaVacía(); else System.out.println(a); } Entonces, llegados a este punto, el método Imprimir enviará a la pantalla la cadena transformada en parámetro y dará un excepción de tipo ErrorCadenaVacía si el puntero era nulo. La excepción que damos nosotros se llama excepción controlada y tiene que ser obligatoriamente gestionada, mientras las que da Java no necesariamente tienen que ser recogidas y gestionadas. Ahora vamos a ver como recoger las excepciones, por ejemplo, nuestra ExcepciónCadenaVacía. Las excepciónes se recogen cuando se solicitan los métodos que se declaran con la cláusula throws. Para recogerlas hay que implementar el método en bloques try que tienen la siguiente sintaxis: try BLOQUE // Éste es un bloque peligros porque puede poner en marcha unas excepciones. catch (Tipo de Excepción que hay que coger) BLOQUE // Éste es un bloque de restablecimiento. catch (Tipo de Excepción que hay que coger) BLOQUE // Éste es un bloque de restablecimiento. …. finally BLOQUE El corpus de esta instrucción se ejecuta hasta el final o hasta que aparezca una excepción. En caso de excepción, se analizan las cláusulas catch para ver si existe un administrador para esa excepción o para una de sus superclases. Si ninguna cláusula catch recoge esa excepción, ésta vuelve a lanzarse en el método que la ha provocado (que la ha ejecutado). Si en el try hay una cláusula finally, su código se pone en marcha después de que se hayan completado todas las demás operaciones del try, independientemente de que ésta haya ejcutado una excepción o no. Por ejemplo, nuestro método Imprimir se puede invocar así: Si X es la cadena que hay que imprimir: try {Imprimir(X);} catch (ErrorCadenaVacía e) {System.out.println ("Lo siento");} En este caso, si X es nulo, se pondrá en marcha la excepción y se recogerá; después se imprimirá la cadena Lo siento, si no X. Si escribiera Imprimir(X) fuera de la try, saldría error porque las excepciones que yo he causado tienen que ser recogidas. Esto obliga al programador a escribir código "bueno". Las excepciones de Java, en cambio, se pueden coger o no. El ejemplo siguiente recoge el HolaMundo del primer capítulo que, si invocado sin parámetros, daba una excepción de ArrayIndexOutOfBoundsException, que, este vez, se gestiona; clase HolaMundo { public static void main(Cadena[] args) { System.out.print ("Hola Mundo, soy el primer programa en Java"); Cadena Nombre; Cadena Apellidos; try {Nombre=args[0];} catch (ArrayIndexOutOfBoundsException e) {Nombre="No has introducido tu Nombre";} ; try {Apellidos=args[1];} catch (ArrayIndexOutOfBoundsException e) {Apellidos="No has intruducido tus Apellidos";} ; System.out.println ("de "+Nombre+" "+Apellidos); } } Intenten ponerlo en marcha de las siguientes formas: java HolaMundo Nombre Apellidos java HolaMundo Nombre java HolaMundo y a ver lo que pasa. Hagan lo mismo con el HolaMundo del primer capítulo. El paquete java.lang El Paquete lang Este paquete lo vimo antes. Ahora, sin embargo, analizaremos algunas funciones que antes no vimos. En primer lugar, hay que decir que éste es uno de los paquetes más importantes de la API Java. Abarca muchísimas clases e interfaces fundamentales para la programación Java, tanto que este paquete se incluye atomáticamente en nuestros programas. Su contenido es:: Intefaces Cloneable Comparable Runnable Clases Boolean Byte Character Character.Subset Character.UnicodeBlock Class ClassLoader Compiler Double Float InheritableThreadLocal Integer Long Math Number Object Package Process Runtime RuntimePermission SecurityManager Short String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Void Excepciones ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException ClassCastException ClassNotFoundException CloneNotSupportedException Exception IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IndexOutOfBoundsException InstantiationException InterruptedException NegativeArraySizeException NoSuchFieldException NoSuchMethodException NullPointerException NumberFormatException RuntimeException SecurityException StringIndexOutOfBoundsException UnsupportedOperationException Errores AbstractMethodError ClassCircularityError ClassFormatError Error ExceptionInInitializerError IllegalAccessError IncompatibleClassChangeError InstantiationError InternalError LinkageError NoClassDefFoundError NoSuchFieldError NoSuchMethodError OutOfMemoryError StackOverflowError ThreadDeath UnknownError UnsatisfiedLinkError UnsupportedClassVersionError VerifyError VirtualMachineError En el primer capítulo vimos las clases que incluyen los tipos primitivos incluidos en este paquete. En este apartado analizaremos unas clases que hacen cosas más complejas. La primera clase que analizaremos es la clase System, que relacciona nuestro programa Java con el sistema en el que se pone en marcha. En primer lugar, la clase System incluye tres atributos estáticos que son: static PrintStream err static InputStream in static PrintStream out Estos programas representan respectivamente el error estándar, el input estándar y el output estándar (que ya utilizamos). Son atributos particulares porque son de tipo clase, por eso se pueden utilizar como referencia para capturar los métodos de las clases, por ejemplo, en la escritura: System.out.println("Hola"); Se invoca el método println(String) de la classe PrintStream, porque out es de tipo PrintStream. En nuestros ejemplos, utilizamos indiferentemente las expresiones: System.out.println("Hola"); System.out.println(verdadero); System.out.println(10); System.out.println('C'); En general, en Java esto no es posible porque un método se invoca a través de un parámetro formal. Esto no es una excepción. De hecho, invocar el método println con todos estos tipos de datos distintos es posible porque PrintStream incluye todos los métodos println utilizados con el parámetro formal adecuado. Así ésta incluye: void println() void println(boolean x) void println(char x) void println(char[] x) void println(double x) void println(float x) void println(int x) void println(long x) void println(Object x) void println(String x) los mismos métodos print, y por supuesto, otros. Lo mismo vale para err, que es también parecido a un out del tipo PrintStream. Éste se usa para evidenciar los errores de programa. Es bastante fácil encontrar expresiones como las siguientes en los programas: try {System.out.println(cadena+" vs "+otracadena+" = " +cadena.compareTo(otracadena));} catch (nullPointerException e) {System.err.println("ERROR: La segunda cadena es nula");}; En cambio, el atributo in es del tipo InputStream, y representa, como dijimos antes, el estándar input. Los métodos para actuar sobre él serán, entonces, unos read: abstract int read() int read(byte[] b) int read(byte[] b, int off, int len) Como es del tipo InputStream, lee unos byte. Si queremos que lea otras cosas tenemos que especializarlo en esta otra cosa. En el siguiente ejemplo, que teclearemos en un archivo que se llama StandardIO.java, le haremos leer enteras líneas. import java.io.*; class StandardIO { public static void main(String[] temas) { System.out.println("Incluye tus input, teclea end [ENVÍO] para salir"); InputStreamReader a=new InputStreamReader(System.in); BufferedReader IN=new BufferedReader(a); String s=new String(); while(s.compareTo("end")!=0) { try {s=IN.readLine();} catch (IOException e) {s="ERROR DE LECTURA";}; System.out.println("Leído: "+s); } } } Vamos a ver algunos métodos de la clase System: static long currentTimeMillis(), este método nos devuelve el tiempo en milisegundos static void exit(int status), este método determina como salir de Java Virtual Machine, con un código . static void gc(), Java coloca tantos objetos, y los descoloca, cuando ya no se usan y se necesita nueva memoria. Para hacerlo, utiliza un Garbage Collector , y este método pone en marcha el garbage collector en cualquier momento para liberar la memoria precedentemente establecida por el programa que todavía no se ha utilizado. Puede resultar muy útil en aplicaciones muy grandes. static String getenv(String name), nos devuelve una cadena que incluye informacciones con respecto al sistema con el que se pone en marcha el programa. El método declarado es deprecated en las últimas Documentaciones on line del Java Development Kit porque el método pronto desaparecerá. Es una de las primeras versiones del lenguaje y todavía aparece porque es compatible con el viejo software. static Properties getProperties() , es el nuevo método para obtener informaciones sobre la propriedad del sistema. static String getProperty(String key), como antes, lo único es que recoge informaciones específicas con respeto a key static String getProperty(String key, String def) static void load(String filename) , carga en la memoria el código incluido en filename, que es una biblioteca dinámica. static void loadLibrary(String libname), carga la biblioteca de sistema mencionada por libname static String mapLibraryName(String libname), hace un mapa de una biblioteca en una cadena que la representa. Hay métodos para volver a asignar los estándars input, output y err, en otros flujos, por ejemplo un archivo. static void setErr(PrintStream err) static void setIn(InputStream in) static void setOut(PrintStream out) Los siguientes métodos reparan las propiedades del sistema. static void setProperties(Properties props) static String setProperty(String key, String value) Utilizamos unos objetos de tipo Properties y ahora veremos cómo están hechos. Se trata de unas especializaciones de cuadros hash de java y son, en sustancia, unos cuadros que incluyen una serie de parejas Clave - Valor. (En realidad, no son precisamente esto, sino que son unas estructuras muy utilizadas en informática para contener los datos. Pero a nosotros nos interesa verlas así de momento). En particular, las Properties del sistema son: Clave Descripción del valor asociado java.version Versión del ambiente de Java Runtime java.vendor Distribuidor del ambiente de Java Runtime java.vendor.url URL del destribuidor de Java java.home Directory donde está instalado Java java.vm.specification.version Versión de las especificaciones de Java Virtual Machine java.vm.specification.vendor Distribuidor de las especificaciones de Java Virtual Machine Nombre de las especificaciones de Java Virtual Machine java.vm.version Versión de la implementación de Java Virtual Machine java.vm.vendor Distribuidor de la implementación de Java Virtual Machine java.vm.name Nombre de la implementación de Java Virtual Machine java.specification.version Versión del ambiente de Java Runtime java.specification.vendor Distribuidor del ambiente de Java Runtime Java Runtime java.specification.name Nombre del ambiente de Java Runtime java.class.version Versión de las clases de Java java.class.path Pathname de las clases de Java os.name Nombre del Sistema Operativo os.arch Arquitectura del Sistema Operativo os.version Versión del sistema Operativo file.separator Separador del Archivo ("/" en UNIX, "" en Windows) path.separator Separador de Path (":" en UNIX, ";"en Windows) line.separator New Line ("n" en UNIX y Windows) user.name Account name del usuario user.home Home directorio del usuario user.dir Working directorio del usuario Para eso, podemos escribir un pequeño programa que nos informe sobre el sistema como el siguiente que hay que editar en un archivo que se llama Sistema.java import java.io.*; public class Sistema { public static void main(String[] arg) { // Cambio el estándar output, uso el archivo Sistema.txt File outFile=new File("Sistema.txt"); FileOutputStream fw; try {fw=new FileOutputStream(outFile) ;} catch (IOException e) {fw=null;}; PrintStream Output=new PrintStream(fw); System.setOut(Output); // Escribo en el nuevo estándar output: // Tiempo: long tiempo=System.currentTimeMillis(); System.out.println("Tiempo en milisegundos: "+tiempo); long t1=tiempo/1000; System.out.println("Tiempo en segundos: "+t1); long sec=t1%60; long t3=t1/60; long min=t3%60; long t4=t3/60; System.out.println("Tiempo en horas h"+t4+" m"+min+" s"+sec); System.out.println("nEs el tiempo pasado del 1/1/1970 hasta ahora.n"); // Propiedad del sistema: System.out.println("nPropiedad del sistema:n"); String tmp; System.out.println("ntJAVAn"); tmp=System.getProperty("java.version "); System.out.println("Versión del ambiente de Java Runtime: "+tmp); tmp=System.getProperty("java.vendor"); System.out.println("Distribuidor del ambiente di Java Runtime: "+tmp); tmp=System.getProperty("java.vendor.url"); System.out.println("URL del distribuidor de Java: "+tmp); tmp=System.getProperty("java.home"); System.out.println("Directorio donde está instalado Java: "+tmp); tmp=System.getProperty("java.vm.specification.version"); System.out.println("Versión de las especificaciones de Java Virtual Machine: "+tmp); tmp=System.getProperty("java.vm.specification.vendor"); System.out.println("Distribuidor de las especificaciones de Java Virtual Machine: "+tmp); tmp=System.getProperty("java.vm.specification.name"); System.out.println("Nombre de las especificaciones de Java Virtual Machine: "+tmp); tmp=System.getProperty("java.vm.version"); System.out.println("Versión de la implementación de Java Virtual Machine: "+tmp); tmp=System.getProperty("java.vm.vendor" ); System.out.println("Distribuidor de la implementación de Java Virtual Machine: "+tmp); tmp=System.getProperty("java.vm.name"); System.out.println("Nombre de la implementación de Java Virtual Machine: "+tmp); tmp=System.getProperty("java.specification.version"); System.out.println("Versión del ambiente de Java Runtime: "+tmp); tmp=System.getProperty("java.specification.vendor"); System.out.println("Distribuidor del ambiente de Java Runtime Java Runtime: "+tmp); tmp=System.getProperty("java.specification.name" ); System.out.println("Nombre del ambiente de Java Runtime: "+tmp); System.out.println("ntCLASESn"); tmp=System.getProperty("java.class.version"); System.out.println("Versión de las clases de Java: "+tmp); tmp=System.getProperty("java.class.path"); System.out.println("Pathname de las clases de Java: "+tmp); System.out.println("ntSISTEMA OPERATIVOn"); tmp=System.getProperty("os.name"); System.out.println("Nombre del Sistema Operativo: "+tmp); tmp=System.getProperty("os.arch"); System.out.println("Arquitectura del Sistema Operativo: "+tmp); tmp=System.getProperty("os.version"); System.out.println("Versión del sistema Operativo: "+tmp); tmp=System.getProperty("file.separator"); System.out.println("Separador del Archivo: "+tmp); tmp=System.getProperty("path.separator"); System.out.println("Separador del Pathname: "+tmp); tmp=System.getProperty("line.separator"); System.out.println("New Line: "+tmp); System.out.println("ntUSUARIOn"); tmp=System.getProperty("user.name"); System.out.println("Account name del usuario: "+tmp); tmp=System.getProperty("user.home"); System.out.println("Home directory del usuario: "+tmp); tmp=System.getProperty("user.dir"); System.out.println("Working directory del usuario: "+tmp); } } Puesto en marcha en mi sistema, el output fue (Sistema.txt): Tiempo en milisegundos: 956241233430 Tiempo en segundos: 956241233 Tiempo en horas h265622 m33 s53 Es el tiempo transcurrido del 1/1/1970 hasta ahora. Propiedad del sistema: JAVA Versión del ambiente de Java Runtime: null Distribuidor del ambiente de Java Runtime: Sun Microsystems Inc. URL del distribuidor de Java: http://java.sun.com/ Directorio donde está instalado Java: C:PROGRAMASJAVASOFTJRE1.3 Versión de las especificaciones de Java Virtual Machine: 1.0 Distribuidor de las especificaciones de Java Virtual Machine: Sun Microsystems Inc. Nombre de las especificaciones de Java Virtual Machine: Java Virtual Machine Specification Versión de la implementación de Java Virtual Machine: 1.3.0rc1-S Distribuidor de la implementación de Java Virtual Machine: Sun Microsystems Inc. Nombre de la implementación de Java Virtual Machine: Java HotSpot(TM) Client VM Versión del ambiente de Java Runtime: 1.3 Distribuidor del ambiente de Java Runtime Java Runtime: Sun Microsystems Inc. Nombre del ambiente de Java Runtime: Java Platform API Specification CLASSI Versión de las clases de Java: 47.0 Pathname de las clases de Java: . SISTEMA OPERATIVO Nombre del Sistema Operativo: Windows 95 Arquitectura del Sistema Operativo: x86 Versión del sistema Operativo: 4.0 Separador del Archivo: Separador del Pathname: ; New Line: USUARIO Account name del usuario: pietro Home directory del usuario: C:WINDOWSProfilesPietr000 Working directory del usuario: D:LavoroHTMLpointcorsoesempicap3lez6 Otra clase muy importante del paquete java.lang di Java es la clase Object. Ésta es la clase desde la que se crean todas las demás clases de Java, es decir, cualquier otra clase de Java es una extensión de la clase java.lang.Object. La class Object no incluye ninguna variable, sino que incluye 11 métodos que se heredan de todas las demás clases de java y pueden ser utilizadas en cualquier objeto fijado en Java, entre las que: clone(), que crea una copia del objeto idéntica a la de la que invoca el método. En este caso hay que fijarse en que el mismo programa Java es un Objeto, por eso, se pueden crear un número arbitrario de programas todos idénticos. Para que se pueda invocar el método clone() de un objeto, este tiene que implementar la interfaz Cloneable . getClass(), devuelve un Objeto de tipo Class que representa la clase a la que pertenece el objeto cuyo método fue solicitado. toString(), transforma el objeto en una cadena. Este método tiene que ser escrito otra vez en los objetos creados por el usuario, pero es muy útil. Pensemos en la escritura Cadena suma="Sumando"+10+" y "+11+" tengo "+(10+11); Creamos una cadena usando unas cadenas y unos int. De forma automática, en este caso Java invoca el método toString() de la clase correspondiente que lo contiene. Esto ocurre en cada expresión que abarca cadenas y objetos como "Cadena"+Objeto. La otra clase del paquete a la que nos referimos antes es Class y representa las clases del lenguaje. Es muy importante porque tiene más de treinta métodos que sirven para gestionar las clases a runtime, lo que es imprenscindible en los demás lenguajes de programación. Objetos de este tipo, llamados descriptores de clase, aunque sean ellos mismos descriptores de interfaces, se crean y se asocian automáticamente a los objetos a los que se refieren. Objetos de tipo Class, Object y otros tipos, son muy importantes si son a runtime porque permiten gestionar el programa que se está poniendo en marcha como si fuera una colección de datos sobre los que es posible trabajar. Java es un lenguaje de potencialidad impresionante. Si pensamos en java.lang, hay una clase que se llama Compiler que incluye métodos para redactar fuentes Java. Hay otras clases como ClassLoader, Runtime... que permiten cargar nuevas clases a runtime, ponerlas en marcha, y modificar el programa mismo mientras está trabajando. Potencialmente, es posible escribir en Java partiendo del código que se automodifica (que evoluciona). Llegados a este punto para terminar la explicación de java.lang, entre otras cosas porque verlo de forma detallada es demasiado complicado y sin duda está fuera de los fines de nuestro curso, nos basta con saber que existe y que hace cosas que para los otros lenguajes de programación es pura fantasía o, como mínimo, es imposible o dificilísimo trabajar a niveles altos. Antes de acabar descrivo brevemente la clase java.lang.Match (diferente de la clase java.math). Esta clase sirve para hacer cálculos matemáticos y tiene dos atributos: static double E , es la e de Eulero static double PI, es la PI griega Los métodos son obviamente todas las funciones matemáticas calculables, entre las que: Los valores absolutos de valores double, float, int e long: static double abs(double a) static float abs(float a) static int abs(int a) static long abs(long a) Las funciones trigonométricas static double acos(double a) - arcocoseno static double asin(double a) - arcoseno static double atan(double a) - arcotangente static double cos(double a) - coseno static double sin(double a) - seno static double tan(double a) - tangente Transformaciones de ángulos static double toDegrees(double angrad) - convierte radiantes en grados static double toRadians(double angdeg) - convierte grados en radiantes static double atan2(double a, double b) - convierte coordinadas cartesianas (b,a) en coordinadas polares (r,theta) Funciones exponenciales y logarítmicas static double exp(double a) - y elevado a la a. static double log(double a) - logaritmo en base y de a Máximos y mínimos entre valores double, float, int e long static double max(double a, double b) static float max(float a, float b) static int max(int a, int b) static long max(long a, long b) static double min(double a, double b) static float min(float a, float b) static int min(int a, int b) static long min(long a, long b) Potencias y raíces static double pow(double a, double b) - calcula a elevado a la b, notar que si b es <1, esta es una raíz(1/b)-esima de a static double sqrt(double a) - calcula la raíz cuadrada de a. Números pseudocasuales static double random() - da un numero casual entre 0 y 1 Redondeos static double rint(double a) - parte entera baja de a, es un entero static long round(double a) - a redondeado, es un long static int round(float a) - a redondeado, es un entero Visto que hemos nombrado el paquete java.math, digamos que éste contiene dos clases, BigInteger e BigDecimal. La primera clase sirve para tratar números enteros de tamaño arbitrario, pongamos por ejemplo el cálculo factorial de un número muy grande, este será un número exageradamente grande. BigDecimal hace lo mismo con los números particulares. Il package java.util Antes de empezar esta lección tengo que hacer una pequeña advertencia. Hemos visto como hay que programar usando Java, pero me he dado cuenta de que los teclados italianos no tienen tienen llaves, en cambio en los teclados que uso yo sí que aparecen. Por lo tanto os tengo que decir cómo podéis hacer aparecer estas llaves en vuestras pantallas. Para que aparezca { hay que pulsar el teclado ALT, y manteniéndolo pulsado hay que teclear en el pequeño teclado numérico que está a la derecha el número 123 (ALT+123). Para que aparezca } hay que teclear en cambio ALT+125. Para los otros símbolos útiles en Java: ALT Gr + es el de [ y ALT Gr + + de ]. Las teclas están subrayadas para no confundirlas con el signo + que indica che indica "pulsar a la vez". ALT Gr es el ALT que se encuentra a la derecha de la tecla ESPACIO, mientras que ALT se encuentra a la izquierda (mirando al teclado). Este paquete es muy útil porque pone a disposición 34 clases y 13 interfaces que implementan algunas de las estructuras de datos más comunes. Algunas operaciones sobre fechas y sobre el calendario, y otras cosas. Además el paquete java.util incluye otros subpaquetes que son: java.util.mime, java.util.zip y java.util.jar que sirven respectivamente para tratar archivos de tipo MIME, de tipo ZIP y de tipo Java Archive (JAR), que veremos en seguida en este capítulo. Como estructura de datos, en informatica, se llama a la estructura lógica lista para contenerun cierto número de datos en la que es posible insertar un datos, quitarlos, buscarlos, como mucho ordenarlos. Una simple estructura de daots que ya hemos visto es la array. Esta contiene un número cero de datos que pueden ser cualquier cosa, desde byte a objetos complejos, y sobre la misma se pueden incluir otros datos, hacer búsquedas o borrar, incluso un array se puede ordenar. Para ve si en un array grande N está presente un dato X, yo tengo que visitar todo el array y hacer comparaciones con cada elemento del array, por lo que esta es una operación bastante compleja. Existen estructuras de datos que esta búsqueda la hacen empleando un solo acceso a la estructura, estas son, por ejemplo las citadas tablas hash. La elección de las estructuras de datos para usar en un programa es bastante complicada, por ello se elige una estructura en lugar de otra dependiendo del uso que se vaya hacer en el programa. Por ejemplo es difícil que en un database muy grande en el que se hacen muchas búsquedas se use un array como estructura de datos, porque empleareamos mucho tiempo para cada búsqueda (el ordenador es rápido, pero tiene sus límites si pensamos que éste emplea tanto tiempo para visitar completamente un database de mil millones de elementos), en este caso lo mejor tal vez sea emplear una tabla hash de cierta grandeza. Java, por tanto, pone a disposción del usuario toda una gama de estructuras de datos, las gestiona, y a nosotros no nos queda más que usarlas. Para la elección de las estructuras de datos más acorde con nuestras intenciones, usaremos nuestro buen hacer porque, para elegirla de forma rigurosa son necesarios conocimientos específicos, entre otros los detalles que se realizan en las estructuras, estudiadas en cursos universitarios, como los algoritmos y Estructuras de Datos, y Fundamentos de Complejidad. Cuando describa las clases de Java que se ocupan de la implementación de estas estructuras, las indicaré y trataré los tiempos de inserción, las búsquedas, etcétera…, pero siempre informalmente. Vamos a ver qué incluye el paquete java.util Interfaces Collection Comparator Enumeration EventListener Iterator List ListIterator Map Map.Entry Observer Set SortedMap SortedSet Estas interfaces establecen algunas propiedades de nuestras estructuras de datos. Se implementan en algunas de las siguientes clases. Clases AbstractCollection AbstractList AbstractMap AbstractSequentialList AbstractSet ArrayList Arrays BitSet Calendar Collections Date Dictionary EventObject GregorianCalendar HashMap HashSet Hashtable LinkedList ListResourceBundle Locale Observable Properties PropertyPermission PropertyResourceBundle Random ResourceBundle SimpleTimeZone Stack StringTokenizer Timer TimerTask TimeZone TreeMap TreeSet Vector WeakHashMap Excepciones ConcurrentModificationException EmptyStackException MissingResourceException NoSuchElementException TooManyListenersException Empezamos viendo algunas estructuras: BitSet, o en español Conjunto de Bit. Esta clase ofrece una forma para crear y gestionar conjuntos bit (1,0) o mejor dicho, valores verdadero, falso. El conjunto es desde luego un vector de bit pero que crece dinámicamente. Al principio los bit tienen un valor falso, y el vector mide 2^32-1 ((dos elevado a la treinta y dos) menos uno). La clase tiene dos constructores: BitSet(), para crear un BitSet de dimensiones estándar, y BitSet(int nbits), para crear un BitSet que contiene nbits bit. Las operaciones que se pueden hacer son: void and(BitSet set), devuelve el and entre el BitSet y el otro BitSet localizados por set. void andNot(BitSet set), borra todos los bit de la BitSet que tienen el correspondiente bit ajustado en la BitSet set. void or(BitSet set), devuelve el or exclusivo entre el BitSet y el otro BitSet localizados por set. void xor(BitSet set) , devuelve el or exclusivo entre el BitSet y el otro BitSet localizados por set. void clear(int bitIndex), ajusta el bit especificado en falso. void set(int bitIndex), ajusta el bit especificado en verdadero. Object clone(), BitSet se declara Cloneable. Este método crea una copia igual. boolean equals(Object obj), compara el objeto con otro objeto. boolean get(int bitIndex), da el valor del bit número bitIndex. int hashCode(), da un código hash para éste bitset. int length(), da la grandeza lógica del bitset, es decir el bit más alto ajustado en verdadero más uno. int size(), da el número de bit en el momento de la bitset. Es el bit más alto que se puede asignar a uno sin ampliar el bit set. String toString(), transforma el bitset en una cadena Probamos el bit set con el siguiente programa CaracteresUtilizados, que hay que editar en un archivo llamado CaracteresUtilizados.java, que coge una cadena en entrada y lee los caracteres que han sido utilizados. import java.util.BitSet; public class CaracteresUtilizados { public static void main (String[] a) { String tmp; try {tmp=a[0];} catch (ArrayIndexOutOfBoundsException e) {errore();} elabora(a[0]); } public static void error() { System.out.println("ERROR, necesito una cadena:"); System.out.println("tTeclear:"); System.out.println("ttjava CaracteresUtilizados CADENA."); System.exit(0); } public static void elabora (String a) { String tmp = a; BitSet utilizados= new BitSet(); for (ent e = 0; e < tmp.length() ; e++) utilizados.set(tmp.charAt(i)); String out="["; ent dim=utilizados.size(); for (ent e = 0; e < dim; e++) { if (utilizados.get(i)) out+=(char )i; }; out+="]"; System.out.println(out); System.out.println("Para redactar he utilizado un bit set de "+utilizados.size()+" bit"); System.out.println ("tttPietro "); } } Analizamos otra clase di java.util, la clase Vector. Esta clase implementa unos array de Object. Lo interesante de los array del lenguaje, es que se puede modificar la largura del vector cuando se realiza la puesta en marcha. En Vector, además de los constructores, tenemos tres tipos de métodos, los que sirven para modificar el vector, los que sirven para obtener los valores incluídos en el vector, y los que sirven para gestionar el crecimiento del vector. Hay cuatro constructores: Vector(), construye un vector de tamaño 10, y que tiene la posibilidad de aumento igual a cero. Vector(Collection c), construye un vector que incluye los elementos de la colección especificada, en el mismo orden en el que aparecen en la colección. Vector(int initialCapacity), construye un vector tan grande como initialCapacity, con posibilidad de aumento igual a cero. Vector(int initialCapacity, int capacityIncrement), construye un vector grande initialCapacity, con la posibilida de aumento igual a capacityIncrement. Algunos métodos de la clase son: void add(int index, Object element), añade un elemento al vector en la posición indicada. boolean add(Object o), añade un elemento al final del vector. boolean addAll(Collection c), añade al final del vector los elementos de la colección especificada, en el mismo órden en el que aparecen en la colección. boolean addAll(int index, Collection c), añade los elementos de la colección en el vector empezando por el código especificado. Estos tres métodos se convierten en verdaderos si los elementos han sido añadididos.Se convierten en falsos si no tienen nada que ver. void addElement(Object obj), añade el elemento al vector y aumenta la capacidad de uno. int capacity(), da la capacidad actual del vector. void clear(), elimina todos los elementos que están en el vector. Object clone(), método utilizado generalmente para clonar el objeto Vector, que es supuestamente cloneable. boolean contains(Object elem), controla si el elemento especificado está en el vector. boolean containsAll(Collection c), controla si toda la colección especificada está en el vector. void copyInto(Object[] anArray), copia el contenido del objeto de tipo Vector en un array de objetos del lenguaje. Object elementAt(int index), devuelve el elemento del vector que se encuentra en la misma posición especificada. boolean equals(Object o), controla la identidad entre el vector y el objeto especificado. Object firstElement(), da el primer elemento del vector en posición 0. Object get(int index), da el elemento del vector que se encuentra en la posición especificada y lo borra del vector. int hashCode(), da el código hash del vector. int indexOf(Object elem), busca el primer dato elemento en el vector, y devuelve el índice. Con primer dato me refiero supuestamente al del índice más bajo. int indexOf(Object elem, int index), busca el primer dato elemento en el vector empezando por el índice especificado y devuelve el índice. void insertElementAt(Object obj, int index), incluye el objeto especificado en la posición que queramos del vector. boolean isEmpty(), controla si el vector está vacío. Object lastElement(), da el último componente del vector. int lastIndexOf(Object elem), da el índice del último elemento del objeto especificado en el vector. int lastIndexOf(Object elem, int index), como antes empezando por el índice especificado y moviéndose hacia detrás. Object remove(int index), borra el elemento que se encuentra en la posición indicada en el vector. boolean remove(Object o), borra el primer dato en el vector del objeto especificado. boolean removeAll(Collection c), borra del vector todos los elementos incluidos en la colección especificada void removeAllElements(), borra todos los elementos del vector. boolean removeElement(Object obj), borra el primer dato del objeto especificado en el vector. void removeElementAt(int index), borra el elemento en la posición especificada. boolean retainAll(Collection c), borra todos los elementos del vector que están incluidos en la colección especificada. Object set(int index, Object element), sustituye los elementos en la posición especificada del vector con el elemento que se le da como parámetro (pone el element en la dirección index). void setElementAt(Object obj, int index), pone el objeto especificado en la posición que queremos. void setSize(int newSize), ajusta el nuevo tamaño del vector. int size(), da el número de posiciones en el vector. List subList(int fromIndex, int toIndex), crea un listado con los elementos del vector que se encuentran en la posición especificada come inicio o como final. List es otra estructura de datos muy importante que Java lleva ya implementada. Object[] toArray() e Object[] toArray(Object[] a), crean un array del lenguaje con los elementos incluidos en el vector. toString(), da una representación bajo forma de cadena del vector que incluye las representaciones como cadena de todos los elementos incluidos en el vector. Prácticamente invoca el toString de todos los objetos. Los que estén acostumbrados a programar con otros lenguajes de programación podrán apreciar la funcionalidad que a menudo el programador tiene que implementar él mismo de forma superficial. Lo que yo aprecio personalmente, como programador, es, además de la dinamicidad de la estructura de Vector, que siempre ha sido por definicón estática, la posibilidad de crear vectores con elementos heterogéneos. En realidad, éstos son vectores de objetos, y sabemos que los objetos de java pueden ser cualesquiera incluso programas. Por ejemplo, es posible crear un vector que incluya números enteros, valores booleanos y cadenas, lo que no se puede hacer con los demás array del lenguaje. Para que quede más claro voy a poner un ejemplo. Fijaos en este vector: tribilín = { true , 10 , "Hola"}; ¿De qué tipo será Tribilín? ent[]? boolean[]? O String[]? Ninguno de los tres. En Java es imposible crear un array hecho de esta forma, utilizando las famosas clases envoltorios, que, hasta ahora, nos habían parecido bastantes inútiles. Podemos crear un array y escribiremos. Object[] tribilín={new Boolean(true), new Integer(10), new String("Hola") }; Además, si utilizamos la clase Vector, tendremos muchos métodos para gestionar este vector de elementos heterogéneos. Pongamos un pequeño ejemplo que usa los vectores. Definimos un espacio geométrico tridimensional de puntos, líneas y caras, que se definen así: class punto { String nombre; ent x,y,z; public punto(String n, ent X,ent Y,ent Z) { x=X; y=Y; x=Z; nombre=n; } public String toString() { return "n"+nombre+"="+x+","+y+","+z+"n"; } } class línea { String nombre; punto inicio; punto fin; public línea(String n, punto i,punto f) { inicio=i; fin=f; nombre=n; } public String toString() { return "n"+nombre+"=("+inicio.toString()+","+fin.toString()+")n"; } } class cara { String nombre; punto x1; punto x2; punto x3; línea l1; línea l2; línea l3; public cara(String n, punto X1,punto X2,punto X3) { x1=X1; x2=X2; x3=X3; l1=new línea(n+"-línea1",x1,x2); l2=new línea(n+"-línea2",x3,x2); l3=new línea(n+"-línea3",x1,x3); nome=n; } public String toString() { return "n"+nombre+"={" +l1.toString()+ "," +l2.toString()+ "," +l3.toString()+"}n"; } } Las clases incluyen unos métodos toString(), que sobrescriben los métodos estándar para crear el outpu en el archivo del programa. Las clases las pondremos en un archivo llamado Geometría.java, donde pondremos también los import necesarios al programa y las clases Geometría que incluye el main que definimos a continuación: import java.util.*; import java.io.*; // Definicicón de la clase punto // Definicicón de la clase línea // Definicicón de la clase cara public class Geometría { public static ent NÚMERO = 3000; public static void main(String[] arg) { Vector espacio=new Vector(1,1); System.out.println("Geometría en el espacio:"); ent pti = NÚMERO; System.out.println(); System.out.println("Género "+pti+" objetos a caso por cada especie"); System.out.println("Cambiar la constante NÚMERO en Geometría para generar un número diferente."); ent lin=NÚMERO; ent car; car=NÚMERO; System.out.println(); ent d1=espscio.capacity(); System.out.println ("Capacidad del vector:"+espacio.capacity()); System.out.println("Redacto los puntos..."); ent e=0; for ( e = 0 ; e { float a=(float ) Math.random(); float b=(float ) Math.random(); float c=(float ) Math.random(); ent x=Math.round(a*1000); ent y=Math.round(b*1000); ent z=Math.round(c*1000); String nome="Punto"+(e+1); punto tmpP=new punto(nombre,x,y,z); espacio.addElement(tmpP); }; System.out.println ("Capacidad del vector:"+espacio.capacity()); ent d2=espacio.capacity(); System.out.println("Redacto las líneas..."); for ( e = 0 ; e { float a=(float ) Math.random(); float b=(float ) Math.random(); float c=(float ) Math.random(); float d=(float ) Math.random(); float e=(float ) Math.random(); float f=(float ) Math.random(); ent x=Math.round(a*1000); ent y=Math.round(b*1000); ent z=Math.round(c*1000); ent x1=Math.round(d*1000); ent y1=Math.round(e*1000); ent z1=Math.round(f*1000); String nombre="Línea"+(i+1); punto P1=new punto ("Punto 1 del "+nombre,x,y,z); punto P2=new punto ("Punto 2 del "+nombre,x1,y1,z1); linea tmpL=new linea(nombre,P1,P2); espacio.addElement(tmpL); }; System.out.println ("Capacidad del vector:"+espacio.capacity()); ent d3=espacio.capacity(); System.out.println("redacto las caras..."); for ( e = 0 ; e { float a=(float ) Math.random(); float b=(float ) Math.random(); float c=(float ) Math.random(); float d=(float ) Math.random(); float e=(float ) Math.random(); float f=(float ) Math.random(); float g=(float ) Math.random(); float h=(float ) Math.random(); float j=(float ) Math.random(); ent x=Math.round(a*1000); ent y=Math.round(b*1000); ent z=Math.round(c*1000); ent x1=Math.round(d*1000); ent y1=Math.round(e*1000); ent z1=Math.round(d*1000); ent x2=Math.round(g*1000); ent y2=Math.round(h*1000); ent z2=Math.round(j*1000); String nome="Cara"+(e+1); punto P1=new punto ("Punto 1 del "+nombre,x,y,z); punto P2=new punto ("Punto 2 del "+nombre,x1,y1,z1); punto P3=new punto ("Punto 1 del "+nombre,x2,y2,z2); cara tmpF=new cara(nombre,P1,P2,P3); espacio.addElement(tmpF); }; System.out.println ("Capacidad final del vector:"+espacio.capacity()); ent d4=espacio.capacity(); File FileOut=new File("Geometría.txt"); FileWriter Output; try {Output=new FileWriter(FileOut);} catch (IOException e) {Output=null;}; try { Output.write("Geometría.txtnnúmero objetos="+3*NÚMERO+"n"); Output.write("Tamaño del Vector:al principio:"+d1+"n después de la inclusión de los números:"+d2); Output.write("ndespués de la inclusión de las líneas:"+d3+"ndespués de la inclusión de las caras:"+d4); Output.write("nnContenido:n"); Output.write(espacio.toString()); Output.write("nnnttPietro Castellucci"); Output.flush(); } catch (IOException e) {}; System.out.println("nLee el archivo Geometría.txt.n"); }; } Si ponemos en marcha el programa veremos que el Vector espacio aumenta dinámicamente con los objetos heterogéneos punto, línea y cara. Finalmente tendremos un archivo llamado Geometría.txt que incluye la descripción del vector. El archivo con 5 elementos por cada especie es algo como: Geometría.txt número objetos=15 Tamaño del Vector: al principio:1 después de la inclusión de los puntos:5 después de la inclusión de las líneas:10 después de la inclusión de las caras:15 Contenido: [ Punto1=653,932,0 , Punto2=100,273,0 , Punto3=855,210,0 , Punto4=351,702,0 , Punto5=188,996,0 , Línea1=( Punto 1 de la Línea1=680,454,0 , Punto 2 de la Línea1=69,16,0 ) , Línea2=( Punto 1 de la Línea2=116,651,0 , Punto 2 de la Línea2=371,15,0 ) , Linea3=( Punto 1 de la Línea3=947,335,0 , Punto 2 de la Línea3=477,214,0 ) , Línea4=( Punto 1 de la Línea4=391,671,0 , Punto 2 de la Línea4=692,725,0 ) , Línea5=( Punto 1 de la Línea5=762,283,0 , Punto 2 de la Línea5=582,192,0 ) , Cara1={ Cara1-línea1=( Punto 1 de la Cara1=826,235,0 , Punto 2 de la Cara1=13,378,0 ) , Cara1-línea2=( Punto 1 de la Cara1=12,950,0 , Punto 2 de la Cara1=13,378,0 ) , Cara1-línea3=( Punto 1 de la Cara1=826,235,0 , Punto 1 de la Cara1=12,950,0 ) } , Cara2={ Cara2-línea1=( Punto 1 de la Cara2=382,30,0 , Punto 2 de la Cara2=224,597,0 ) , Cara2-línea2=( Punto 1 de la Cara2=277,361,0 , Punto 2 de la Cara2=224,597,0 ) , Cara2-línea3=( Punto 1 de la Cara2=382,30,0 , Punto 1 de la Cara2=277,361,0 ) } , Cara3={ Cara3-línea1=( Punto 1 de la Cara3=139,802,0 , Punto 2 de la Cara3=880,935,0 ) , Cara3-línea2=( Punto 1 de la Cara3=643,921,0 , Punto 2 de la Cara3=880,935,0 ) , Cara3-línea3=( Punto 1 de la Cara3=139,802,0 , Punto 1 de la Cara3=643,921,0 ) } , Cara4={ Cara4-linea1=( Punto 1 de la Cara4=516,614,0 , Punto 2 de la Cara4=429,210,0 ) , Cara4-línea2=( Punto 1 de la Cara4=979,860,0 , Punto 2 de la Cara4=429,210,0 ) , Cara4-línea3=( Punto 1 de la Cara4=516,614,0 , Punto 1 de la Cara4=979,860,0 ) } , Cara5={ Cara5-línea1=( Punto 1 de la Cara5=152,663,0 , Punto 2 de la Cara5=828,101,0 ) , Cara5-linea2=( Punto 1 de la Cara5=651,761,0 , Punto 2 de la Cara5=828,101,0 ) , Cara5-línea3=( Punto 1 de la Cara5=152,663,0 , Punto 1 de la Cara5=651,761,0 ) } ] Pietro Castellucci Intentad fijar la constante NÚMERO a 4000 y poned en marcha el programa. Como habéis visto hay muchas estrucuturas de datos en java.util, y describirlas todas sería demasiado trabajo. Lo que nos interesa es que todas tienen métodos para introducir, quitar, recuperar y métodos para gestionar la estructura misma. Las estructuras de datos implementadas son tablas hash, listas, pile, code, árboles etc… Según vuestras exigencias podeís utilizar una u otra. Para tener más detalles, sin embargo, os aconsejo ojear la documentación del Java Development Kit, donde stán escritas todas estas clases. Por ahora os sirve con saber que las tablas hash son muy rápidas buscando un objeto. Por lo general, basta con un solo acceso a las estructuras de datos para encontrar el objeto que buscáis. Los listados son como los vectores. Los objetos están relaccionados entre sí, y desde un obbjeto es posible alcanzar el siguiente o el anterior o los dos, según la realización del listado . Los Pile son listados particulares, en los que se introducen objetos siempre al comienzo de la estructura y, de aquí, se pueden tomar. Por lo tanto en pila el último objeto que ha entrado es el primero que sale. Pensad en los objetos como si fueran unos documentos que hay despachar desde una oficina. Se ponen en órden en la mesa del encargado según sus llegadas, uno encima del otro. El encargado de despachar los documentos empieza siempre por el que está encima de la pila. Ésta puede parecer una estructura de datos tonta, pero os aseguro que es la más mportante de todas. Las utilizan todos los lenguajes de programación para invocar las funciones y los procedimientos. (Incluso Java la usa para invocar los métodos), para transladar los parámetros y recuperar los resultados. Sin esta estructura sería imposible programar de forma recursiva, que es una forma de programar muy potente. Las colas funcionan al revés. Se pueden comparar con las colas de los bancos. Los objetos son las personas que están en la cola delante de la ventanilla. Llegan y se ponen al final de la cola. Mientras tanto el empleado atiende a las personas que están delante. Tambiém esta estructura es muy importante porque algunas variantes (las colas de prioridad, en las que los objetos tienen prioridad, se atienden según esta característica, es decir, se cuelan). Se utilizan mucho en los sistemas operativos como Windows, Linux, Unix, etc. para gestionar las necesidades de imprenta de una sola impresora por parte de todos los usuarios del sistema. Antes de pasar al siguiente paquete, quiero mostraros una pequeña comparación sobre la búsqueda de un objeto entre muchos que están incluidos en distintas estructuras. Particularmente, en las estructuras de tipo vector y de tipo tabla hash. Veremos como los resultados de la búsqueda cambian de forma evidente aumentando el tamaño de las estructuras. Imaginemos unos objetos de este tipo: class O { String nombre=new String(); ent valor; public O(String a, ent v) { nombre=a; valor=v; } } Creamos un vector de 100000 de estos elementos. Buscamos uno y vemos el tiempo que tardamos: import java.util.*; class O { String nombre=new String(); ent valor; public O(String a, ent v) { nombre=a; valor=v; } } public class Rvector { public static ent NÚMERO = 100000; public static void main (String[] s) { Vector V = new Vector(NUMERO); ent numnombre=128756; O O_30000=null; System.out.println("Redacto el vector, introduzco "+NÚMERO+" objetos de tipo O con valores que no tienen relación con el índice."); System.out.print("Inicio: O_"+numnombre); for (ent e=0; i< NÚMERO;i++) { O tmp = new O("O_"+numnombre,numnombre); if (numnombre==30000) O_30000 = tmp; numnombre--; V.add(e,tmp); }; System.out.println(" Fin: O_"+numnombre); System.out.println("Inicio la búsqueda de O_30000"); long Inicio=System.currentTimeMillis(); ent index=V.indexOf(O_30000); O tmp=(O ) V.get(index); long Fine=System.currentTimeMillis(); System.out.println("Oggetto O_30000 trovato in "+(Fin-Inicio)+" millisec. al índice "+index); System.out.println("Vale:nNombre:"+tmp.nombre+"nValor:"+tmp.valor+"n"); } } El programa buscará el objeto O_30000. El output del programma es: Redacto el vector, introduzco 100000 objetos de tipo O con valores independientes del índice. Inicio: O_128756 Fin: O_28756 Empiezo la búsqueda de O_30000 Objeto O_30000 encontrado en 50 millisec. ….. Ahora hago lo mismo usando una estructura como la tabla hash: import java.util.*; class O { String nombre=new String(); ent valor; public O(String a, ent v) { nombre=a; valor=v; } } public class Rhash { public static ent NÚMERO = 100000; public static void main (String[] s) { Hashtable H = new Hashtable(NUMERO+1); ent numnombre=128756; O O_30000=null; System.out.println("Redacto el cuadro, introduzco "+NÚMERO+" objetos de tipo O."); System.out.print("Inicio: O_"+numnombre); for (ent e=0; i< NÚMERO;e++) { O tmp = new O("O_"+numnombre,numnombre); if (numnombre==30000) O_30000 = tmp; numnombre--; H.put(tmp,tmp); }; System.out.println(" Fin O_"+numnombre); System.out.println("Inicio la búsqueda de O_30000"); long Inizio=System.currentTimeMillis(); O tmp=(O ) H.get(O_30000); long Fine=System.currentTimeMillis(); System.out.println("Obbjeto O_30000 encontrado en "+(Fin-Inicio)+" millisec. "); System.out.println("Vale:nNombre:"+tmp.nombre+"nValor:"+tmp.valor+"n"); } } Otra vez el programa buscará el objeto O_30000. El output del programma es: Redacto el vector, introduzco 100000 objetos de tipo O. Inicio: O_128756 Fin: O_28756 Empiezo la búsqueda de O_30000 Objeto O_30000 encontrado en 0 millisec. ….. El paquete java.util no incluye sólo estas utilísimas estructuras de datos, sino también clases que simplifican la gestión de fechas y horarios, para la internacionalización, y otras clases de utilidad como el string tokenizer. Coge de una cadena todas las palabras y unos generadores de números casuales. El paquete java.util En esta lección veremos la parte de java.util que trata de los utilísimos archivos .zip e i .jar Empezamos viendo java.util.zip Los archivos .zip son archivos que contienen unos archivos comprimidos y varios programas. Se utilizan sobre todo para cambiar datos en internet porque reducen notablemente los tamaños. Hay varios tipos de archivos comprimidos y varios programas para comprimir y ampliar los datos, pensemos en los archivos RAR, en los CAB de Windows, en los ARJ. Este paquete nos da la posibilidad de tratar datos comprimidos según los estándars ZIP y GZIP, que utilizan el algoritmo de compresión llamado DEFLATE. este paquete incluye también utility que controlan los códigos checksum CRC-32 y Adler-32 de un flujo arbitrario de entrada. ¿Para qué usar este paquete? Las razones son muchas. En primer lugar, los programas Java que incluyen imágenes, animaciones y sonidos, pueden ser realmente grandes y es posible crear un archivo .ZIP con todos los archivos necesarios para que funcione. De esta forma se disminuye el tamaño del mismo. Pero, para que se puedan utilizar por el programa, primero tienen que descomprimirse y este paquete nos ofrece esta posibilidad. Otra razón para usar este paquete es que la compresión de datos en informática es un problema bastante complejo. En esta operación se utilizan unos algoritmos que se basan en el álgebra de los números, tema no muy conocido fuera de las facultades de Ciencias. Por esta razón, son unos algoritmos bastantes incomprensibles para los que no tienen práctica en el asunto. Este paquete ofrece la posibilidad a todo el mundo de comprimir y ampliar estos datos. Veamos, entonces, lo que incluye el paquete java.util.zip Interfaces Checksum, es un interfaz que representa el código checksum de los datos. Clases CheckedInputStream, es un flujo de entrada que trata también el checksum de los datos de entrada. CheckedOutputStream, es un flujo de salida que trata también el checksum de los datos en salida. CRC32, clase usada para calcular el código checksum de tipo CRC-32 de un flujo de datos. Deflater, clase que se ocupa de las compresiones de los datos utilizando la compresión según la biblioteca ZLIB. DeflaterOutputStream, flujo de salida que comprime los datos utilizando el algoritmo Deflate. GZIPInputStream, filtro para el stream de ingreso para leer datos comprimidos según el formato GZIP. GZIPOutputStream, filtro para el flujo de salida para escibir datos con el código zip utilizando GZIP. Inflater, soporte para la compresión de tipo ZLIB. InflaterInputStream, filtro para el flujo de entrada, para ampliar datos comprimidos según el algoritmo Deflate. ZipEntry, utilizada para representar un archivo de entrada de tipo ZIP. ZipFile, utilizada para leer el contenido de un archivo ZIP. ZipInputStream, utilizada para leer los archivos contenidos en un archivo ZIP. ZipOutputStream, utilizada para escibir datos comprimidos de formato ZIP. Excepciones DataFormatException, error de formato de los datos. ZipException Cada una de estas clases tendrá sus métodos. Para conocerlos todos os remito a la documentación del JDK porque sólo analizaremos algunos con un pequeño ejemplo. El siguiente programa abre el directorio en el que se encuentra y busca todos los archivos .zip, por cada archivo que encuentra y mira el contenido. import java.util.*; import java.util.zip.*; import java.io.*; public class ReadZip { public static void main(String [] a) { File dir=new File("."); System.out.println("Abro el directorio "+dir.getAbsolutePath()); File[] cont=dir.listFiles(); ent MAX=cont.length; for (ent e = 0; i { String tmp=cont[i].getName(); if ((tmp.endsWith(".zip"))||(tmp.endsWith(".ZIP"))) { // es un archivo .zip System.out.println("He encontrado "+tmp); controlaZip(cont[i]); }; } } public static void controlaZip(File f) { System.out.println("Contenido del archivo "+f.getName()); ZipFile Zf; try {Zf=new ZipFile(f);} catch (ZipException e){Zf=null;} catch (IOException e1){Zf=null;} ; Enumeration files=Zf.entries(); while(files.hasMoreElements()) System.out.println(files.nextElement()); } } Además podemos ampliar estos archivos. El siguiente programa recoge todos los .zip del directorio donde se pone en marcha y los amplia. import java.util.*; import java.util.zip.*; import java.io.*; public class Decomp { public static void main(String [] a) { File dir=new File("."); System.out.println("Abro el directorio "+dir.getAbsolutePath()); File[] cont=dir.listFiles(); ent MAX=cont.length; for (ent e = 0; i<MAX; e++) { String tmp=cont[i].getName(); if ((tmp.endsWith(".zip"))||(tmp.endsWith(".ZIP"))) { // es un archivo .zip System.out.println("He encontrado "+tmp); try {controlaZip(cont[i]);} catch (IOException e){}; }; } } public static void controlaZip(File f) throws IOException { System.out.println("Ampliación del archivo "+f.getName()+":"); ZipFile Zf; try {Zf=new ZipFile(f);} catch (ZipException e){Zf=null;} catch (IOException e1){Zf=null;} ; Enumeration files=Zf.entries(); while(files.hasMoreElements()) { ZipEntry tmpFile=(ZipEntry ) files.nextElement(); System.out.println("amplio el archivo "+tmpFile.getName()); System.out.println("tamaño comprimido "+ tmpFile.getCompressedSize()+" tamaño no comprimido "+ tmpFile.getSize()+" CRC "+tmpFile.getCrc()); System.out.println("modificado "+tmpFile.getTime()); InputStream in= Zf.getInputStream(tmpFile); FileOutputStream out= new FileOutputStream(tmpFile.getName()); for (ent ch=in.read();ch!=-1;ch=in.read()) out.write(ch); out.close(); in.close(); } } } El paquete java.util.jar se pone a disposición del programador de las clases y de los interfaces para tratar los archivos de tipo Java Archive (JAR), sobre todo es posible leerlos y escribirlos. Los archivos JAR se basan en el estándar ZIP, con un archivo opcional llamado manifest . El contenido del paquete es el siguiente: Clases Attributes Attributes.Name JarEntry JarFile JarInputStream JarOutputStream Manifest Excepciones JarException Os remito a la documentación del JDK para más información. El paquete java.net Java, como ya dijimos anteriormente, nació como lenguaje para la red y sólo sucesivamente se convirtió en un verdadero lenguaje de programación. Su papel de líder para la programación no se pone en duda y, por eso, pone a disposición del programador diferentes paquetes para llevar a cabo esta programación. Como el objetivo final del curso es programar Applet, tenemos que verlo aunque sea de modo superficial. El paquete es muy amplio y su contenido es el siguiente: Interfaces ContentHandlerFactory FileNameMap SocketImplFactory SocketOptions URLStreamHandlerFactory Clases Authenticator ContentHandler DatagramPacket DatagramSocket DatagramSocketImpl HttpURLConnection InetAddress JarURLConnection MulticastSocket NetPermission PasswordAuthentication ServerSocket Socket SocketImpl SocketPermission URL URLClassLoader URLConnection URLDecoder URLEncoder URLStreamHandler Excepciones BindException ConnectException MalformedURLException NoRouteToHostException ProtocolException SocketException UnknownHostException UnknownServiceException De todo esto nosotros veremos sólo algo. Para empezar vamos aver qué pasa en la red. Los ordenadores en Internet comunican intercambiando paquetes de datos, llamados paquetes IP (Internet Protocol). Estos paquetes salen de un ordenador, pasan por varios nudos de la red (Servidor de la red) y llegan a su destino. Para establecer el recorrido intermedio entre los dos ordenadores que quieren comunicare se pone en marcha un algoritmo de routing (hay varios tipos según las exigencias y según el tipo de red). Para establecer el remite de una comunicación, los destinatarios, los nudos internet, se necesita que cada ordenador conectado a la red tenga un nombre que lo identifique unívocamente. Este nombre es un número y se llama dirección IP. La dirección IP es un número de 32 bit, que se puede escribir con varios formatos, sin embargo, el más utilizado es el formato decimal separado por puntos. Por ejemplo, una dirección IP es 594. 24.114.462 (Se ha elgido un número cualquiera). Como los ordenadores recuerdan muy bien los números, pero nosotros los humanos no, se inventaron los DNS (Domain Name System) que asocian unos nombres a estas direcciones IP. La primera clase del paquete que analizaremos es la InetAddres, que gestiona estas direcciones IP. La clase tiene varios métodos, entre los que hay uno que devuelve las direcciones Ip del ordenador en el que se está trabajando, otro que, fijado un nombre de dominio, devuelve la dirección IP. El siguiente ejemplo nos muestra el uso. import java.net.*; public class DireccionesIP { public static void main(String [] a) { String dom="developer.java.sun.com"; try { InetAddress loc=InetAddress.getByName(dom); System.out.println("IP de "+dom+" : "+loc.getHostAddress()); } catch (UnknownHostException e) {System.out.println("No existe el dominio "+dom);}; try { InetAddress loc=InetAddress.getLocalHost(); System.out.println("IP local: "+loc.getHostAddress()); System.out.println("Nombre local"+loc.getHostName()); } catch (UnknownHostException e) {}; } } Como ejercicio, coged como nombre de dominio el primer tema del programa, por ejemplo,java DireccionesIP HTMLpoint El paquete dota de clases útiles para tratar los socket fundamentales basados en TCP y en UDP, que son protocoles utilizados en la mayor parte de las aplicaciones Internet. Nosotros no los analizaremos, sino que pasaremos directamente a las clases que gestionan aplicaciones Web de nivel más alto. Veamos la clase URL. ¿Qué es un URL? Un URL, o Uniform Resource Locator, es un puntero a un recurso web. Un recurso web puede ser cualquier cosa, un directorio, un archivo, un objeto en red como, por ejemplo, una interfaz para hacer unas query a un database remoto, o para un motor de búsqueda. Los URL son muchos, cada uno con un protocolo distinto, sin embargo los más utilizados son los que utilizan protocolos HTTP (HyperText Transfer Protocol) y FTP (File Transfer Protocol). La clase URL se conecta con objetos web accediendo a éstos a través sus direcciones URL. Vosotros que sois usuarios de HTMLpoint no necesitáis más explicaciones sobre el formato de las direcciones URL como http://www.cli.di.unipi.it/~castellu/index.html y que el index.html es opcional, es decir que la dirección URL http://www.cli.di.unipi.it/~castellu es igual a la anterior, que ~castellu es un directorio que se encuentra en el servidor, y que la dirección del servidor se identifica como http://www.cli.di.unipi.it . Sin embargo, os digo que, a diferencia de los web browser, l'http:// delante de la dirección es indispensable porque distingue el protocolo del URL, protocolo que el Navigator y el Explorer intuyen aunque se omitan. Cuando programemos los apliques utilizaremos sólo los URL para acceder también a los archivos locales, viéndolos como recursos de la red. Lo haremos porque en los apliques no se pueden utilizar los archivos, por razones de seguridad, entonces para leer un archivo hay que verlo como un recurso de la red. Las últimas versiones de Java están eliminando esta limitación del lenguaje, utilizando unas firmas que permiten leer y escribir un archivo de forma controlada incluso en la red. Por eso en futuro será posible utilizar incluso los archivos en los archivos, siempre que se asuman algunas responsabilidades. Veamos unos constructores de objetos URL: URL(String spec) , crea un objeto URL según la representación a cadena, por ejemplo "HTMLpoint" URL(String protocol, String host, int port, String file), crea un objeto URL, especificándolo todo, incluso la puerta. URL(String protocol, String host, int port, String file, URLStreamHandler handler), como el anterior, lo único que especifica también la URLStreamHandler, que es la superclase común a todos los protocolos(HTTP,FTP,Gopher). URL(String protocol, String host, String file), crea un objeto URL especificando el protocolo, el host y el Archivo en el servidor host, por ejemplo: URL("http","www.cli.di.unipi.it","~castellu/index.html"). Analizamos ahora unos métodos de la clase boolean equals(Object obj), compara dos objetos URL. Object getContent(), da el contenido del objeto URL. String getFile(), da el nobre del archivo del URL. String getHost(), el host int getPort(), el número de la puerta String getProtocol(), el nombre del protocolo. String getRef(), da el puntero a la URL. int hashCode(), da el código hash del objeto. URLConnection openConnection(), abre una conexión con el objeto remoto indicado por la URL. InputStream openStream(), abre una conexión con el objeto web indicado por la url en forma de flujo de lectura. String toExternalForm(), da una cadena que representa la URL. String toString(), da una representación del objeto URL. A continuación damos un pequeño ejemplo de cómo se utiliza la clase URL. import java.net.*; import java.io.*; public class getPage { public static void main(String[] arg) { String un; try {un=arg[0];} catch (ArrayIndexOutOfBoundsException e) { un="http://www.htmlpoint.com/index.asp"; System.out.println("Ninguna URL definida, cojo "+un); }; System.out.println("URL:"+un); URL url; boolean tribilín=false; try {url= new URL(un);} catch (MalformedURLException e) { System.out.println("URL equivocado, cojo http://www.htmlpoint.com/index.asp "); url = null; tribilín=true; }; if (tribilín) try {url = new URL ("http://www.htmlpoint.com/index.asp ");} catch (MalformedURLException e){}; BufferedReader stream; try {stream = new BufferedReader (new InputStreamReader (url.openStream()));} catch (IOException e){ System.out.println("Error de apertura del archivo"); stream=null; System.exit(0); }; File out=new File("."+url.getFile()); FileWriter Output; try {Output=new FileWriter(out);} catch (IOException e) {Output=null;}; String l; try { while ((l=stream.readLine())!=null) { Output.write(l); }; Output.flush(); Output.close(); } catch (IOException e){System.out.println("Error de lectura.");}; } } El programa coge una url de los parámetros de entrada; si éste no es válido o no hay parámetros, abre por default la url http://HTMLpoint/index.asp. Entonces recoge la página que ha leído y la guarda en el disco. Como se puede ver, esto se parece a un web browser, utilizando un poco de gráfica se podría visualizar incluso la página. Intentad poner en marcha el programa poniendo las siguientes direcciones: java getPage http://www.cli.di.unipi.it/~castellu/index.htm java getPage http://www.cli.di.unipi.it/~castellu/pietro1.htm java getPage http://www.cli.di.unipi.it/~castellu/quiénsoy.htm y simplemente java getPage, y veréis los resultados. Conclusiones sobre los paquetes En este apartado hemos visto unos paquetes que contienen los API del lenguaje Java, pero hay más: java.applet , que analizaremos en el próximo capítulo, sirve para crear unos programas que trabajan en los web browsers, llamados applet. java.awt , este paquete y sus subpaquetes implementan las clases para, a su vez, implementar los controles GUI, para implementar interfaces gráficas, además de instrumentos para el dibujo, manipulación de las imágenes, imprimir y otras funciones. Lo veremos en el próximo capítulo java.beans, paquete que permite definir los componentes Java y utilizarlos en otros programas. java.rmi, paquete para la invocación de métodos remotos, es decir, de métodos de objetos que se encuentran en cualquier lugar en la red, para construir unas aplicaciones distribuidas. java.security, hay clases que implementan las funciones de seguridad como, por ejemplo, las clases utilizadas para criptografar documentos antes de enviarlos a la red. java.sql, interfaz entre el lenguaje Java y el lenguaje para base de datos SQL. java.text, clases muy útiles para la interacción. javax.accessibility, clases que apoyan las tecnologías que facilitan a los usuarios no aptos. javax.swing, es una extensión de java.awt para construir apliques y aplicaciones gráficas: es prodigioso. org.omg.CORBA, permite relacionar el lenguaje Java con el lenguaje CORBA. Otra vez os animo, para saber más, a controlar la documentación del JDK, disponible On line, tanto para descargarla como para consultarla, en el sito de la Sun Microsystem www.sun.com . Estos son los paquetes estándar del lenguaje Java. A estos se suman las extensiones estándar del lenguaje. Las extensiones estándar son paquetes que, en las próximas versiones de Java, se convertirán en paquetes estándar y que, hasta ahora, son versiones beta. Swing fue la extensión hasta que salió Java2. Ahora, de hecho, se utilizan más las viejas awt. API Servlet, está destinada a la programación de aplicaciones del servidor en Java. API está formada por paquetes javax.servlet y javax.servlet.http. Java 3D, gestiona el dibujo tridimensional y es parecida a la versión Java de OpenGL (JavaGL), la famosísima biblioteca de la SGI (alguien la conocen como Glide, es decir, como la biblioteca OpenGL por las 3Dfx) y DirectX de Microsoft. Se puede bajar del sito: http://java.sun.com/products/java-media/3D/index.html Java Media Framework, gestiona, en los programas Java, varios formatos audio, video y multimedial. Los archivos que los ayudan son los siguientes: .mov, .avi, .viv, .au, .aiff, .wav, .midi, .rmf, .gsm, .mpg, .mp2, .rtp. Si no toda, por lo menos una parte se convertirá en estándar con Java 1.3 que está a punto de salir al mercado (finales de abril). Se baja del sito: http://www.javasoft.com/products/java-media/jfm/index.html Speech, funciones de reconocimiento vocal, no sólo para las órdenes, sino que es posible también editar archivos enteros. Este paquete hace también el output vocal. Se puede bajar del sito: http://java.sun.com/products/java-media/speech/index.html Telephony, funciones de telefonía y fax. JavaMail, clases para gestionar el correo electrónico. Java Naming and Directory Services, para acceder a los servicios de nombres y directorio utilizando protocoles como LDAP. este paquete se ha convertido en estándar en JDK 1.3 Java Management, para la gestión de redes locales. JavaSpaces, más clases para la creacción de aplicaciones distribuidas. JavaCommerce, para el comercio electrónico. Personalmente no veo la hora de que se conviertan en estándar las API Java 3D, Java Media Framework, JavaSpeech y Java Telephony, porque las funciones que prometen estas API son realmente excepcionales. Utilizarlas ahora es posible, pero con cierto riesgo. Realmente son todavía versiones beta y, por eso, llenas de errores. Además si se quieren escribir apliques utilizando estas nuevas funciones, esto es posible, pero para ponerlos en marcha se necesita el apliqueviewer del JDK, porque seguramente el Java implementado en los web browser todavía no las tiene. Hay que pensar que Swing se ha convertido en un paquete estándar del lenguaje, pero existen todavía unos browser que no lo tienen. Interfaces gráficas y sucesos Por fin llegamos a la programación de interfaces gráficas, es decir, a la creacción de apliques y de aplicaciones a ventanas. En primer lugar, intentamos establecer qué es un interfaz y qué quiere decir hacerla gráfica. Cada programa, como ya dijimos, se puede ver como un objeto que calcula una función, es decir, que coge unos datos del exterior y devuelve unos resultados. Por ejemplo, pensemos en un simple programa que calcula la suma de dos números. Este programa cogerá como input dos números y devolverá como output un solo número que representa la suma de los primeros dos. Lo que acabamos de decir es válido en general para todas las aplicaciones y no es un caso específico del ejemplo anterior. Pensemos en un videojuego: el input lo dará la palanca de control, y el output sará la gráfica que aparece en la pantalla. Por esta razón, conceptualmente tanto el videojuego como el programa suma, como cualquier programa que un programador pueda inventarse, son funciones calculadas sobre unos datos de entrada que devuelven resultados. La interfaz del programa hacia el usuario que la utiliza es la manera en que el programa coge los datos del usuario y le devuelve los resultados. Hasta ahora vimos unas interfaces de texto, el caso de la suma de los números en las que los datos se cogían del input estándar con una System.in.read() y los resultados se imprimían en el output estándar con una System.out.print(). Las System.in y out representan una interfaz del programa hacia el exterior, en este caso, hacia el usuario. Otras interfeces con el exterior que ya hemos analizado, son los archivos que representan interfeces de entrada o de salida hacia usuarios u otros programas. Estableciendo una pequeña comparación, no tan imposible, entre un programa y el hombre, podemos decir que la interfaz en entrada del cerebro está representada por la vista, el tacto, el gusto, el oído y el olfato, mientras la interfaz de salida está representada por la palabra y el movimiento de los músculos. Notar que esto no es algo muy raro porque los robots, aunque no tienen los mismos canales de entrada que los hombres (tienen la vista, una especie de tacto, algo que indica la proximidad) y de salida (a menudo hablan, en lugar de los músculos tienen motores eléctricos conectados a brazos mecánicos, pinzas, etc...), tienen el mismo funcionamiento del hombre. Realizan acciones (dan un ouput) consecuentemente a unos estímulos (señas de input), aunque por supuesto el hombre es mucho más complejo gracias a su capacidad de pensamiento que todavía la inteligencia artificial no consigue alcanzar totalmente. Realmente un robot no puede decidir nada al conocer sólo informaciones parciales sobre un problema, en cambio el hombre puede hacerlo porque es capaz de formular hipótesis, etc.…). Por tanto, la interfaz está compuesta por todos los canales de los que un programa recoge informaciones y hacia las que devuelve unos resultados. Algunos datos de entrada sirven para que cambie sólo el estado del programa, otros, en cambio, dan inmediatamente un output. Nosotros los consideramos todos datos en los que se calculan funciones. Realmente una función se calcula también teniendo en cuenta el estado del programa y, también, el estado y, por consiguiente, el input que le ha cusado es un input de la función. Aclaro este tema con un ejemplo. Pensemos en un programa que calcula dos funciones, una de suma y otra de sustracción entre dos números, y pensemos en un input que toma un número para elegir un estado (1 o 2), y después dos números. A estos dos números se les aplica la función suma o la función sustracción según el estado del programa. Poe ejemplo: INPUT: <1,10,10> à OUTPUT: 20 INPUT:<2,10,10> à OUTPUT: 0 En el ejemplo, el input sobre el estado se usa para calcular el output. Lo que nos interesa es la interfaz del usuario. Vimos como los programas pueden relaccionarse con otros programas o con periféricas (enviando, por ejemplo, órdenes a una impresora), pero esto no nos interesa. Nos interesa en cambio ver todo lo que le sirve al programa para dialogar con el usuario, es decir, la llamada interfaz del usuario. En este caso nos interesa ver la interfaz gráfica del programa, es decir, la interfaz que hace mucho más agradable el programa al usuario, en lugar de la gris interfaz de caracteres que vimos antes. La interfaz gráfica del programa está compuesta por los llamados componentes GUI (Graphics User Interface), que son componentes que sirven para el input o el output y tienen un aspecto gráfico. Son, por ejemplo, unos componentes GUI de un programa, todos los menús, los pulsadores, las etiquetas, etc…. Todas estas GUI estrán, obviamente, conectadas a una estructura que las incluye. Las dos estructuras que analizaremos, ya que son las más importantes, son los apliques y las ventanas. Cada GUI es, entonces, un trocito de interfaz gráfica, reciben los sucesos y, de acuerdo con éstos, dan unos resultados. Pensemos en un pulsador: se puede clicar o dejar de clicar. Cuando un usuario programa una interfaz gráfica que incluye un pulsador tendrá que gestionar incluso los sucesos asociados a éste, es decir, tendrá que decir qué ocurre cuando se clica el pulsador, cuando se deja de clicar, cuando le pasamos el ratón encima, etc. Cada GUI tendrá un tipo de suceso asociado, algunos los gestionan automáticamente las clases que se amplian para incluir el GUI en la ventana (por ejemplo, la modificación gráfica del pulsador clicado que se pone en evidencia); otros los puede definir el usuario (como el clic de un pulsador, si no hay gestor no ocurre nada) y otros los tiene que definir el usuario (como todos los sucesos del teclado cuando queremos escuchar por lo menos uno de éstos, por ejemplo, nos interesa programar cómo se pulsa una tecla, tenemos que gestionar incluso la presión, etc.…). Cada tipo de suceso, para cada tipo de componente GUI, tiene que ser declarado como escucha del suceso. Es un programa que espera que el suceso ocurra sobre el componente y cuando esto ocurre, lo gestiona. La programación de interfaces gráficas es, por esta razón, diferente de la normal programación porque se dibujan las interfaces y luego se gestionan los suceos que llegan, mientras que en las normales aplicaciones había un main que representaba todo el programa. La gestión de los sucesos en Java ha cambiado de la versión 1 a Java 2 (JDK 1.2 para arriba). Nosostros analizaremos la nueva gestión de los sucesos, la de Java 2 porque la primera fue cambiada porque no se entendía a que componente se asocia el suceso. En nuestra explicación de los componentes GUI analizaremos cómo definirlos, cómo inicializarlos, cómo introducirlos en una ventana o en un aplique y, finalmente, cómo gestionar los sucesos. Como ya dijimos, Java 2 tiene dos colecciones de paquetes para las interfaces gráficas, java.awt, que ya existía en Java 1, y javax.swing, que salió con Java 2, construida sobre las AWT, que aumenta muchísimo las posibilidades para las interfaces gráficas. ¿Qué es una aplicación a ventanas? La aplicación a ventanas es el tipo de aplicación que más a menudo utilizamos cuando trabajamos con el ordenador. Es una aplicación que se pone en marcha de forma local, que utiliza como interfaz del usuario las tecnologías de las ventanas típica de los sistemas operativos Mac OS (en la que nació), Windows (todas sus variantes, Windows 3.1, Windows 95, 95-OSR2, 98, NT, 2000 y Millenium), XWindows (el servidor gráfico para Linux y Unix). Casi todos los programas que se utilizan actualmente son aplicaciones a ventanas. Lo son, por ejemplo, Word, Netscape, Explorer, Jbuilder, Visual Studio, WinZip, Paint, XV, etc… (Todos pertenecen a los correspondientes productores). Por lo tanto veremos cómo se crea una Ventana usando el paquete AWT. Como siempre creamos una aplicación con el main, como hicimos anteriormente. Sin embargo, la clase que creamos ahora ampliará la clase java.awt.Frame, que representa la ventana completa, incluye el título, los pulsantes de reducción a icono, maximizar y cerrar. La clase Frame tiene varios métodos y constructores que veremos dentro de poco. Ahora vamos a crear nuestra primera ventana con el título Primera Ventana y que no incluye nada. La editamos en Ventana.java. import java.awt.*; public class Ventana extends Frame { public Ventana() { super("Primera Ventana"); setLocation(100,100); setSize(200,100); show(); } public static void main(String[] arg) { new Ventana(); System.out.println("he creado la ventana"); } } Al principio, como para cada aplicación, se invoca el main que crea un nuevo objeto de tipo Ventana. (El main y la ventana podrían estar en dos archivos separados, uno que gestiona el main, y el otro que gestiona la ventana). En el constructor del objeto ventana se invoca al constructor de la superclase, es decir, del frame, con la cadena "Primera Ventana" (es el título). Luego se invoca el método setLocation que declara la posición del ángulo derecho en la parte que está arriba de la ventana en el desktop, en este caso <100, 100> (Son la x y la y respectivamente. La x se mide partiendo del lado izquierdo de la pantalla y aumenta a medida que se va hacia la derecha. La y se mide partiendo de la parte superior de la pantalla y aumenta a medida que se va hacia abajo). Después se invoca el método setSize que permite especificar anchura y altura. En nuestro caso, tiene una anchura de 200 y una altura de 100. Finalmente se invoca el método show() y a continuación aparece la ventana en la pantalla. Las coordinadas de la pantalla no son las coorinadas cartesianas. realmente la y es precisamente opuesta porque aumenta a medida de que se baja y desminuye a medida de que se sube. Éste es un problema que tiene no sólo Java, sino que todos los lenguajes de programación y todas las bibliotecas para la gráfica Raster. Se debe a razones históricas, supongo, debidas a la forma de dibujar los pixel en pantalla a nivel de hardware. Hay que acostumbrarse, pero las primeras veces puede provocar problemas de razonamiento. Intentad redactar y poner en marcha el programa y cuando veáis vuestra primera ventana os animaréis. Sucesivamente eliminad, por turnos, los métodos setLocation, setSize y show, colocad también las ventanas en distintas posiciones y evaluad los cambios. En el ejemplo, se ven sucesos que el sistema gestiona automáticamente. Son Ir Resize de la Ventana y la presión de los pulsadores reduce a iconos y maximiza (minimiza), los cuales se gestionan en la clase Frame. En cambio, no se gestiona el suceso cerrar ventana (el pulsador de la x). Si se teclea no ocurre nada y para acabar la puesta en marcha del programa hay que ir al prompt del DOS desde el que empezó la aplicación y teclear CTRL+C (el exit para todos los programas DOS). Por último, creo que queda claro, la aplicación no funciona en ambientes que no son gráficos, es decir, en DOS y en Linux. Se necesita Windows (y el DOS cargado en una ventana, es decir, prompt de MS-DOS) o Xwindows con una shell abierta. Además no es verdad que a una aplicación se puede asociar a una solo ventana. Java es un lenguaje que permite la multiprogramación. Cada ventana es un programa independiente que trabaja contemporaneamente con otros. Por eso puedo crear, para la misma aplicación, más ventanas FRame, como en el ejemplo que ofrecemos a continuación. import java.awt.*; public class Ventanas extends Frame { public Ventanas(String Nombre, ent x, ent y) { super(Nombre); setLocation(x,y); setSize(200,100); show(); } public static void main(String[] arg) { System.out.println("Creo 4 ventanas solapadas"); for (ent e1;i<5;i++) new Ventanas("Ventana "e,10+(e*10), 10+(e*10)); System.out.println("Creo 4 ventanas a cuadros"); for (ent e=5;e<9;e++) new Ventanas("Ventana "+e,(e-5)*200, 100+(e-5)); System.out.println("He creado las ocho ventanas"); } } Las mismas cosas se podían hacer extendiendo la clase JFrame del paquete javax.swing. import javax.swing.*; public class VentanaSwing extends JFrame { public VentanaSwing() { super("Primera Ventana"); setLocation(100,100); setSize(200,100); show(); } public static void main(String[] arg) { new VentanaSwing(); System.out.println("He creado la ventana"); } } Este programa es como Ventana.java. lo único que amplía JFrame de swing. Las únicas diferencias son el contenido de la ventana, que esta vez es gris y antes era blanco, y el pulsador cerrar, que esta vez cierra la ventana (sólo la ventana y no toda la aplicación). Realmente la utilización de swing y de awt es muy parecida. Sin embargo, swing es más complejo y pone a disposición muchas más clases, y ofrece la posibilidad de cambiar el aspecto de las ventanas a runtime, etc. Desgraciadamente, no todos los web browser las pueden utilizar. Por eso, nosotros veremos las awt, y después hablaremos de las swing, dejando claro que los que quieran hacer un aplique para su propria página html hasta ahora TIENEN que utilizar las awt. Dentro de poco, cuando el XML se convierta en estándar y se tengan que cambiar los browser, se podrá utilizar con toda tranquilidad swing incluso para los apliques. Por lo tanto, vemos qué incluye la clase Frame. En primer lugar, los constructores son dos: Frame() , que crea un Frame sin ningún título, al principio invisible. Frame(String T), que crea un Frame con título T, también inicialmente invisible. Los atributos de la clase son: static int ICONIFIED static int NORMAL para indicar el estado de las ventanas y static int CROSSHAIR_CURSOR static int DEFAULT_CURSOR static int E_RESIZE_CURSOR static int HAND_CURSOR static int MOVE_CURSOR static int N_RESIZE_CURSOR static int NE_RESIZE_CURSOR static int NW_RESIZE_CURSOR static int S_RESIZE_CURSOR static int SE_RESIZE_CURSOR static int SW_RESIZE_CURSOR static int TEXT_CURSOR static int W_RESIZE_CURSOR static int WAIT_CURSOR Todos se declaran como deprecated, para los cursores, remplazados por la clase Cursor. Hereda la alineación de los componentes de Component. Los métodos son: void addNotify(), conecta el frame a un recurso de la pantalla y lo convierte en visible. int getCursorType(), declarado Deprecated. Es de la versión 1.1 de las JDK static Frame[] getFrames(), da un array que incluye todos los Frames creados por la aplicación. Image getIconImage(), da el icono del Frame. Es un objeto de tipo Icon, que analizaremos a continuación. MenuBar getMenuBar(), devuelve la barra de los menús del frame. Es un objeto de tipo MenuBar, que analizaremos a continuación. int getState(), da el estado del frame. String getTitle(), da el título del frame boolean isResizable(), da verdadero si el frame se puede agrandar y reducir con el ratón. protected String paramString() , da la cadena que incluye los parámetros del Frame. void remove(MenuComponent m), quita el MenuBar especificado por el frame. void removeNotify(), quita la conexión entre el frame y el recurso que representa la pantalla, convirtiéndolo en algo que no se puede visualizar. void setCursor(int cursorType), controla el cursor en JDK1.1. Se declara deprecated. void setIconImage(Image image), averigua el icono del Frame. void setMenuBar(MenuBar mb), asigna una MenuBar al frame. void setResizable(boolean resizable), averigua el Frame que puede cambiar dimensiones o no. Por default lo es. void setState(int state), averigua el estado del frame void setTitle(String title), averigua el título del frame. Frame es una clase que amplía java.awt.Window y, por eso, hereda los métodos y los atributos. Realmente Frame es una Window que añade un borde y un MenuBar. Los métodos heredados de windows son: addWindowListener, applyResourceBundle, applyResourceBundle, dispose, getFocusOwner, getInputContext, getLocale, getOwnedWindows, getOwner, getToolkit, getWarningString, isShowing, pack, postEvent, processEvent, processWindowEvent, removeWindowListener, show, toBack, toFront. Window amplía java.awt.Container, es un contenedor de objetos AWT. Es un componente que puede incluir otros componentes. Por lo tanto, por la propiedad de la transitividad, Frame hereda los métodos y los atributos. Los métodos son: add, add, add, add, add, addContainerListener, addImpl, countComponents, deliverEvent, doLayout, findComponentAt, findComponentAt, getAlignmentX, getAlignmentY, getComponent, getComponentAt, getComponentAt, getComponentCount, getComponents, getInsets, getLayout, getMaximumSize, getMinimumSize, getPreferredSize, insets, invalidate, isAncestorOf, layout, list, list, locate, minimumSize, paint, paintComponents, preferredSize, print, printComponents, processContainerEvent, remove, remove, removeAll, removeContainerListener, setFont, setLayout, update, validate, validateTree Container amplía java.awt.Component. Un Component es un objeto que tiene una representación gráfica, por ejemplo un cursor será una extensión de esta clase que amplía, a su vez, java.lang.Object Los atributos heredados por Component son: BOTTOM_ALIGNMENT, CENTER_ALIGNMENT, LEFT_ALIGNMENT, RIGHT_ALIGNMENT, TOP_ALIGNMENT mientras los métodos heredados por Component son: action, add, addComponentListener, addFocusListener, addInputMethodListener, addKeyListener, addMouseListener, addMouseMotionListener, addPropertyChangeListener, addPropertyChangeListener, bounds, checkImage, checkImage, coalesceEvents, contains, contains, createImage, createImage, disable, disableEvents, dispatchEvent, enable, enable, enableEvents, enableInputMethods, firePropertyChange, getBackground, getBounds, getBounds, getColorModel, getComponentOrientation, getCursor, getDropTarget, getFont, getFontMetrics, getForeground, getGraphics, getHeight, getInputMethodRequests, getLocation, getLocation, getLocationOnScreen, getName, getParent, getPeer, getSize, getSize, getTreeLock, getWidth, getX, getY, gotFocus, handleEvent, hasFocus, hide, imageUpdate, inside, isDisplayable, isDoubleBuffered, isEnabled, isFocusTraversable, isLightweight, isOpaque, isValid, isVisible, keyDown, keyUp, list, list, list, location, lostFocus, mouseDown, mouseDrag, mouseEnter, mouseExit, mouseMove, mouseUp, move, nextFocus, paintAll, prepareImage, prepareImage, printAll, processComponentEvent, processFocusEvent, processInputMethodEvent, processKeyEvent, processMouseEvent, processMouseMotionEvent, removeComponentListener, removeFocusListener, removeInputMethodListener, removeKeyListener, removeMouseListener, removeMouseMotionListener, removePropertyChangeListener, removePropertyChangeListener, repaint, repaint, repaint, repaint, requestFocus, reshape, resize, resize, setBackground, setBounds, setBounds, setComponentOrientation, setCursor, setDropTarget, setEnabled, setForeground, setLocale, setLocation, setLocation, setName, setSize, setSize, setVisible, show, size, toString, transferFocus Mientras que de Object se heredan los mismos métodos: clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait Algunos los analizaremos, mientras que para los demás os aconsejo consultar la documentación del JDK, en la que están descritas todas detalladamente. El diagrama de las extensiones de Frame es el siguiente: Llegados a este punto, se entiende por qué es importante la extensión de las clases en Java. Realmente en Frame se pueden invocar todos los métodos que analizamos antes. En cambio, el método de JFrame de swing es: Por lo tanto Jframe hereda todos lo métodos de Frame porque deriva de éste y, además, crea otros. ¿Qué es un aplique? Un aplique no es más que una aplicación Java que se encuentra en web. El aplique presenta unas diferencias con las aplicaciones porque no tienen ningún main, y son clases que tienen el mismo nombre que el del archivo que las incluye, que amplían la clase Applet del paquete java.applet. Incluso para los apliques existe la versión JApplet de swing, que se utiliza para introducir componentes Swing en lugar de componentes AWT. Un aplique necesita un archivo html que lo invoque. Por ejemplo, PrimoApplet.java, el aplique que queremos poner en marcha, lo redactamos y el compilador genera PrimoApplet.class. Para ponerlo en marcha necesitamos un archivo html que incluya el TAG en su interior: <applet code="PrimoApplet.class" ></applet> Si este archivo se llama tribilín.html, llegados a este punto tenemos dos posibilidades para poner en marcha el aplique. La primera, en fase debug, es utilizar el programa appletviewer de JDK y, para esto, tendremos que escribir partiendo del prompt de dos: appletviewer tribilín.html La segunda es utilizar un web browser, como Explorer o Netscape invocando el archivo tribilín.html. Como se puede ver, se pone en marcha siempre el archivo html, y no el archivo .class como ocurría para las aplicaciones. Es el archivo html el que invoca la aplicación .java. Para más informaciones sobre las páginas html os aconsejo visitar el sito HTMLpoint donde se encuentran apartados que tratan este tema. Nosostros prepararemos unas páginas sencillas que sirven sólo para cargar nuestros apliques. por ejemplo, para el aplique PrimoApplet.class de antes, el archivo tribilín.html será algo parecido a: <html> <head> <title>Applet PrimoApplet </head> <body> El siguiente es el aplique PrimoApplet.class <applet code="PrimoApplet.class" width=100 height=100>¡Tu browser es viejo, tienes que cambiarlo!</APPLET> </body> </html> Hasta ahora hemos visto cómo poner en marcha el aplique, ahora lo tenemos que crear. En primer lugar tenemos que crear una clase llamada PrimoApplet, que amplía la clase java.applet.Applet, y tenemos que definir unos métodos que el sistema (appletviewer o el browser) invocará automáticamente. Uno de estos métodos se llama paint(Graphics O), y Graphics es un objeto que representa la pantalla del aplique. Nosostros lo volveremos a definir de forma que salga en pantalla lo que queramos. Utilizaremos el método drawString de la clase Graphics para imprimir una cadena en la pantalla. El programa PrimoApplet.java es import java.applet.*; import java.awt.*; public class PrimoApplet extends Applet { public void paint (Graphics g) { g.drawString("Hola, yo soy el primer aplique.",0,50); } } El paquete aplique contiene tres interfaces y una clase: Interfaces AppletContext, esta interfaz corresponde al ambiente del aplique, es decir al documento que lo incluye y a los demás apliques que están el en mismo documento. AppletStub, se refiere al ambiente de puesta en marcha del aplique, tanto el browser como el appletviewer. AudioClip, la interfaz es una simple abstracción para que toquen unos audios. Clase Aplique Analizamos la clase Applet más detalladamente. El constructor es único, sin argumentos. Aplique() Hay unos métodos invocados del browser o de appletviewer automáticamente, que son: void init(), este método se invoca nada más cargar completamente en el sistema el aplique. Se utiliza principalmente para inicializar el aplique. void start(), invocado cuando el sistema pone en marcha el aplique y le avisa del suceso. void stop(), invocado cuando el sistema bloquea la puesta en marcha del aplique y le avisa del suceso, invocado cuando se teclea STOP del appletviewer y cambia la página en el browser. void destroy(), invocado cuando el aplique se destruye, es decir, cuando cambiamos, sale del browser o del appletviewer Por lo tanto el ciclo vital de un aplique es: Se carga y, sucesivamente, se le da el nombre de init(); Se pone en marcha, se teclea start(). Invoca el método paint() de la superclase Container; Se para tecleando stop del browser o, cuando la ventana que lo incluye no está en primer plano, se teclea stop(). Cuando vuelve en primer plano se teclea start(); Finalmente, se destruye cuando se sale del browser que lo puso en marcha. En primer lugar se invoca stop y, sucesivamente, el destroy(); En el ejemplo que ponemos a continuación, se visualizan las fases precedentes para ver los resultados con un browser, visto que el output son System.out.print(). Seleccionar en herramientas (tool), show java consola (o parecidos), con el appletviewer, en cambio, el output se escribe en la ventana de la que se invoca el html que invoca el aplique. Lo editamos en el archivo Etapas.java: import java.applet.*; public class Etapas extends Applet { public Etapás() { System.out.println("Invocado el constructor de Etapas"); } public void init() { super.init(); System.out.println("Puesto en marcha public void init()"); } public void start() { super.start(); System.out.println("Puesto en marcha public void start()"); } public void stop() { super.stop(); System.out.println("Puesto en marcha public void stop()"); } public void destroy() { super.destroy(); System.out.println("Puesto en marcha public void destroy()"); } } Lo redactamos con: javac Etapas.java Para cargarlo vamos a crear el archivo Etapas.html que incluye: <html> <head> <title>Etapas.html carga Etapas.class</title> </head> <body> El siguiente es el aplique Etapas, que nos muestra las etapas por las que pasa el aplique. <BR> <applet code="Etapas.class" width=200 height=100>¡Tu browser es viejo, tienes que cambiarlo!</APPLET> </body> </html> Pra ponerlo en marcha teclearemos: appleviewer Etapas.html, o abriremos el archivo Etapas.html con nuestro browser preferido. Los demás métodos de la clase Applet son: AppletContext getAppletContext(), da el AppletContext asociado al aplique, es decir, el documento que lo puso en marcha y los demás invocados por éste. String getAppletInfo(), da informaciones sobre el aplique, tiene que ser sobrescrita, la normal da nulo. AudioClip getAudioClip(URL url), da el objeto de tipo AudioClip asociado al URL introducido. Os recuerdo que la URL es un recuso del web. AudioClip getAudioClip(URL url, String name), da el objeto de tipo AudioClip asociado a la URL y al nombre . URL getCodeBase(), da la url asociado al aplique. URL getDocumentBase(), da la url del documento html que ha invocado el aplique. Image getImage(URL url), da el objeto de tipo Image asociada a la url introducida y se puede imprimir en la pantalla. Image getImage(URL url, String name), da el objeto de tipo Image asociado a la url y al nombre. Locale getLocale(), da el objeto de tipo Local asociado al aplique y se ocupa de la internacionalización. Se encuentra en el paquete java.util. String getParameter(String name), da el valor del parámetro llamado name tomado de la página html que invoca el aplique. El aplique, por lo tanto, se puede invocar con valores de entrada, como el args del main. Este método los recoge. Por ejemplo, si invoco el aplique Clock.class de esta forma: <applet code="Clock" width=50 height=50> <param name=Color value="blue"> </applet> Si en código del aplique escribo getParameter("Color") el resultado será "blue". String[][] getParameterInfo(), da un array que incluye informaciones sobre los parámetros del aplique. boolean isActive(), nos dice si el aplique está activo. static AudioClip newAudioClip(URL url), coge un AudioClip de un dato url. void play(URL url), toca el audio clip tomado de la url absoluto. void play(URL url, String name), toca el Clip que nos da la url y el nobmre especificado. void resize(Dimension d) o void resize(int width, int height), pide al plique que modifique sus dimensiones. Dimension es un objeto awt que es una dimensión, es decir, un valor de anchura y altura. void setStub(AppletStub stub), ajusta el AppletStub del aplique al nuevo stub. void showStatus(String msg), pide al aplique que la cadena se imprima en la ventana de estado del aplique. Un aplique es una extensión de Panel, que es un simple contenedor. De éste hereda el método: addNotify Panel amplía Container del que hereda y hace heredar a Applet, los métodos: add, add, add, add, add, addContainerListener, addImpl, countComponents, deliverEvent, doLayout, findComponentAt, findComponentAt, getAlignmentX, getAlignmentY, getComponent, getComponentAt, getComponentAt, getComponentCount, getComponents, getInsets, getLayout, getMaximumSize, getMinimumSize, getPreferredSize, insets, invalidate, isAncestorOf, layout, list, list, locate, minimumSize, paint, paintComponents, paramString, preferredSize, print, printComponents, processContainerEvent, processEvent, remove, remove, removeAll, removeContainerListener, removeNotify, setFont, setLayout, update, validate, validateTree A su vez Container amplía Component, y, por lo tanto, están los atributos BOTTOM_ALIGNMENT, CENTER_ALIGNMENT, LEFT_ALIGNMENT, RIGHT_ALIGNMENT, TOP_ALIGNMENT y los métodos: action, add, addComponentListener, addFocusListener, addInputMethodListener, addKeyListener, addMouseListener, addMouseMotionListener, addPropertyChangeListener, addPropertyChangeListener, bounds, checkImage, checkImage, coalesceEvents, contains, contains, createImage, createImage, disable, disableEvents, dispatchEvent, enable, enable, enableEvents, enableInputMethods, firePropertyChange, getBackground, getBounds, getBounds, getColorModel, getComponentOrientation, getCursor, getDropTarget, getFont, getFontMetrics, getForeground, getGraphics, getHeight, getInputMethodRequests, getLocation, getLocation, getLocationOnScreen, getName, getParent, getPeer, getSize, getSize, getTreeLock, getWidth, getX, getY, gotFocus, handleEvent, hasFocus, hide, imageUpdate, inside, isDisplayable, isDoubleBuffered, isEnabled, isFocusTraversable, isLightweight, isOpaque, isValid, isVisible, keyDown, keyUp, list, list, list, location, lostFocus, mouseDown, mouseDrag, mouseEnter, mouseExit, mouseMove, mouseUp, move, nextFocus, paintAll, prepareImage, prepareImage, printAll, processComponentEvent, processFocusEvent, processInputMethodEvent, processKeyEvent, processMouseEvent, processMouseMotionEvent, removeComponentListener, removeFocusListener, removeInputMethodListener, removeKeyListener, removeMouseListener, removeMouseMotionListener, removePropertyChangeListener, removePropertyChangeListener, repaint, repaint, repaint, repaint, requestFocus, reshape, resize, resize, setBackground, setBounds, setBounds, setComponentOrientation, setCursor, setDropTarget, setEnabled, setForeground, setLocale, setLocation, setLocation, setName, setSize, setSize, setVisible, show, size, toString, transferFocus Y, por lo tanto, los de Object: clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait La jerarquía es: Preparamos otro pequeño aplique llamado Info.java import java.applet.*; import java.awt.*; import java.awt.image.*; public class Info extends Applet implements ImageObserver { public Info() { } public void init() { super.init(); setBackground(Color.yellow); resize(400,200); } public void start() { super.start(); } public void stop() { super.stop(); } public void destroy() { super.destroy(); } public void paint (Graphics g) { g.setColor(Color.darkGray); String p=getAppletInfo(); if (p!=null) g.drawString(p,10,10); g.drawString("CODE:"+getCodeBase().toString(),10,20); g.drawString("DOC:"+getDocumentBase().toString(),10,30); Image io=getImage(getCodeBase(),"me.JPG"); // Para visualizar esta imágen necesito que Info implemente la interfaz ImageObserver. // g.drawImage(Image,x,y,ImageObserver); g.drawImage(io,10,40,this); g.drawString("éste soy yo",80,80); String nome=getParameter("pámetro3"); String cognome=getParameter("parámetro2"); String eta=getParameter("parámetro1"); g.drawString("Pone en marcha el programma",10,150); g.drawString(nombre,10,160); g.drawString(apellidos,10,170); g.drawString("de "+edad+" años",10,180); } public String getAppletInfo() { return "Applet de Pietro Castellucci"; } public String[][] getParameterInfo() { String[][] r={ {"parámetro1","entero","Tu edad"}, {"parámetro2","Cadena", "Tus Apellidos"}, {"parámetro3","Cadena","Tu Nombre"} }; return r; } } Para cargar el aplique vamos a crear un archivo llamado Info.html, que incluye: <html> <head> <title>Info.html carga Info.class</title> </head> <body> ëste es el aplique Info. <BR> <applet code="Info.class" width=400 height=200> <param name=parámetro1 value=" EDAD DEL QUE PONE EN MARCHA EL PROGRAMA "> <param name=parámetro2 value=" APELLIDOS DEL QUE PONE EN MARCHA EL PROGRAMA "> <param name=parámetro3 value=" NOMBRE DEL QUE PONE EN MARCHA EL PROGRAMA "> ¡Tu browser es viejo, hay que cambiarlo! </APPLET> </body> </html> Cuidado con especificar los parámetros parámetro1, parámetro2 y parámetro3 porque en el aplique no se hace ningún control de sus definiciones. Por lo tanto, el aplique hace una excepción no capturada, además en el directorio donde ponéis el aplique, tiene que haber un archivo llamado me.JPG, que es una imágen 67x88 pixel. Aplicaciones mixtas Llegados a este punto, después de haber visto qué son los Frame y los Applet, se nos ocurre una pregunta. ¿Es posible combinar las dos técnicas, para invocarlas de alguna forma, para crear unas aplicaciones mixtas? La respuesta, obviamente, es sí. Podemos hacer programas que son mixtos, una combinación de las aplicaciones y los apliques. Realmente, debido a la modularidad del lenguaje, podemos crear unas aplicaciones Java que utilizan otras aplicaciones, lo que no ocurre en los normales lenguajes de programación, donde es simplemente posible invocar programas de otro programa. Supongamos, por ejemplo, que creamos un programa Java llamado Calculadora.java, con su main y sus métodos, entre los que hay uno que coge dos enteros y devuelve la suma. Supongamos entonces, que lo redactamos en Calculadora.class y que lo incluímos ya puesto en marcha. Por último hacemos otro programa de cualquier tipo en el que podemos utilizar el programa Calculadora.class, es decir la clase Calculadora y todas sus funciones públicas, entre ellas la de suma. Volviendo al tema de los apliques que llaman de los frame, fijaos en este ejemplo: import java.awt.*; import java.applet.*; public class Mixta extends Applet { Frame F; public void init() { F = new Frame("Hola, éste es un Frame"); F.setSize(100,50); F.show(); } public void paint(Graphics g) { g.drawString("Yo soy un aplique",10,20); new p(); } public void destroy() { String [] param={""}; Ventana.main(param); } } class p extends Frame { p() { setTitle("Otro Frame puesto en marcha por el aplique"); setSize(250,100); setLocation(200,300); show(); new Dialog(this,"Ésta es una dialog"); } public void paint(Graphics g) { g.drawString("Podría ser un banner publicitario",10,40); } } Lo ponéis en un archivo llamado Mixta.java, y lo pondréis en marcha creando un archivo Mixta.html que incluye: <html> <head> <title>mixta</title> </head> <body> Lo siguiente es un aplique:<BR> <applet code="Mixta.class" width=200 height=100>¡Tu browser es viejo, tienes que cambiarlo!</APPLET> </body> </html> Ponedlo en marcha y a ver lo que ocurre. Siempre el aplique pasa en segundo plano, es decir, coloca otra ventana encima, y despúes vuelve al primer plano. Interfaces gráficas: GUI e AWT Antes de empezar éste que es uno de los capítulos más interesantes del curso, introducimos el paquete awt y explicamos qué es un componente GUI. Veremos también unos componentes que son contenedores para otros componentes. El paquete Abstract Window Toolkit pone a disposición del programador una serie de clases e interfaces gráficas. El paquete java.awt incluye también unos subpaquetes que son: java.awt.color java.awt.datatransfer java.awt.dnd java.awt.event java.awt.font java.awt.geom java.awt.im java.awt.image java.awt.image.renderable java.awt.print Analizaremos algunas funciones. Las clases del paquete swing han sido construidas sobre las clases awt. Swuing es actualmente el paquete más utilizado para construir interfaces gráficas y las veremos enseguida. Awt incluye: Interfaces ActiveEvent, interfaces para sucesos que saben cómo se han activado. Adjustable, interfaces para objetos que tienen un valor numérico entre una serie de valores. Composite, interfaz que sirve para redactar las primitivas gráficas de awt. CompositeContext, ambiente optimizado para las operaciones de composición de primitivas. ItemSelectable, interfaz para objetos que incluyen un conjunto de item de los que pueden ser seleccionados cero o más de uno. (Generalmente se pueden seleccionar por lo menos y al máximo un item. Esta interfaz elimina esta limitación). LayoutManager, interfaz para las clases que incluirán unos gestores de ajuste de página. LayoutManager2, como arriba. MenuContainer, superclase de todos los menús. Paint, esta interfaz define los colores que hay que utilizar para las operaciones gráficas usando Graphics2D. PaintContext, ambiente optimizado para las operaciones de redacción de primitivas gráficas usando Graphics2D. PrintGraphics, define el contexto para imprimir. Shape, definicciones para construir una forma geométrica. Stroke, más decoraciones para las formas. Transparency, define los tipos más comunes de transparencia. Clases AlphaComposite, clase que implementa las composiciones del factor alfa de los colores utilizando el blending y la transparecia de gráfica y las imágenes. AWTEvent, root de los sucesos de AWT. AWTEventMulticaster, dispatcher para suceos AWT, eficaz y multiprogramable. Hace el multicast de sucesos a varios componentes. AWTPermission, para los permisos AWT. BasicStroke, atributos para las líneas externas de las primitivas gráficas. BorderLayout, es el primer gestor de ajuste de línea. Estos gestores, que analizaremos dentro de muy poco, sirven apra disponer los elementos (los componentes) en los contenedores. Éste divide el contenedor en 5 partes: norte, sur, este, oeste y centro. Button, clase de los botones. Canvas, área en la que se puede dibujar una aplicación. CardLayout, otro gestor de layout. Checkbox, CheckboxGroup, CheckboxMenuItem gestionan los botones de tipo booleano (apretado o no apretado) Choice, presenta un menú a cortina para elegir. Color, colores en RGB o en otros espacios arbitrarios de colores. Component, Clase de la que derivan todos los componentes GUI (pulsadores, etiquetas,…) ComponentOrientation, orientación de los componentes. Container, contenedor de los componentes. Cursor, cursores. Dialog, ventanas de diálogo en las que se indican errores y no sólo eso. Dimension, cajas que gestionan el tamaño de los componentes, la altura y la anchura. Event, clase que gestiona los sucesos según el viejo modelo de gestión de los sucesos (Java 1.0). Se ha dejado para que se puedan trabajar las viejas aplicaciones y apliques Java y se declara deprecated. En efecto, este modelo se ha cambiado porque, en situaciones complejas, es decir, con muchos componentes puestos unos encima de otros, no se entendía qué componente recibía el suceso. EventQueue, cola de sucesos. FileDialog, dialog especial que gestiona el input y el output del archivo. Es muy cómoda. FlowLayout, otro gestor del ajuste de línea. Font, fonts para el texto. FontMetrics, atributos para las font. Frame, clase ya analizada que implementa las ventanas con título y marco. GradientPaint, rellena las figuras con colores lineares. Graphics, contexto gráfico en el que se puede dibujar. Graphics2D, nuevo contexto gráfico, incluido con Java 2, que da otras sofisticadas posibilidades al dibujo. GraphicsConfiguration, describe las características del destino de la gráfica que puede ser el monitor o la impresora. GraphicsConfigTemplate, utilizada para obtener una GraphicsConfiguration válida. GraphicsEnvironment, describe los ambientes gráficos y los font que se pueden utilizar para cada específica plataforma de Java. GraphicsDevice, describe el GraphicDevice que se puede utilizar en un cierto ambiente. GridBagConstraints, GridBagLayout, GridLayout, gestores del ajuste de línea. Image, superclase para todas las clases que representan una imágen gráfica. Insets, representa el borde de un contenedor. Label, etiqueta. List, listado MediaTracker, clase de utilidad para gestionar objetos multimediales. Menu, menú de tipo pull-down (a descenso). MenuBar, barra de los menús. MenuComponent, superclase de todos los componentes de los menús. MenuItem, Voz de menú. MenuShortcut, sirve para representar el acelerador para una voz de menú. Panel, contenedor. Point, punto en las coordenadas x,y. Polygon, polígono. PopupMenu, menú de tipo invisible. PrintJob, sirve para inicializar y para imprimir. Rectangle, rectángulo. RenderingHints, incluye ayudas para el rendering utilizadas por Graphics2D. RenderingHints.Key, define las teclas utilizadas para controlar los diferentes aspectos del rendering. Scrollbar, barra de desplazamiento. ScrollPane, panel con dos barras de desplazamiento. SystemColor, representa los colores simbólicos de un objeto GUI en un sistema. TextComponent, superclase de todos los componentes que incluyen el texto. TextArea, área de testo. TextField, simple línea de texto. TexturePaint, cómo rellenar una figura con una texture. Toolkit, superclase del Abstract Window Toolkit. Window, ventana (sin borde y barra de menú). Excepciones AWTException IllegalComponentStateException Errores AWTError Visto este recorrido panorámico por el paquete informático, intentaremos comprender para qué sirven estas clases. Entre todas las clases distinguimos algunas según lo que hacen. En primer lugar, hemos visto unas clases Frame, Dialog e Windows, que representan unas ventanas. Estas ventanas incluyen unos menús (las primeras dos) y unos componentes GUI y para incluirlos hay que tener unos contenedores. Pertenecen a este tipo Container y Panel. Cada Contenedor puede incluir un Layout Manager que gestiona la colocación de varios componentes en el mismo contenedor y puede incluir incluso otros contenedores, que pueden crear estructuras complejas. Finalmente hemos visto los verdaderos componentes GUI que se añaden a los contenedores, por ejemplo, Label. En el ejemplo que ponemos a continuación añadimos una label al contenedor del Frame. import java.awt.*; public class Etiqueta extends Frame { Label et=new Label(); public Etiqueta(String etiqueta) { et.setText(etiqueta); setTitle("Etiqueta "+etiqueta); add(et); pack(); show(); } public static void main (String[] a) { try {new Etiqueta(a[0]);} catch (ArrayIndexOutOfBoundsException e) {System.out.println("ERROR: teclear java Etiqueta CADENA");}; } } Como ya hemos dicho, los GUI van tanto en los Frame como en Applet, por lo tanto se puede pensar en la versión applet del programa anterior. import java.applet.*; import java.awt.*; public class ApplEtiqueta extends Applet { Label et=new Label(); public void init () { et.setText("Hola"); add(et); } } Se pondrá en un archivo llamado ApplEtiqueta.java y se invocará desde un archivo html que incluye: <html> <head> <title> </title> </head> <body> <APPLET code="ApplEtiqueta.class" width=200 height=100> </APPLET> </body> </html> En el paquete swing todo es muy parecido, aunque la gestión de los contenedores es un poco diferente. Las etiquetas y los botones Empezamos este recorrido panorámico sobre los GUI. Para utilizar varios componentes en la misma ventana necesitamos uno o más contenedores. Éstos los analizaremos sucesivamente, sin embargo tendré que utilizarlos de vez en cuando en este apartado sin haberlos explicado. Estoy seguro de que no tendréis problemas para comprender los programas porque usaré tanto los Layout como los Container de forma muy simple. Sin embargo os pido perdón si este procedimiento os puede causar algún inconveniente. En particular, utilizaré los Panel o utilizaré unas constantes de la clase BorderLayout, que separa el Container, en este caso el Panel, en cinco zonas, NORTH, SOUTH, WEST, EAST e CENTER. En esta lección analizaremos dos componentes, los más simples, las Label y los Botones. Las primeras no tienen ningún suceso asociado, mientras los segundos, según el tipo de botón, tienen dos tipos de sucesos asociados.. Etiquetas Empezamos nuestra panorámica sobre los componentes analizando el primer elemento simple, la etiqueta, que, entre otras cosas, ya hemos utilizado. La etiqueta es un contenedor para un texto. Esta hace aparecer en la pantalla una simple línea de texto que el usuario no puede editar, y que, sin embargo, la aplicación puede cambiar. La etiqueta, invocada en Java Label, se encuentra en el paquete java.awt. Por lo tanto para ser utilizada, nuestro programa tendrá que empezar una import java.awt.Label (para incluir sólo la clase Label del paquete), o import java.awt.* (para incluir todas las clases del paquete) internamente al programa que quiere utilizarla, por lo tanto, tendrá que declarar un objeto de tipo Label de la siguiente forma: Label etiqueta = new Label(); o, si no ha sido importado el paquete java.awt: java.awt.Label etiqueta = new Label(); La Label tiene tres constructores: Label() , que construye una etiqueta vacía. Label(String text), que construye una etiqueta con texto Text, justificada a la izquierda. Label(String text, int alignment) , que construye una etiqueta con texto Texto, justificada con alignment. Los valores de alignment son: Label.LEFT, Label.RIGHT, y Label.CENTER. Algunos métodos del objeto son: int getAlignment(), da la justificación actual del objeto. String getText() , da el texto incluido en el label. void setAlignment(int alignment), cambia la justificación de la Label con alignment. void setText(String text), ajusta el texto de la Label. Además existe el método AccessibleContext getAccessibleContext(), que veremos cuando analicemos los contenedores. Realmente la Label es un contenedor de texto. Label viene de la siguiente jerarquía: Por lo tanto hereda los métodos de Object y los de java.awt.Component. Todos los componentes GUI se heredan de Component, por lo tanto, al final de la panorámica de los GUI hablaremos de Component que incluye métodos muy útiles para sus gestión. Por ejemplo, métodos para ajustar y obtener informaciones sobre tamaño, colores del fondo, colores del texto, bordes, etc… del componente. A parte los métodos de Component, estamos listos para utilizar las Label en nuestos programas. Por ahora no pongo ningún ejemplo sobre el uso de las Label porque las veremos en el siguiente párrafo cuando hablemos de los botones. El paquete swing pone a disposición una clase parecida llamada JLabel que es una extensión de Label, y que, a su vez, pone a disposición una amplia gama de funciones añadidas. Por ejemplo, es posible crear JLabel que tengan tanto un texto como una imagen. Botones Otro componente muy utilizado, y de simple realización es el botón. Es un poco más complicado de la Label porque puede recibir unos sucesos (Click el botón). Los botones son de tres tipos: los normales, les Checkbox y los radiobutton. Empezamos con los primeros. La clase que define los botones es Button java.awt y pone a disposición todos los métodos para gestionar el aspecto del botón. Los constructores de la clase son dos: Button(), costruye un botón sin etiqueta; Button(String txt), costruye un botón con etiqueta txt. Algunos métodos de la clase son: void addActionListener(ActionListener l), añade el botón a un objeto invocado. ActionListener, que escucha los sucesos. Cuando se clique el botón, el suceso será gestionado por ActionListener. AccessibleContext getAccessibleContext(), como para las Label, los botones son también unos contenedores. String getActionCommand(), a cada botón se asocia una órden que devuelve el actual. String getLabel(), devuelve la etiqueta asociada al botón. EventListener[] getListeners(Class listenerType), da un array que incluye a todos los que escuchan sucesos establecidos por este botón. addXXXListener(), añade un oyente de sucesos de tipo tipo XXX. En Java hay varios tipos de sucesos, los analizaremos dentro de poco. Uno de éstos es el suceso de tipo action y, en este caso, el método es addActionListener( ) void removeActionListener(ActionListener l), elimina el oyente de sucesos 1 precedentemente asociado al botón. void setActionCommand(String command), ajusta la órden asociada al botón. Al principio la órden es la etiqueta. void setLabel(String label), ajusta la etiqueta del botón. Ahora podemos definir nuestro primer botón. Sólo tenemos que conseguir entender cómo capturar el suceso que resulta de la presión del mismo. Como ya dicho anteriormente, trataré de la gestión de los suceos según Java 2 porque el viejo modelo de gestion de los eventos de Java 1 es obsoleto y no funciona. Según Java2, definido un botón, como el suceso principal del botón es el click, que pertenece a la familia de los sucesos Action, hay que asociar un ActionListener al mismo botón utilizando el método addActionListaner(ActionListener l); Lo que tenemos que hacer es crear un ActionListener especializado para nuestro botón (o para nuestros botones). ActionListener se define en el paquete java.awt.event (que hay que incluirlo en el programa). Es una interfaz y, por lo tanto, tiene que ser implementada en la clase en la que se ha definido. Por ejemplo, estoy haciendo un Frame llamado Tribilín que incluye un botón del que quiero escuchar el click y quiero escucharlo con un ActionListener especializado por mí llamado EscuchaTribilín. Tendré que escribir algo parecido a: import java.awt.*; import java.awt.event.*; public class Tribilín extends Frame { Button botón=new Button("Hola"); … métodos de Tribilín, entre los que está el constructor, que incluye un botón.addActionListener(new EscuchaTribilín()); public class EscuchaTribilin implements ActionListener() { // Gestión del suceso } // Fin clase EscuchaTribilín dentro de Tribilín }//Fin clase Tribilín En EscuchaTribilín tendré que especializar todos los métodos de la interfaz que estoy extendiendo. ActionListener tiene sólo un método: void actionPerformed(ActionEvent e), por lo tanto, en EscuchaTribilón tendré que definir el método public void actionPerformed(ActionEvent e). Éste es el método que se invocará cuando clique el botón. La ActionEvent que llega al método como parámetro, incluye todas las informaciones sobre el suceso, incluso la órden. EscuchaTribilín se puede asociar incluso a más botones. Su actionPerformed se invocará al click de cada uno de estos botones. Aclaremos en seguida este concepto que, a lo mejor, es un poco más difícil de las demás cosas que hemos hecho con el anterior ejemplo. Sin embargo, os aseguro que una vez asimilado el concepto, la gestión de los sucesos de Java resultará muy simple. Creamos un Frame que tenga un botón y una Label: cada vez que se clica el botón, la etiqueta da un mensaje. import java.awt.*; import java.awt.event.*; public class Botón extends Frame { // Constructor clase Botón Button clícame=new Button("Clícame"); Label clicado=new Label("No me has clicado ni siquiera una vez"); public Botón() { clícame.addActionListener(new Oyente()); // setup comando clícame.setActionCommand("CLIC"); // Añado un botón y la etiqueta al Frame. // No hagáis caso a las siguientes instrucciones, // la add sirve para añadir un componente ad // un contenedor y el segundo parámetro // add, es decir BorderLayout, es un gestor de Layout, // que sirve para establecer la forma en la que los objetos // GUI se colocan en el contenedor. add(clícame,BorderLayout.NORTH); add(clicado,BorderLayout.SOUTH); // métodos de Frame pack(); show(); } // main public static void main (String [] arg) { new Botón(); } // Oyente de suceso Action int Volte=2; public class Oyente implements ActionListener { public void actionPerformed (ActionEvent e) { String Órden=e.getActionCommand(); if (Órden.compareTo("CLIC")==0) { clicado.setText("Me has clicado"); clícame.setLabel("Vuelve a clicarme"); clícame.setActionCommand("RECLIC"); }; if (Órden.compareTo("RECLIC")==0) clicado.setText("Me has clicado "+(Veces++)+" veces."); } }// Fin Oyente }// Fin Botón El resultado es la siguiente ventana: Como se ve en Oyente se gestionan dos órdenes diferentes asociados al mismo botón y éste, cuando se clica por primera vez, cambia la órden asociado al suceso clic. Intentad cambiarlo modificando la orden para estalblecer con certeza las primeras diez veces que se clica e imprimiendo en la etiqueta "Has clicado nueve veces el botón" (la novena vez). Resumiendo, en una interfaz gráfica, en primer lugar se programa el aspecto gráfico, es decir, se incluyen los botones, los menús, etc. Después se crean y se añaden los oyentes de los sucesos que, según el suceso, invocan uno u otro método de gestión del programa (pone al día la interfaz, calcula las funciones del programa de la que es la interfaz gráfica). Éste es un principio general de la programación de Interfaces Gráficas, es decir, de la programación de los sucesos. Cada programa que utilizáis funciona de esta forma y cada programa que escribíss también. El tipo de programación es un poco diferente al que un programador sobre plataforma no gráficas (tipo Dos, Linux senza X-Win) está acostumbrado y al principio puede resultar no muy simple. Sin embargo hay que acostumbrarse, después es todo igual. El segundo tipo de botones que analizamos son las Checkbox, es decir, los botones de tipo booleano, que retienen el estado. Se utilizan para hacer opciones no exclusivas o exclusivas entre ellas (en este último caso son unos radiobutton). Por ejemplo, si hay que elegir entre unos hobbies preferidos, estos botones, una vez clicados, se quedan así y hay que volver a clicarlos para que vuelvan a la situación inicial. Pra crear una Checkbox hay que crear un objeto que pertenece a la clase Checkbox de java.awt. Los constructores son: Checkbox(), crea una Checkbox sin etiqueta. Checkbox(String label), crea una Checkbox con etiqueta label. Checkbox(String label, boolean state), crea una Checkbox con etiqueta label, y hay que clicarla o no según el valor booleano state. En cambio para los radiobutton: Checkbox(String label, boolean state, CheckboxGroup group), crea una Checkbox con etiqueta label introducida en una particular CheckboxGroup, que es un contenedor de Checkbox. Checkbox(String label, CheckboxGroup group, boolean state) crea una Checkbox con etiqueta label introducida en una particular CheckboxGroup, y ajustada según el valor de state. Prácticamente, entre todos los botones introducidos en el CheckboxGroup, sólo uno puede ser clicado, los demás no se tienen que clicar. Las Checkbox del primer tipo, como he dicho, se utilizan para hacer unas opciones no exclusivas como, por ejemplo, la opción de los intereses entre una lista de los intereses posibles. Mientras las Checkbox del segundo tipo, es decir, los radiobuttons sirven para hacer opciones exclusivas como, por ejemplo, la opción de la renta de una persona en las diferentes fases. Entre los métodos de la clase Checkbox los más interesantes son: void addItemListener(ItemListener l) , asocia un ItemListener a la Checkbox, el ItemListener gestiona los sucesos de los Checkbox y no sólo eso. Son un segundo tipo de sucesos que aprenderemos a utilizar. De todas formas, se parecen a los ActionListener, lo único es que se asocian a los objetos que tienen un estado. CheckboxGroup getCheckboxGroup(), da el posible grupo en el que se intruduce la Checkbox. Las ya conocidas String getLabel() e EventListener[] getListeners(Class listenerType) boolean getState(), este método dice que la Checkbox se ha clicado o no. void removeItemListener(ItemListener l), quita el actual oyente de sucesos Item. void setCheckboxGroup(CheckboxGroup g), ajusta un gruppo para la Checkbox. void setLabel(String label), ajusta la etiqueta void setState(boolean state), ajusta el estado. Vamos a ver ahora lo que contiene un CheckboxGroup. El constructor no tiene parámetros y los métodos no declarados Deprecated son tres: Checkbox getSelectedCheckbox(), da la Checkbox seleccionada en el grupo . void setSelectedCheckbox(Checkbox box), ajusta la Checkbox del grupo indicado. String toString(), da una cadena que representa el objeto. Ahora vamos a poner un pequeño ejemplo que usa tanto Checkbox como RadioButton. import java.awt.*; import java.awt.event.*; public class Check extends Frame { // Constructot clase Botón Label r=new Label("Renta anual"); Label i=new Label("Intereses"); Button sale=new Button("saledela aplicación"); CheckboxGroup renta=new CheckboxGroup(); Checkbox r1=new Checkbox("De 0 a 10"); Checkbox r2=new Checkbox("De 11 a 30"); Checkbox r3=new Checkbox("De 31 a 70"); Checkbox r4=new Checkbox("De 71 a 100"); Checkbox r5=new Checkbox("De 101 a 200"); Checkbox r6=new Checkbox("De 201 a 500"); Checkbox r7=new Checkbox("Soy un ricachón"); Checkbox i1=new Checkbox("Esport"); Checkbox i2=new Checkbox("Informática"); Checkbox i3=new Checkbox("Lectura"); Checkbox i4=new Checkbox("Cine"); Checkbox i5=new Checkbox("Animales"); Checkbox i6=new Checkbox("Teveos"); Checkbox i7=new Checkbox("Loterías"); Checkbox i8=new Checkbox("Mujeres"); Checkbox i9=new Checkbox("Hombres"); public Check() { sale.addActionListener(new Oyente()); Panel p1=new Panel(); Panel p2=new Panel(); p1.add(r); p1.add(r1); p1.add(r2); p1.add(r3); p1.add(r4); p1.add(r5); p1.add(r6); p1.add(r7); r1.setCheckboxGroup(renta); r2.setCheckboxGroup(renta); r3.setCheckboxGroup(renta); r4.setCheckboxGroup(renta); r5.setCheckboxGroup(renta); r6.setCheckboxGroup(renta); r7.setCheckboxGroup(renta); add(p1,BorderLayout.NORTH); p2.add(i); p2.add(i1); p2.add(i2); p2.add(i3); p2.add(i4); p2.add(i5); p2.add(i6); p2.add(i7); p2.add(i8); p2.add(i9); add(p2,BorderLayout.CENTER); add(sale,BorderLayout.SOUTH); // métodos de Frame pack(); show(); } // main public static void main (String [] arg) { new Check2(); } // Oyentes de sucesos Action public class Oyente implements ActionListener { public void actionPerformed (ActionEvent e) { System.out.println("afiliación al sito XXXXXXX.it"); System.out.print("ganancia anual "); if (r1.getState()) System.out.println(r1.getLabel()); else if (r2.getState()) System.out.println(r2.getLabel()); else if (r3.getState()) System.out.println(r3.getLabel()); else if (r4.getState()) System.out.println(r4.getLabel()); else if (r5.getState()) System.out.println(r5.getLabel()); else if (r6.getState()) System.out.println(r6.getLabel()); else if (r7.getState()) System.out.println(r7.getLabel()+" (Qué suerte!)"); else System.out.println("NO DECLARADO"); System.out.println("Intereses declarados:"); if (i1.getState()) System.out.println("t"+i1.getLabel()); if (i2.getState()) System.out.println("t"+i2.getLabel()); if (i3.getState()) System.out.println("t"+i3.getLabel()); if (i4.getState()) System.out.println("t"+i4.getLabel()); if (i5.getState()) System.out.println("t"+i5.getLabel()); if (i6.getState()) System.out.println("t"+i6.getLabel()); if (i7.getState()) System.out.println("t"+i7.getLabel()); if (i8.getState()) System.out.println("t"+i8.getLabel()); if (i9.getState()) System.out.println("t"+i9.getLabel()); System.exit(0); } }// Fin Oyente }// Fin Botón Fig 4: Ventana del programa He tenido que usar otra vez los layout y también los Panel. Lo siento porque todavía no los hemos tratado, sin embargo, es la única forma para introducir más GUI en la misma ventana. Por ejemplo, intentad quitar los Panel y, sucesivamente, añadid todo directamente a las ventanas, sin utilizar los Layout. Váis a ver lo que ocurre. En la aplicación hay un botón que sirve para cerrar la ventana porque todavía no sabemos gestionar los sucesos de la ventana, como el close, que en este caso no funciona. Dentro de poco podremos gestionar incluso éstos. En la aplicación no he gestionado los sucesos de las Checkbox porque no lo he necesitado. Hay situaciones en las que se tienen que gestionar, en las que, además del estado del botón cambia algo, por ejemplo, el estado de algún GUI. Lo vemos en este ejemplo. import java.awt.*; import java.awt.event.*; public class Check2 extends Frame { // Constructot clase Botón Button cierra=new Button("Sale de la aplicación"); Checkbox i1=new Checkbox("primeros 3"); Checkbox i2=new Checkbox("segundos 4"); Checkbox ii1=new Checkbox("uno"); Checkbox ii2=new Checkbox("dos"); Checkbox ii3=new Checkbox("tres"); Checkbox ii4=new Checkbox("cuatro"); Checkbox ii5=new Checkbox("cinco"); Checkbox ii6=new Checkbox("seis"); Checkbox ii7=new Checkbox("siete"); public Check2() { cierra.addActionListener(new Oyente()); Panel p1=new Panel(); Panel p2=new Panel(); p1.add(ii1); p1.add(ii2); p1.add(ii3); p1.add(ii4); p1.add(ii5); p1.add(ii6); p1.add(ii7); ii1.addItemListener(new AItem1()); ii2.addItemListener(new AItem1()); ii3.addItemListener(new AItem1()); ii4.addItemListener(new AItem1()); ii5.addItemListener(new AItem1()); ii6.addItemListener(new AItem1()); ii7.addItemListener(new AItem1()); i1.addItemListener(new AItem2()); i2.addItemListener(new AItem2()); add(p1,BorderLayout.NORTH); p2.add(i1); p2.add(i2); add(p2,BorderLayout.CENTER); add(cierra,BorderLayout.SOUTH); // métodos de Frame pack(); show(); } // main public static void main (String [] arg) { new Check(); } // Oyente de sucesos Action public class Oyente implements ActionListener { public void actionPerformed (ActionEvent e) {System.exit(0);} }// Fin Oyente // Oyente de sucesos Item public class AItem1 implements ItemListener { public void itemStateChanged(ItemEvent e) { i1.setState(ii1.getState()&& ii2.getState()&& ii3.getState()); i2.setState(ii4.getState()&& ii5.getState()&& ii6.getState()&& ii7.getState()); } }//Fin AItem1 public class AItem2 implements ItemListener { public void itemStateChanged(ItemEvent e) { ii1.setState(i1.getState()); ii2.setState(i1.getState()); ii3.setState(i1.getState()); ii4.setState(i2.getState()); ii5.setState(i2.getState()); ii6.setState(i2.getState()); ii7.setState(i2.getState()); } }//Fin AItem2 }// Fin Botón Contenedores y Gestión de los Layout En la lección anterior hemos visto como introducir en nuestras aplicaciones y en nuestros apliques unas etiquetas y unos botones. Para hacerlo hemos utilizado unos contenedores y unos layout sin saber cómo funcionaban. En este lección analizeremos sus funcionamientos y cómo aprovecharlos para mejorar nuestras interfaces. Empezamos con describir la clase Container del paquete java.awt. Es una extensión de la clase Component de la que proceden todos los componentes GUI y, en realidad, éste mismo es un componente GUI. Un Container es un contenedor para los objetos GUI y, por lo tanto, también para los demás container. Se utiliza junto a un Layout Manager y permite que abarquen más de un GUI (y Contenedores), permitiendo que aparezcan varios objetos en nuestras interfaces. Vemos qué contiene la clase. Container() , es el único constructor de la clase. Unos métodos: Component add(Component comp), añade el componente al final del contenedor. Component add(Component comp, int index), añade el componente en la posición indicada por el contenedor. void add(Component comp, Object constraints) e void add(Component comp, Object constraints, int index) , como antes, lo único es que especifica unas constantes para el componente que se ha introducido. Component add(String name, Component comp), añade al contenedor el componente y le da un nombre. void addContainerListener(ContainerListener l), ajusta el ContainerListener que escuchará los sucesos que han ocurrido en el contenedor. void doLayout(), provoca la disposición de los componentes en el contenedor. Component findComponentAt(int x, int y), devuelve el componente que se encuentra en la posición especificada del contenedor (posición gráfica). Component findComponentAt(Point p) , devuelve el componente que se encuentra en la posición especificada del contenedor (posición gráfica). float getAlignmentX(), devuelve el ajuste de líneas a lo largo del eje X del contenedor. float getAlignmentY(), devuelve el ajuste de líneas a lo largo del eje Y del contenedor. Component getComponent(int n), devuelve el enésimo componente introducido en el contenedor. Component getComponentAt(int x, int y), devuelve el componente que se encuentra en la posición especificada del contenedor (posición gráfica). Component getComponentAt(Point p), devuelve el componente que se encuentra en la posición especificada del contenedor (posición gráfica). int getComponentCount() , da el número de componentes introducidos en el contenedor. Component[] getComponents(), da todos los componentes introducidos en el contenedor. Insets getInsets(), da un objeto Insets para el contenedor. Este objeto representa el tamaño (gráfico= del contenedor. LayoutManager getLayout(), devuelve el LayuotManager asociado al contenedor. EventListener[] getListeners(Class listenerType), devuelve todos los oyentes de sucesos al contenedor. Dimension getMaximumSize(), devuelve el tamaño máximo que puede tener el contenedor. Dimension getMinimumSize(), devuelve el tamaño mínimo que puede tener el contenedor. Dimension getPreferredSize(), devuelve el tamaño preferido por el contenedor. void list(PrintStream out, int indent), imprime el listado de los componentes introducidos en el contenedor, sobre un stream de output. void list(PrintWriter out, int indent), imprime en una impresora los componentes introducidos en el contenedor. void paint(Graphics g), dibuja el contenedor. La paint es una función muy importante. Veremos cómo definirla para dibujar en una ventana. void paintComponents(Graphics g). dibuja todos los componentes del contenedor. void print(Graphics g), imprime el contenedor (el aspecto gráfico). void printComponents(Graphics g), imprime todos los componentes introducidos en el contenedor (sus aspectos gráficos). void remove(Component comp), elimina el componente especificado por el contenedor. void remove(int index), aparta el componente que se encuentra en la posición especificada en el contenedor. void removeAll(), aparta todos los componentes introducidos en el contenedor. void removeContainerListener(ContainerListener l), elimina el oyente de sucesos para containers de este container. void setFont(Font f), ajusta las Font utilizadas por el texto en el contenedor. void setLayout(LayoutManager mgr), asocia al contenedor un gestor de layout. void update(Graphics g), vuelve a dibujar el contenedor. void validate(), anula el contenedor y sus componentes. Introduciendo componentes en el contenedor, como en el ejemplo sucesivo, nos damos cuenta de que sólo el último será visualizado. Esto ocurre porque no hemos dado gestor de Layout. En el caso de applet, en cambio, el LayoutManager de default es el FlowLayout. import java.awt.*; public class Contenedor extends Frame { Label l1=new Label("Etiqueta1"); Label l2=new Label("Etiqueta2"); Label l3=new Label("Etiqueta3"); Label l4=new Label("Etiqueta4"); Label l5=new Label("Etiqueta5"); public Contenedor() { // uso add, porque el Frame es una extensión de Window, que a su // vez amplía Container. add(l1); add(l2); add(l3); add(l4); add(l5); doLayout(); pack(); show(); } public static void main(String [] arg) { new Contenedor(); } } Por lo tanto, vemos cómo introducir un gestor de Layout en el contenedor. El método de la clase container que lo permite es void setLayout(LayoutManager mgr). No nos queda que ver cómo es el objeto de tipo LayoutManager. LayoutManager es una interfaz. De las clases que la implementan analizaremos GridLayout, FlowLayout. Hay otra interfaz llamada LayoutManager2, que amplía ésta y que tiene otras clases que la implementan. De éstas veremos: CardLayout, BorderLayout, GridBagLayout, BoxLayout, OverlayLayout. Por lo tanto, en el método setLayout (…) podemos utilizar como parámetro un objeto de estas clases. Llegados a este punto, tenemos que comprender qué diferencia hay entre los layout manager. El FlowLayout deja colocar los componentes de un contenedor de la izquierda hacia la derecha en una sóla línea. Para utilizar el LayoutManager FlowLayout del ejemplo precedente basta con invocar el método setLayout con parámetro new FlowLayout(), y después los componentes se introducen automáticamente, de la add de la derecha hacia la izquierda en la misma línea. import java.awt.*; public class ContEFL extends Frame { Label l1=new Label("Etiqueta1"); Label l2=new Label("Etiqueta2"); Label l3=new Label("Etiqueta3"); Label l4=new Label("Etiqueta4"); Label l5=new Label("Etiqueta5"); public ContEFL() { // uso add, porque el Frame es una extensión de Window, que a su // vez amplía Container. setLayout(new FlowLayout()); add(l1); add(l2); add(l3); add(l4); add(l5); pack(); show(); } public static void main(String [] arg) { new ContEFL(); } } La ventana que sale es: El GridLayout permite colocar los componentes en un contenedor como si se dispusieran en una plantilla. Todos los componentes tendrán el mismo tamaño. La clase tiene tres constructores, uno sin parámetros que crea una planilla con 1 línea, prácticamente un FlowLayout, y otros dos que permiten especificar el tamaño de la planilla. El ejemplo anterior cambia de esta forma: import java.awt.*; public class ContEGL extends Frame { Label l1=new Label("Etiqueta1"); Label l2=new Label("Etiqueta2"); Label l3=new Label("Etiqueta3"); Label l4=new Label("Etiqueta4"); Label l5=new Label("Etiqueta5"); public ContEGL() { // uso add, poruqe el Frame es una extensión de Window, que a su // vez amplía Container. setLayout(new GridLayout(2,3)); add(l1); add(l2); add(l3); add(l4); add(l5); pack(); show(); } public static void main(String [] arg) { new ContEGL(); } } La ventana es la siguiente: El LayoutManager CardLayout permite visualizar los componentes diferentes en plazos de tiempo diferentes, es decir, que visualiza sólo un componente a la vez. Sin embargo el componete visualizado se puede cambiar. Hay aquí un ejemplo del uso de CardLayout import java.awt.*; import java.awt.event.*; public class ContECL extends Frame { Label l1=new Label("Etiqueta1"); Label l2=new Label("Etiqueta2"); Label l3=new Label("Etiqueta3"); Label l4=new Label("Etiqueta4"); Label l5=new Label("Etiqueta5"); Panel p=new Panel(new GridLayout(2,1)); CardLayout CL=new CardLayout(14,14); Panel p1=new Panel(CL); Panel NPB=new Panel(new FlowLayout()); public ContECL() { p.add(NPB); p.add(p1); Button N=new Button("Próximo"); Button P=new Button("Anterior"); NPB.add(P); NPB.add(N); P.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { CL.previous(p1); } } ); N.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { CL.next(p1); } } ); p1.add("uno",l1); p1.add("dos",l2); p1.add("tres",l3); p1.add("cuatro",l4); p1.add("cinco",l5); add(p); pack(); show(); } public static void main(String [] arg) { new ContECL(); } } El resultado de los ejemplos son las siguientes ventanas: Muestran como las cinco etiquetas aparecen en la misma posición en plazos de tiempo diferentes. Notad que en el ejemplo no se han utilizado los demás gestores de Layout para que se puedan introducir los botones Próximo y Anterior. BorderLayout es uno de los gestores de Layout más utilizados. Permite introducir en un contenedor cinco componentes, uno al Norte, uno al Sur, uno al Este, uno al Oeste y otro al Centro. El tamaño del contenedor lo establecerá el componente central. Las cinco posiciones están indicadas por los contenedores de la clase: BorderLayout.NORTH BorderLayout.SOUTH BorderLayout.EAST BorderLayout.WEST BorderLayout.CENTER hay aquí un ejemplo del uso de BorderLayout. He cambiado los colores de fondo de las etiquetas para que se vea el componente entero. import java.awt.*; public class ContEBL extends Frame { Label l1=new Label("Etiqueta al Norte",Label.CENTER); Label l2=new Label("Etiqueta al Sur",Label.CENTER); Label l3=new Label("Etiqueta al Este",Label.CENTER); Label l4=new Label("Etiqueta al Oeste",Label.CENTER); Label l5=new Label("Etiqueta al Centro",Label.CENTER); public ContEBL() { // uso add porque el Frame es una extensión de Window, que a su // vez amplía Container. l1.setBackground(Color.pink); l2.setBackground(Color.lightGray); l3.setBackground(Color.green); l4.setBackground(Color.yellow); l5.setBackground(Color.orange); setLayout(new BorderLayout()); add(l1,BorderLayout.NORTH); add(l2,BorderLayout.SOUTH); add(l3,BorderLayout.EAST); add(l4,BorderLayout.WEST); add(l5,BorderLayout.CENTER); pack(); show(); } public static void main(String [] arg) { new ContEBL(); } } El resultado es: Hay también otros gestores de Layout, que no analizaremos, y que, sin embargo, podéis estudiar junto a éstos en la documentación oficial de las Java Development Kit. Menús Un menú en una aplicación no es más que un MenuBar en el que hay varios menús. Pensemos en un programa cualquiera con las voces de menú File Edit y Help. Estas tres voces en Java son unos objetos de la clase Menú y se tienen que añadir a un objeto de la clase MenuBar que se une a la ventana. Cada menú tiene varias voces. por ejemplo, el menú File tendrá las voces: Abrir, Cerrar, Guarda y Salir. Éstos en Java son unos objetos de la clase MenuItem (o también Menú si incluyen otros submenús). Por lo tanto, si a una aplicación le quisieramos añadir un menú tendríamos hacer las siguientes cosas siguiendo un órden cualquiera: Crear los objetos MenuItem Crear los objetos menú y pegarles los MenuItem Crear una MenuBar y pegarles los Menús Además, como siempre, tenemos que escribir unos gestores para los sucesos de los menús y asociarlos a los menús. Veamos, en práctica, cómo se construye un menu, empezando por los MenuItem. Los sucesos de los MenuItem son los que tenemos que gestionar nosotros a diferencia de los sucesos de los menús que los gestiona el sistema. Mientras los segundos sirven para que aparezcan y desaparezcan las voces del menú, los primeros son los clics sobre la orden correspondiente al Item. Por lo tanto, para éstos tendremos que escribir unos ActionListener, como para los botones. Realmente no son otra cosa que unos botones especiales. Los constructores son tres: MenuItem() , que construye un MenuItem sin etiqueta. MenuItem(String label), que construye MenuItem con etiqueta label. MenuItem(String label, MenuShortcut s), que construye un MenuItem con etiqueta label y acelerador (tecla de opción rápida) definido en MenuShortcut s. Algunos métodos son: addActionListener(ActionListener l), asocia un ActionListener al MenuItem para escuchar los sucesos de tipo ActionEvent (el clic). void deleteShortcut(), borra la tecla de opción rápida para el menuitem. String getActionCommand(), da la acción asociada al MenuItem. La acción es la que pasa al actionListener del botón para identificar el botón mismo. Así varios item pueden tener el mismo gestor de sucesos que podrá distinguir el botón clicado basándose en la órden que le llega. String getLabel(), devuelve la etiqueta del MenuItem EventListener[]getListeners(Class listenerType) , devuelve todos los oyentes de sucesos asociados al MenuItem, del tipo listenerType. MenuShortcut getShortcut(), devuelve la definición del acelerador para el MenuItem. boolean isEnabled(), dice si el menú esta disponible o no. Si no lo está se visualizará en gris. void removeActionListener(ActionListener l), elimina el oyente asociado. void setActionCommand(String command), ajusta la orden asociada al MenuItem. Si no está especificado, la órden es la etiqueta del MenuItem. void setEnabled(boolean b), habilita y deshabilita el MenuItem. void setLabel(String label), ajusta la etiqueta para el MenuItem. void setShortcut(MenuShortcut s), define el acelerador para el menú. Por lo tanto, para crear los Item del menú File descrito antes tendremos que escribir: MenuItem abrir=new MenuItem("Abrir"); MenuItem cerrar=new MenuItem("Cerrar"); MenuItem guardar=new MenuItem("Guardar"); MenuItem salir=new MenuItem("Salir"); Por tanto creamos el objeto Menu para pegar los MenuItem. Dos constructores del objeto son: Menu(), construye un Menú sin etiqueta Menu(String label), construye un menú con etiqueta label. Entre los métodos hay: MenuItem add(MenuItem mi), añade un MenuItem al Menú. void add(String label), añade una etiqueta al Menú (tipo separador). void addSeparator(), añade un verdadero separador al menú, es decir, una línea. Añadiendo objetos a un menú de esta forma, se colocarán en el mismo órden en el que se añaden. Para especificar, en cambio, la posición en que si quieren introducir hay que usar los métodos: void insert(MenuItem menuitem, int index) void insert(String label, int index) void insertSeparator(int index) Los métodos MenuItem getItem(int index) e int getItemCount() sirven para coger un item establecido en una posición y para conocer el número de MenuItem incluidos en un menú. Para eliminar los Item del menú, en cambio, se utilizan los métodos: void remove(int index) void remove(MenuComponent item) e void removeAll() Por lo tanto, para crear nuestro menú File tendremos que escribir: Menu file= new Menu("File"); file.add(abrir); file.add(guardar); file.add(cerrar); file.addSeparator(); file.add(salir); Llegados e este punto, suponiendo haber creado también Edit y Help, tenemos que añadirle el MenuBar que se crea utilizando el constructor MenuBar(). En esta barra estan los métodos remove y removeAll como para los menús, y está la add: Menu add(Menu m); Por lo tanto nuestro MenuBar se tendrá que crear de la siguiente forma: MenuBar barra=new MenuBar(); barra.add(file); barra.add(edit); barra. setHelpMenu(help); Este último método es especial para añadir un menú de ayuda. Finalmente, no nos queda que asociar la barra de menú a nuestra ventana. Esto es muy simple porque nuestra ventana será una extensión de Frame y en frame está el método: setMenuBar(MenuBar mb); En nuestro caso será setMenuBar (barra); CUIDADO: con awt no es posible introducir menú en Dialog y en Applet (los que se ven son falsos menús, es decir, se programan dibujando unos menús o no son sólo awt), mientras con swing sí. A continuación mostramos el listado de una aplicación que define un menú y escucha los sucesos: import java.awt.*; import java.awt.event.*; public class Ventanamenú extends Frame { MenuItem abrir=new MenuItem("Abrir"); MenuItem cerrar=new MenuItem("Cerrar"); MenuItem salvar=new MenuItem("Guardar"); MenuItem salir=new MenuItem("Salir"); MenuItem cortar=new MenuItem("Cortar"); MenuItem copiar=new MenuItem("Copiar"); MenuItem pegar=new MenuItem("Pegar"); MenuItem eliminar=new MenuItem("Eliminar"); MenuItem buscar=new MenuItem("Buscar"); MenuItem sustituir=new MenuItem("Sustituir"); MenuItem ayuda=new MenuItem("Índice"); Menu file= new Menu("File"); Menu edit= new Menu("Edit"); Menu help= new Menu("Help"); MenuBar barra = new MenuBar(); Label resultado=new Label("Ninguna voz de menú clicada"); public Ventanamenú() { setupSucesos(); file.add(abrir); file.add(guardar); file.add(cerrar); file.addSeparator(); file.add("Menú Salir"); file.addSeparator(); file.add(salir); edit.add(cortar); edit.add(copiar); edit.add(pegar); edit.add(eliminar); edit.addSeparator(); edit.add(buscar); edit.add(sustituir); help.add(ayuda); barra.add(file); barra.add(edit); barra.setHelpMenu(help); setMenuBar(barra); add(resultado); pack(); show(); addWindowListener(new VentanamenúWindowListener()); } void setupSucesos() { abrir.addActionListener(new OyenteMenú()); salvar.addActionListener(new OyenteMenú()); cerrar.addActionListener(new OyenteMenú()); salir.addActionListener(new OyenteMenú()); cortar.addActionListener(new OyenteMenú()); copiar.addActionListener(new OyenteMenú()); pegar.addActionListener(new OyenteMenú()); eliminar.addActionListener(new OyenteMenú()); buscar.addActionListener(new OyenteMenú()); sustituir.addActionListener(new OyenteMenú()); ayuda.addActionListener(new OyenteMenú()); } public static void main(String[] arg) { new Ventanamenú(); } class OyenteMenú implements ActionListener { public void actionPerformed (ActionEvent e) { resultado.setText(" Clicado "+e.getActionCommand()); if (e.getActionCommand().compareTo("Salir")==0) System.exit(0); } } class VentanamenúWindowListener implements WindowListener { public void windowActivated(WindowEvent e) { System.out.println("Escuchado un Window Activated"); } public void windowClosed(WindowEvent e) { System.out.println("Escuchado un Window Closed"); } public void windowClosing(WindowEvent e) { System.out.println("Escuchado un Window Closing"); System.exit(0); } public void windowDeactivated(WindowEvent e) { System.out.println("Escuchado un Window Deactivaded"); } public void windowDeiconified(WindowEvent e) { System.out.println("Escuchado un Window Deiconified"); } public void windowIconified(WindowEvent e) { System.out.println("Escuchado un Window Iconified"); } public void windowOpened(WindowEvent e) { System.out.println("Escuchado un Window Opened"); } } } Como acabamos de ver en el ejemplo, hay otro oyente de sucesos. Escucha los sucesos de tipo WindowEvent y sirve para escuchar sucesos de la ventana. Para asociarlo a la ventana se utiliza el método addWindowListener(WindowListener l); en el que WindowListener es el oyente de los sucesos. Para definir un oyente de sucesos hay, por lo tanto, que implementar la interfaz WindowListener y volver a definir todos los métodos: class MÍOYENTE implements WindowListener Los métodos para definir son: public void windowActivated(WindowEvent e) public void windowClosed(WindowEvent e) public void windowClosing(WindowEvent e) public void windowDeactivated(WindowEvent e) public void windowDeiconified(WindowEvent e) public void windowIconified(WindowEvent e) public void windowOpened(WindowEvent e) Hay que volver a definirlos todos. Si ponemos en marcha el ejemplo vemos, gracias a las System.out.println colocadas en los métodos redefinidos, a qué tipo de suceso de la ventana están asociados los distintos métodos, como, por ejemplo, windowClosed se invoca cuando se pulsa la X de la ventana. Como ejercicio intentad escribir unos WindowListener para escuchar el suceso cerrar Ventana para todos los Frame definidos en los ejemplos de este capítulo (los dejé a posta sin definición). Los menús analizados no son los únicos que se pueden implementar en Java. Otro tipo de menú es el de tipo Popup, es decir, el menú que se asocia a un componente o a una posición. Un ejemplo de menú popup es el que sale en el desktop del sistema operativo Windows cuando se aprieta el botón derecho del ratón (véase Dibujo). Para crear un menú popup hay que crear un objeto que pertenezca a la clase PopupMenu, utilizando uno de los constructores: PopupMenu(), PopupMenu(String label), que crean, respectivamente, un menú sin o con etiqueta. PopupMenu es una extensión de Menú, por lo tanto hereda los métodos incluso los que sirven para añadir y eliminar unos MenuItem. En cambio, entre sus métodos hay uno para visualizar el menú en una posición establecida de la pantalla con respecto a un determinado componente: show(Component origin, int x, int y) Listas y selecciones A menudo en las interfaces de nuestro Apliques nos sucede que el usuario tiene que hacer una o más selecciones entre varias posibilidades. Para esto tenemos que hacer una lista de las posibilidades y después leer la opción. Para hacerlo podemos utilizar las listas, que representan una serie de objetos y dan la posibilidad de elegir. Pensemos, por ejemplo, en un aplique que gestiona las reservas de visitas guiadas de algunas localidades y que nos obligue a elegir una. Además de las form conocidas para el Nombre, Apellidos, etc.. tendremos la lista de las localidades. Analicemos cómo podemos implementarla. Para construir la lista utilizaremos uno de los tres constructores: List() , crear una nueva lista List(int rows), crea una nueva lista con rows líneas List(int rows, boolean multipleMode), crea una nueva lista con rows líneas, y se le dice que, según el valor booleano multipleMode, se podrán elegir uno o más elementos de la lista. En nuestro caso utilizaremos la tercera forma de constructor. List lista=new List(0,true); Algunos métodos que podemos utilizar en las listas son: void add(String item) e void add(String item, int index), para colgar, al final de la lista o en cierta posición, un nuevo componente. void addActionListener(ActionListener l), para asociar un ActionListener a la lista. void addItemListener(ItemListener l), para asociar un ItemListener a la lista. String getItem(int index), para tener el elemento en posición indicada. int getItemCount() , para obtener el número de los elementos. String[] getItems(), para obtener todos los elementos. EventListener[] getListeners(Class listenerType) , para obtener los oyentes asociados a la lista del tipo que queremos. Dimension getMinimumSize(), da el tamaño mínimo de la lista. Dimension getPreferredSize(), da el tamaño preferido para la lista. int getRows() , dice el número de líneas al momento visibles en la lista. Int getSelectedIndex(), da el índice del elemento seleccionado. int[] getSelectedIndexes(), da los índices de los elementos seleccionados. String getSelectedItem(), da el objeto seleccionado. String[]getSelectedItems(), da los objetos seleccionados. Object[] getSelectedObjects(), da la lista de los elementos seleccionados en un vector de objetos. boolean isIndexSelected(int index), dice si el índice está seleccionado. boolean isMultipleMode(), dice si, en la lista, se pueden seleccionar más elementos. void makeVisible(int index), visualiza el item en la posición indicada. void remove(int position), void remove(String item), elimina los elementos indicados por el índice o la etiqueta. void removeAll(), elimina todos los elementos de la lista. void removeActionListener(ActionListener l) e void removeItemListener(ItemListener l), eliminan los oyentes de sucesos definidos para la lista. void replaceItem(String newValue, int index), modifica el elemento especificado. void select(int index), selecciona el elemento indicado en la lista. void setMultipleMode(boolean b), dice a la lista si es posible seleccionar sólo un elemento o más de uno. Veamos el ejemplo sobre el uso de las listas. import java.awt.*; import java.awt.event.*; public class listas extends Frame { List lista=new List(0,true); Label text=new Label("Maravillas que se pueden visitar en la localidad elegida"); public listas() { super("Elegir itinerario"); lista.add("Bienvenido"); lista.add("Foiano de Val Fortore"); lista.add("Baselice"); lista.add("San Bartolomeo en Galdo"); lista.add("San Marco de los Cavoti"); lista.add("Montefalcone en Val Fortore"); lista.add("Pesco Sannita"); lista.add("Colle Sannita"); lista.add("Castelvetere en Val Fortore"); lista.add("Castelfranco en Miscano"); lista.add("Ginestra de los Schiavoni"); lista.add("San Giorgio la Molara"); lista.add("Molinara"); lista.add("Pietrelcina"); lista.add("Fragneto Monforte"); lista.add("Circello"); lista.add("Campolattaro"); add(lista,BorderLayout.CENTER); add(text,BorderLayout.SOUTH); addWindowListener(new listeWindowListener()); lista.addItemListener(new escuchaLista()); setSize(350,100); setResizable(false); show(); } public static void main(String [] arg) { new listas(); } class listeWindowListener implements WindowListener { public void windowActivated(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowClosing(WindowEvent e) { String[] s=lista.getSelectedItems(); int i=0; System.out.println("Itinerario seleccionado"); try { while (true) { System.out.println(s[i++]); } } catch (ArrayIndexOutOfBoundsException er) {System.out.println("Qué lo pases bien...");} System.exit(0); } public void windowDeactivated(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowOpened(WindowEvent e) {} } class escuchaLista implements ItemListener { public void itemStateChanged(ItemEvent e) { int índice=((Integer) e.getItem()).intValue(); if (índice==0) text.setText("Rocca de los Rettori, arco de Trajano, anfiteatro Romano, ciudad espectáculo"); if (índice==1) text.setText("localidad San Giovanni, Campanario, via Roma, lago, fiesta S.Giovanni, fiesta del emigrante"); if (índice==2) text.setText("óasis ds San Leonardo"); if (indice==3) text.setText("casco histórico"); if (índice==4) text.setText("casco histórico"); if (índice==5) text.setText("casco histórico"); if (índice==6) text.setText("casco histórico"); if (índice==7) text.setText("casco histórico"); if (índice==8) text.setText("casco histórico"); if (índice==9) text.setText("Bosque"); if (índice==10) text.setText("casco histórico"); if (índice==11) text.setText("Lago de San Giorgio"); if (índice==12) text.setText("casco histórico"); if (índice==13) text.setText("Piana Romana, casco histórico, casas de Padre Pío"); if (índice==14) text.setText("Encuentro internacional de globos, Palacio Ducal"); if (índice==15) text.setText("casco histórico"); if (índice==16) text.setText("Dique de Campolattaro"); } } } Por lo tanto, las listas son unos buenos GUI para que el usuario ponga en marcha una opción entre las muchas posibilidades. Hay otro GUI que sirve para esto, que prácticamente es una lista que aparece y desaparece, en el sentido de que se puede ver sólo el objeto seleccionado y que los demás se ven sólo cuando hay que hacer la selección, tipo menú, mientras en las listas se pueden ver siempre. El GUI del que estoy hablando es el Choice (selección), cuyo funcionamiento es parecido al de las listas. El constructor es uno sin parámetros, mientras los métodos son: void add(String item), añade una posibilidad al final de las selecciones posibles. void addItem(String item), como la add void addItemListener(ItemListener l), asocia un oyente de sucesos para la Choice. String getItem(int index), devuelve la elección posible que se encuentra en el índice . int getItemCount(), da el número de posibilidades para la Choice. EventListener[] getListeners(Class listenerType), da todos los oyentes asociados a la Choice. int getSelectedIndex(), devuelve el índice de la lista seleccionada en la Choice. String getSelectedItem(), devuelve la opción seleccionada en la Choice. Object[] getSelectedObjects(), da todos los item seleccionados. void insert(String item, int index), introduce una seleccióm en la posición indicada de la Choice. void remove(int position), void remove(String item), void removeAll() , para eliminar las selecciones posibles de la lista. void removeItemListener(ItemListener l), elimina el oyente de sucesos de tipo ItemListener asociado a la Choice. void select(int pos), void select(String str), para seleccionar una opción. Escribamos un pequeño programa que utilice las Choice. Pero, como hasta ahora hemos creado unas aplicaciones a ventana, creemos un aplique, y visto que es GUI, se pueden utilizar en los dos tipos de programas. Hablando de aplique, como no lo hemos hacho hasta ahora, creemos un aplique que lee los parámetros que le pasan los archivos html que lo invoca. Es lo que hacen prácticamente la mayor parte de los apliques del sito HTMLpoint. En el aplique, como ejemplo, he puesto un pequeño control sobre el campo autor. Esto me sirve para enseñaros cómo se puede hacer. Puede resultar útil si se crea un aplique y lo distribuimos de forma que sea obligatorio el parámetro del autor y su valor. También sirve para evitar que otros puedan hacerse creadores del aplique y, por lo tanto, pretender los derechos de la creación del aplique. El control no es complicado, un hacker lo evitaría fácilmente. Sin embargo un hacker no tiene ningún interés en destruir un programa free, sólo para eliminar los derechos de autor. En cambio, para un usuario normal es imposible poner en marcha el programa sin especificar el nombre del autor. El nombre puede incluso ser un número de serie si queremos vender un programa. El archivo html, se llamará Choice.html, y su contenido es: <html> <head> <title> Ejemplo de uso de la java.awt.Choice y del cambio de los parámetros a un aplique </title> </head> <body> <APPLET code="Selecciones.class" width=350 height=100> <param name=loc1 value="Bienvenido"> <param name=loc2 value="Foiano de Val Fortore"> <param name=loc3 value="Baselice"> <param name=loc4 value="San Bartolomeo en Galdo"> <param name=loc5 value="San Marco de los Cavoti"> <param name=loc6 value="Montefalcone in Val Fortore"> <param name=loc7 value="Pesco Sannita"> <param name=loc8 value="Colle Sannita"> <param name=loc9 value="Castelvetere en Val Fortore"> <param name=loc10 value="Castelfranco en Miscano"> <param name=loc11 value="Ginestra de los Schiavoni"> <param name=loc12 value="San Giorgio la Molara"> <param name=loc13 value="Molinara"> <param name=loc14 value="Fragneto Monforte"> <param name=loc15 value="Circello"> <param name=loc16 value="Campolattaro"> <param name=autore value="Pietro Castellucci"> </APPLET> </body> </html> Mientras que el código del aplique, editado en el archivo llamado Selecciones.java, es: import java.awt.*; import java.awt.event.*; import java.applet.*; public class Selecciones extends Applet { Label text=new Label("Maravillas que se pueden visitar en la localidad seleccionada"); Choice scelte=new Choice(); public void init() { // Control de los derechos de autor: String autor=getParameter("autor"); if (autor==null) System.exit(0); if (autor.compareTo("Pietro Castellucci")!=0) { System.out.println("Parámetro autor no valido, introducir como valor Pietro Castellucci"); System.exit(0); } String p=getParameter("loc1"); if (p==null) p="Localidad 1 no definida"; scelte.add(p); p=getParameter("loc2"); if (p==null) p="Localidad 2 no definida"; scelte.add(p); p=getParameter("loc3"); if (p==null) p="Localidad 3 no definida"; scelte.add(p); scelte.add(p); p=getParameter("loc4"); if (p==null) p="Localidad 4 no definida"; scelte.add(p); p=getParameter("loc5"); if (p==null) p="Localidad 5 no definida"; scelte.add(p); p=getParameter("loc6"); if (p==null) p="Localidad 6 no definida"; scelte.add(p); p=getParameter("loc7"); if (p==null) p="Localidad 7 no definida"; scelte.add(p); p=getParameter("loc8"); if (p==null) p="Localidad 8 no definida"; scelte.add(p); p=getParameter("loc9"); if (p==null) p="Localidad 9 no definida"; scelte.add(p); p=getParameter("loc10"); if (p==null) p="Localidad 10 no definida"; scelte.add(p); p=getParameter("loc11"); if (p==null) p="Localidad 11 no definida"; scelte.add(p); p=getParameter("loc12"); if (p==null) p="Localidad 12 no definida"; scelte.add(p); p=getParameter("loc13"); if (p==null) p="Localidad 13 no definida"; scelte.add(p); p=getParameter("loc14"); if (p==null) p="Localidad 14 no definida"; scelte.add(p); p=getParameter("loc15"); if (p==null) p="Localidad 15 no definida"; scelte.add(p); p=getParameter("loc16"); if (p==null) p="Localidad 16 no definida"; scelte.add(p); p=getParameter("loc17"); if (p==null) p="Localidad 17 no definida"; scelte.add(p); scelte.addItemListener(new escuchaChoice()); // Visualizzo la Choice add(selecciones, BorderLayout.CENTER); add(text, BorderLayout.SOUTH); } class escuchaChoice implements ItemListener { public void itemStateChanged(ItemEvent e) { int indice=scelte.getSelectedIndex(); if (indice==0) text.setText("Rocca dei Rettori, arco de Trajano, anfiteatro Romano"); if (indice==1) text.setText("localidad San Giovanni, Campanario, via Roma, lago"); if (índice==2) text.setText("óasi de San Leonardo"); if (índice==3) text.setText("casco histórico"); if (índice==4) text.setText("casco histórico"); if (índice==5) text.setText(""); if (índice==6) text.setText(""); if (índice==7) text.setText(""); if (índice==8) text.setText(""); if (índice==9) text.setText("Bosque"); if (índice==10) text.setText(""); if (índice==11) text.setText("Lago de San Giorgio"); if (índice==12) text.setText(""); if (índice==13) text.setText("Piana Romana, casco histórico, casas de Padre Pío"); if (índice==14) text.setText("Encuentro internacional de globos, palcio Ducale"); if (índice==15) text.setText(""); if (índice==16) text.setText("Dique de Campolattaro"); } } } El último componente GUI que veremos en esta lección es el ScrollPane, es decir, un panel en el que es posible introducir un componente tan grande como queramos, y que tiene que ser visualizado a trozos. El ScrollPane usa dos Scrollbar, una horizontal y otra vertical, para seleccionar la parte del GUI que queremos ver. Fijaos que la Scrollbar es un objeto también, por lo tanto, se puede introducir y gestionar en las proprias aplicaciones. En el ScrollPane se puede elegir si visualizar las Scrollbar siempre, nunca o sólo cuando sea necesario. Un ScrollPane típico es el que utilizamos para visualizar un archivo de texto. Hay dos tipos de constructores para el objeto: ScrollPane(int sbp), crea un ScrollPane que pueda visualizar las Scrollbar definidas por spb. Los valores posibles son : ScrollPane.SCROLLBARS_ALWAYS siempre a la vista ScrollPane.SCROLLBARS_AS_NEEDED a la vista sólo si lo necesitamos ScrollPane.SCROLLBARS_NEVER nunca a la vista ScrollPane(), crea un ScrollPane que pueda visualizar ScrollPane.SCROLLBARS_AS_NEEDED. Entre los métodos, además de los que sirven para gestionar el ScrollPane, encontramos los de Container y de Component. En efecto, ScrollPane es una extensión de Container, por lo tanto, si lo vemos como un contenedor, se puede ver como un componente. El Texto, los sucesos, Dialog Nos queda por ver un componente fundamental para una interfaz, es decir, el texto. Los abstract window Toolkit de Java ponen a disposición dos clases para el texto: TextArea y TextField, las dos amplían la clase TextComponent. En TextComponent encontramos una serie de métodos útiles para la gestión del texto, tanto en TextArea como en TextField, entre ellos: Color getBackground() e setBackground(Color c), para capturar y verificar el color de Background del texto. String getSelectedText(), captura el texto seleccionado. int getSelectionEnd(), da la posición del final de la selección del texto. Int getSelectionStart(), da la posición del inicio de la selección del texto. String getText(), da el texto del TextComponent boolean isEditable(), dice si el texto del componente se puede editar. void select(int selectionStart, int selectionEnd), selecciona el texto de selectionStart a selectionEnd. void selectAll(), selecciona todo el texto. void setEditable(boolean b), verifica si el texto se puede editar o no. void setSelectionEnd(int selectionEnd), verifica el final de la selección del texto. void setSelectionStart(int selectionStart), verifica el inicio de la selección. void setText(String t), verifica el contenido del componente del texto con valor t. Además en TextComponent se pueden utilizar todos los métodos de Component, es decir: action, add, addComponentListener, addFocusListener, addHierarchyBoundsListener, addHierarchyListener, addInputMethodListener, addKeyListener, addMouseListener, addMouseMotionListener, addNotify, addPropertyChangeListener, addPropertyChangeListener, bounds, checkImage, checkImage, coalesceEvents, contains, contains, createImage, createImage, deliverEvent, disable, disableEvents, dispatchEvent, doLayout, enable, enable, enableEvents, enableInputMethods, firePropertyChange, getAlignmentX, getAlignmentY, getBounds, getBounds, getColorModel, getComponentAt, getComponentAt, getComponentOrientation, getCursor, getDropTarget, getFont, getFontMetrics, getForeground, getGraphics, getGraphicsConfiguration, getHeight, getInputContext, getInputMethodRequests, getLocale, getLocation, getLocation, getLocationOnScreen, getMaximumSize, getMinimumSize, getName, getParent, getPeer, getPreferredSize, getSize, getSize, getToolkit, getTreeLock, getWidth, getX, getY, gotFocus, handleEvent, hasFocus, hide, imageUpdate, inside, invalidate, isDisplayable, isDoubleBuffered, isEnabled, isFocusTraversable, isLightweight, isOpaque, isShowing, isValid, isVisible, keyDown, keyUp, layout, list, list, list, list, list, locate, location, lostFocus, minimumSize, mouseDown, mouseDrag, mouseEnter, mouseExit, mouseMove, mouseUp, move, nextFocus, paint, paintAll, postEvent, preferredSize, prepareImage, prepareImage, print, printAll, processComponentEvent, processFocusEvent, processHierarchyBoundsEvent, processHierarchyEvent, processInputMethodEvent, processKeyEvent, processMouseEvent, processMouseMotionEvent, remove, removeComponentListener, removeFocusListener, removeHierarchyBoundsListener, removeHierarchyListener, removeInputMethodListener, removeKeyListener, removeMouseListener, removeMouseMotionListener, removePropertyChangeListener, removePropertyChangeListener, repaint, repaint, repaint, repaint, requestFocus, reshape, resize, resize, setBounds, setBounds, setComponentOrientation, setCursor, setDropTarget, setEnabled, setFont, setForeground, setLocale, setLocation, setLocation, setName, setSize, setSize, setVisible, show, show, size, toString, transferFocus, update, validate Ahora, veamos los TextField. Prácticamente un TextField es una única línea de texto que se puede editar, con aspecto gráfico. Es uno de los campos que estamos acostumbrados a ver en las Form que se encuentran en internet para introducir nombres, apellidos, etc. Hay cuatro posibles constructores: TextField(), crea un TextField vacío. TextField(int c), crea un TextField vacío de c columnas, es decir, una línea de c caracteres. TextField(String t), crea un TextField inicializado con t. TextField(String t, int c), crea un TextField inicializado con t, de c caracteres. De los varios métodos, los más interesantes para nuestros fines son: boolean echoCharIsSet(), dice si el TextField realiza el "echo" de los caracteres introducidos. int getColumns(), da el número de columnas de los TextField. char getEchoChar(), da el carácter utilizado para el "echo". void setColumns(int columns), verifica el número de las columnas de los TextField. void setEchoChar(char c), verifica el carácter de "echo" para el TextField. void setText(String t), para verificar el texto introducido. Para el tamaño: Dimension getMinimumSize() Dimensión getMinimumSize(int c) Dimensión getPreferredSize() Dimensión getPreferredSize(int c) La TextArea, en cambio, es un verdadero texto, no sólo una línea, sino muchas. Incluye también las dos barras para navegar en el texto si éste es más grande de la ventana en la que está visualizado. Los constructores son: TextArea(), construye una nueva TextArea. TextArea(int r, int c) , construye una nueva TextArea, de r líneas y c columnas. TextArea(String text), construye una nueva TextArea inicializada con el texto pasado. TextArea(String text, int rows, int columns) , construye una nueva TextArea inicializada con el texto pasado, de r líneas y c columnas. TextArea(String text, int rows, int columns, int scrollbars), como el anterior, sólo que permite especificar la visualización de las barras de texto. Vamos a ver algunos métodos: void append(String str), cuelga el texto pasado a la TextArea. (En la viejas versiones de Java es appendText(String str)). int getColumns(), int getRows() para tener información sobre el número de columnas o líneas. void insert(String str, int pos), introduce el texto pasado en la posición pasada. (En las viejas versiones de Java es insertText(String str)). void replaceRange(String str, int start, int end), sustituye el texto de start a end con la cadena pasada. void setColumns(int columns) e void setRows(int rows) para verificar el número de columnas o de líneas de la TextArea. Para el tamaño del objeto GUI: Dimensión getMinimumSize() Dimensión getMinimumSize(int rows, int columns) Dimensión getPreferredSize() Dimensión getPreferredSize(int rows, int columns) Hemos visto todos los componentes de texto. Llegados a este punto podemos poner un ejemplo que los utiliza las dos. Creamos un aplique, por lo tanto el launcher es: <html> <head> <title> Ejemplo de uso de los componentes de texto. </title> </head> <body> <APPLET code="Texto.class" width=500 height=200> <param name=testo value="Texto inicial para la TextArea.Introducir más caracteres."> </APPLET> </body> </html> El código que hay que editar en Texto.java, es el siguiente: import java.awt.event.KeyListener; import java.awt.event.KeyEvent; import java.awt.TextField; import java.awt.TextArea; import java.awt.BorderLayout; import java.applet.*; public class Texto extends Applet { TextField TF=new TextField(20); TextArea TA=new TextArea(); public void init() { add(TA,BorderLayout.CENTER); add(TF,BorderLayout.SOUTH); String t=getParameter("texto"); if (t!=null) TA.setText(t); TF.setEditable(false); TA.addKeyListener(new MyKeyListener()); } public class MyKeyListener implements KeyListener { public void keyPressed(KeyEvent e) { } public void keyReleased(KeyEvent e) { int valor=e.getKeyCode(); if (valor!=10) TF.setText("Introducido carácter "+e.getKeyChar()); else TF.setText("Introducido carácter ENVIAR"); } public void keyTyped(KeyEvent e) { } } } La gestión de los sucesos en Java2 Hemos visto casi todos los componentes GUI de awt. Para construir una interfaz basta con combinarlos y escuchar los sucesos. Por cada GUI analizada, hemos visto cómo gestionar unos sucesos. Ahora vamos a ver qué sucesos se pueden escuchar. En primer lugar hemos escuchado unos sucesos de tipo ActionEvent, es decir, sucesos que actúan sobre las GUI gracias al usuario como, por ejemplo, pulsar un botón. Para esto, os recuerdo que hemos implementado la interfaz ActionListener, y hemos vuelto a definir el único suceso actionPerformed (ActionEvent e), que se invoca cuando se pone en marcha una acción sobre el componente GUI (o sobre los componentes) a los que está asociado. Para asociar al GUI el oyente de los sucesos que hemos definido, hemos utilizado el método addActionListener (ActionListener oyente) del GUI. Para usarlos hay que incluir en el programa: import java.awt.event.ActionListener; import java.awt.event.ActionEvent; o impore java.awt.event.*; Oyentes de sucesos de tipo ActionListener se pueden asociar a los objetos GUI de awt: Button List (cuando seleccionamos o no seleccionamos un elemento) MenuItem TextField Hemos visto también cómo implementar un ItemListener, para escuchar sucesos de la GUI que tienen un estado de on/off, cuando modifican su propiio estado. Para asociarlo a una GUI hemos utilizado addItemListener(ItemListener oyente) de la GUI. Cuando el suceso es generado por GUI, se invoca el método itemStateChanged(ItemEvent e) del oyente asociado. Hay que incluir: import java.awt.event.*; o import java.awt.event.ItemListener; import java.awt.event.ItemEvent; Los GUI awt para que pueda ser escuchado, son: Checkbox CheckboxMenuItem (no lo hemos visto. Es como un MenuItem, lo único es que funciona como una Checkbox, es decir, se puede clicar o no. Si se asocia a un CheckboxGroup, se convierte otra vez en un radiobutton, sólo que en un menú). Choice List Además hemos visto los oyentes de los sucesos de las ventanas, es decir, los de tipo WindowListener. Se asocian a las ventanas usando el método de éstas addWindowListener(WindowListener WL), y para definirlas hay que implementar la interfaz WindowListener y volver a definir los métodos: void windowActivated(WindowEvent e), invocado cuando la ventana está activa, es decir, cuando aparece en primer plano en el desktop. void windowClosed(WindowEvent e), invocado cuando la ventana se cierra. void windowClosing(WindowEvent e), invocado cuando se aprieta el botón cerrarVentana (X). void windowDeactivated(WindowEvent e), invocado cuando la ventana pasa a un segundo plano. void windowDeiconified(WindowEvent e), invocado cuando la ventana se convierte de icono a activa. void windowIconified(WindowEvent e), cuando se transforma en icono. void windowOpened(WindowEvent e), cuando se abre. Para escuchar los sucesos de la ventana hay que incluir: import java.awt.event.WindowListener; import java.awt.event.WindowEvent; o import java.awt.event.*; Los sucesos de las ventanas se manifiestan casi siempre por parejas. Por ejemplo, cuando pulsamos la tecla X en la parte de arriba a la derecha de la ventana, primero se invoca windowClosing y después windowClosed, o cuando una ventana se transforma de icono a activa, primero escucha windowDeiconified y luego WindowActivated, etc. Es posible escuchar sucesos de tipo ventana para los elementos: Window Frame Dialog Por lo que se refiere a los Dialog abro un paréntesis. Son Frame sin icono. Reducir a icono y maximizar son muy parecidos para hacer precisamente unos diálogos rápidos con el usuario. Son, por ejemplo, unos dialog las ventanas que aparecen en las aplicaciones y dicen: ¿estás seguro de que quieres salir? Un Dialog puede ser modal o no modal. El primero es un Dialog que bloquea completamente la aplicación y que, por tanto, ya no oye los sucesos, el segundo en cambio va en paralelo a ésta. Los dialog de awt no pueden incluir menús, mientras que sí los incluyen los de swing (javax.swing.JDialog) si. Vamos a ver qué sucesos pueden escuchar los objetos de tipo TextComponent, es decir, objetos de tipo TextField y TextArea. Podemos escuchar sucesos de tipo TextEvent, implementando la interfaz TextListener, y volviendo a definir el método textValueChanged(TextEvent e), invocado cada vez que el texto se modifica. La interfaz y el suceso se encuentran en java.awt.event Los objetos de tipo container, es decir, Panel, ScrollPane, Window (y, por lo tanto, Dialog y Frame), pueden escuchar los sucesos en el contenedor, utilizando el método addContainerListener(ContainerListener l). Hay que implementar la interfaz ContainerListener y volver a definir los métodos. void componentAdded(ContainerEvent e) void componentRemoved(ContainerEvent e) Éstos se invocan automáticamente cuando se añaden o se quitan elementos al contenedor. Hay que incluir java.awt.event.ContainerListener y java.awt.event.ContainerEvent. Llegados a este punto vamos a ver qué sucesos se pueden escuchar en objetos de tipo Component, es decir, en TODOS los componentes awt, incluidos los contenedores y las ventanas. En Component están los métodos: addComponentListener(ComponentListener l) addFocusListener(FocusListener l) addHierarchyBoundsListener(HierarchyBoundsListener l) addHierarchyListener(HierarchyListener l) addInputMethodListener(InputMethodListener l) addKeyListener(KeyListener l) addMouseListener(MouseListener l) addMouseMotionListener(MouseMotionListener l) addPropertyChangeListener(PropertyChangeListener listener) addPropertyChangeListener(String propertyName, PropertyChangeListener listener) Los ComponentListener escuchan sucesos en el componente y hay que volver a definir los métodos. void componentHidden(ComponentEvent e) void componentMoved(ComponentEvent e) void componentResized(ComponentEvent e) void componentShown(ComponentEvent e) Éstos se invocan cuando el componente se oculta, se mueve, cambia su tamaño y se visualiza. FocusListener escucha los sucesos de focus del teclado en el componente, es decir, si se selecciona o no el componente del teclado. Hay que volver a definir los métodos: focusGained(FocusEvent e), invocado cuando se selecciona el componente. focusLost(FocusEvent e), invocado por el componente cuando pierde la selección del teclado. Pensemos, por ejemplo, en la navegación de las varias form de una interfaz, pulsando la tecla TAB. HierarchyBoundsListener, escucha los sucesos de remoción de los antepasados del componente, por ejemplo, un botón en una ventana. Se implementa esta interfaz, escucha si la ventana se mueve. Hay que volver a definir los métodos: void ancestorMoved(HierarchyEvent e), invocada cuando el antepasado se remueve. void ancestorResized(HierarchyEvent e) , invocada cuando se cambia de tamaño el antepasado. HierarchyListener escucha los cambios de los antepasados del componente. Hay que volver a definir el método hierarchyChanged(HierarchyEvent e). Será HierarchyEvent el que incluye el cambio. KeyListener ya lo hemos visto: escucha los sucesos del teclado. Hay que volver a definir los métodos. void keyPressed(KeyEvent e), invocado cuando se tecla una tecla. void keyReleased(KeyEvent e), invocado cuando se deja de teclear una tecla. void keyTyped(KeyEvent e), invocado cuando se sigue pulsando una tecla. En resumen, cuando se teclea una tecla se oyen los sucesos en éste orden: keyPressed, keyTyped y keyReleased. MouseListener escucha los sucesos del ratón. Hay que volver a definir los métodos. void mouseClicked(MouseEvent e), invocado cundo se clica un botón del ratón en el componente. void mouseEntered(MouseEvent e), invocado cuando el ratón entra sobre el componente. void mouseExited(MouseEvent e), invocado cuando el ratón sale del componente. void mousePressed(MouseEvent e), invocado cuando se sigue teniendo pulsado el ratón sobre el componente. void mouseReleased(MouseEvent e), invocado cuando se deja de pulsar el ratón. MouseMotionListener, otros sucesos del ratón sobre el componente: mouseDragged(MouseEvent e), invocado cuando se aprieta y se mueve el ratón sobre el componente. void mouseMoved(MouseEvent e), invocado cuando el ratón se mueve sobre el componente, con o sin pulsar botones. La última interfaz para los sucesos del ratón es MouseInputListener, que implementa las dos interfaces anteriores. Por lo tanto escucha todos los sucesos anteriores del ratón. En el ejemplo a continuación se escuchan varios sucesos y se utiliza también un Dialog. import java.awt.event.*; import java.awt.*; class escuchar extends Frame { Button b1=new Button("Botón1"); Button b2=new Button("Visualiza Dialog"); Button b3=new Button("salir"); Button b4=new Button("Cerrar Dialog"); Choice l1=new Choice(); TextField TF1=new TextField(15); Dialog D; AscoltaActionListener aal; AscoltaItemListener ail; AscoltaWindowListener awl; AscoltaWindowListenerDialog awld; AscoltaMouseListener aml; AscoltaKeyListener akl; public Ascolta() { setTitle("Frame, ejemplo oyentes suscesos"); setLocation(100,100); setLayout(new GridLayout(5,1)); add(b1); add(b2); add(b3); add(l1); add(TF1); D=new Dialog(this,true); D.setSize(200,100); D.setLocation(200,100); D.add(b4,BorderLayout.SOUTH); D.setTitle("Dialog - modale"); preparaD(false); l1.add("Selección 1"); l1.add("Seleccióón 2"); l1.add("Selección 3"); // Inicializar los oyentes de los sucesos. aal=new AscoltaActionListener(); ail=new AscoltaItemListener(); awl=new AscoltaWindowListener(); awld=new AscoltaWindowListenerDialog(); aml=new AscoltaMouseListener(); akl=new AscoltaKeyListener(); b1.addActionListener(aal); b2.addActionListener(aal); b3.addActionListener(aal); TF1.addActionListener(aal); l1.addItemListener(ail); addWindowListener(awl); addMouseListener(aml); b1.addMouseListener(aml); b2.addMouseListener(aml); b3.addMouseListener(aml); b4.addMouseListener(aml); l1.addMouseListener(aml); TF1.addMouseListener(aml); TF1.addTextListener(new TextListener() { public void textValueChanged(TextEvent e) { System.out.println("TextListener: cambiar el valor de la TextField"); } } ); D.addMouseListener(aml); addKeyListener(akl); b1.addKeyListener(akl); b2.addKeyListener(akl); b3.addKeyListener(akl); b4.addKeyListener(akl); l1.addKeyListener(akl); TF1.addKeyListener(akl); D.addKeyListener(akl); pack(); show(); } void prepararD(boolean m) { D.setModal(m); } public static void main (String [] s) { System.out.println("Escuchar los sucesos."); new Escuchar(); } // ActionListener class EscucharActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { String c=e.getActionCommand(); if (c.compareTo("Botón1")==0) System.out.println("ActionListener: Pulsar Botón1"); else if (c.compareTo("Salir")==0) { System.out.println("ActionListener: Pulsar Salir"); System.exit(9); // es un número cualquiera, se llama exit code. } else if (c.compareTo("Visualizar Dialog")==0) { System.out.println("ActionListener: Pulsar Visualizar Dialog"); D.show(); b4.addActionListener(aal); b2.removeActionListener(aal); D.addWindowListener(awld); } else if (c.compareTo("Cerrar Dialog")==0) { System.out.println("ActionListener: Pulsar Visualizar Dialog"); D.hide(); b4.removeActionListener(aal); b2.addActionListener(aal); D.removeWindowListener(awld); } else { System.out.println("ActionListener: Pulsar Enviar en la TextField, letto:"+TF1.getText()); TF1.setText(""); } } } // ItemListener class EscucharItemListener implements ItemListener { public void itemStateChanged(ItemEvent e) { String val=l1.getSelectedItem(); System.out.println("ItemListener: Seleccionado "+val); } } // WindowListener public class EscucharWindowListener implements WindowListener { public void windowActivated(WindowEvent e) { System.out.println("WindowListener: Ventana activada"); } public void windowClosed(WindowEvent e) { System.out.println("WindowListener: Ventana cerrada"); } public void windowClosing(WindowEvent e) { System.out.println("WindowListener: Pulsar cerrar ventana"); System.exit(0); } public void windowDeactivated(WindowEvent e) { System.out.println("WindowListener: Ventana no activada"); } public void windowDeiconified(WindowEvent e) { System.out.println("WindowListener: Ventana sin icono"); } public void windowIconified(WindowEvent e) { System.out.println("WindowListener: Ventana reducir a icono"); } public void windowOpened(WindowEvent e) { System.out.println("WindowListener: Ventana abierta"); } } public class EscucharWindowListenerDialog implements WindowListener { public void windowActivated(WindowEvent e) { System.out.println("WindowListener: Dialog activado"); } public void windowClosed(WindowEvent e) { System.out.println("WindowListener: Dialog cerrado"); } public void windowClosing(WindowEvent e) { System.out.println("WindowListener: Pulsar cerrar Dialog"); D.hide(); b4.removeActionListener(aal); b2.addActionListener(aal); D.removeWindowListener(awld); } public void windowDeactivated(WindowEvent e) { System.out.println("WindowListener: Ventana no activada"); } public void windowDeiconified(WindowEvent e) { System.out.println("WindowListener: Dialog sin icono"); } public void windowIconified(WindowEvent e) { System.out.println("WindowListener: Dialog reducir a icono"); } public void windowOpened(WindowEvent e) { System.out.println("WindowListener: Dialog abierto"); } } // Mouse Listener: class EscucharMouseListener implements MouseListener { public void mouseClicked(MouseEvent e) { Component a=e.getComponent(); String n=a.getName(); System.out.println("MouseListener: Mouse clicado sobre el componente "+n); } public void mouseEntered(MouseEvent e) { Component a=e.getComponent(); String n=a.getName(); System.out.println("MouseListener: Mouse entrado en el componente "+n); } public void mouseExited(MouseEvent e) { Component a=e.getComponent(); String n=a.getName(); System.out.println("MouseListener: Mouse salido del componente "+n); } public void mousePressed(MouseEvent e) { Component a=e.getComponent(); String n=a.getName(); System.out.println("MouseListener: el botón está pulsado sobre el componente"+n); } public void mouseReleased(MouseEvent e) { Component a=e.getComponent(); String n=a.getName(); System.out.println("MouseListener: Mouse no está pulsado en el componente "+n); } } // Teclado class AscoltaKeyListener implements KeyListener { public void keyPressed(KeyEvent e) { System.out.println("KeyListener: Pulsar tecla "+e.getKeyChar()+ " codice "+ e.getKeyCode()); } public void keyReleased(KeyEvent e) { System.out.println("KeyListener: Dejar de pulsar tecla "+e.getKeyChar()+ " codice "+ e.getKeyCode()); } public void keyTyped(KeyEvent e) { System.out.println("KeyListener: Estás pulsando la tecla "+e.getKeyChar()+ " codice "+ e.getKeyCode()); } } } Introducción a swing Además del paquete java.awt, Java pone a disposición del programador el paquete javax.swing para crear unas interfaces gráficas. En este capítulo veremos para qué la Sun Microsystem ha creado otro paquete y qué diferencias hay con awt. Para presentar el paquete hablaré del programa que estoy preparando para mi tesina en la Facultad de Informática de la Universidad de Pisa. Para informaciones con respecto a Orespics podéis poneros en contacto conmigo o con las profesoras Laganà y Ricci del Departamento de Informática de Pisa (Corso Italia, 40). El nombre del programa es Orespics Programming Language y, en práctica, es un ambiente integrado en el que es posible programar agentes paralelos que comunicam entre ellos, intercambiando mensajes. El ambiente permite definir y poner en marcha estos agentes. Se propone como instrumento de soporte didáctico al aprendizaje de las modalidades de programación paralela, programación que puede resultar muy difícil. Precisamente por ser un instrumento didáctico, tiene una interfaz usuario User Friendly. La interfaz ha sido escrita utilizando el paquete Swing de Java. Swing ha sido totalmente escrito en Java utilizando el paquete awt, y pone a disposición del usuario muchas clases que están también en awt, pero mucho mejores y más potentes. Además introduce muchas más clases que no están en awt. Por lo tanto, veamos cuáles son las clases y las interfaces incluidas en el paquete: Interfaces Action BoundedRangeModel ButtonModel CellEditor ComboBoxEditor ComboBoxModel DesktopManager Icon JComboBox.KeySelectionManager ListCellRenderer ListModel ListSelectionModel MenuElement MutableComboBoxModel Renderer RootPaneContainer Scrollable ScrollPaneConstants SingleSelectionModel SwingConstants UIDefaults.ActiveValue UIDefaults.LazyValue WindowConstants Clases AbstractAction AbstractButton AbstractCellEditor AbstractListModel ActionMap BorderFactory Box Box.Filler BoxLayout ButtonGroup CellRendererPane ComponentInputMap DebugGraphics DefaultBoundedRangeModel DefaultButtonModel DefaultCellEditor DefaultComboBoxModel DefaultDesktopManager DefaultFocusManager DefaultListCellRenderer DefaultListCellRenderer.UIResource DefaultListModel DefaultListSelectionModel DefaultSingleSelectionModel FocusManager GrayFilter ImageIcon InputMap InputVerifier JApplet JButton JCheckBox JCheckBoxMenuItem JColorChooser JComboBox JComponent JDesktopPane JDialog JEditorPane JFileChooser JFrame JInternalFrame JInternalFrame.JDesktopIcon JLabel JLayeredPane JList JMenu JMenuBar JMenuItem JOptionPane JPanel JPasswordField JPopupMenu JPopupMenu.Separator JProgressBar JRadioButton JRadioButtonMenuItem JRootPane JScrollBar JScrollPane JSeparator JSlider JSplitPane JTabbedPane JTable JTextArea JTextField JTextPane JToggleButton JToggleButton.ToggleButtonModel JToolBar JToolBar.Separator JToolTip JTree JTree.DynamicUtilTreeNode JTree.EmptySelectionModel JViewport JWindow KeyStroke LookAndFeel MenuSelectionManager OverlayLayout ProgressMonitor ProgressMonitorInputStream RepaintManager ScrollPaneLayout ScrollPaneLayout.UIResource SizeRequirements SizeSequence SwingUtilities Timer ToolTipManager UIDefaults UIDefaults.LazyInputMap UIDefaults.ProxyLazyValue UIManager UIManager.LookAndFeelInfo ViewportLayout Excepciones UnsupportedLookAndFeelException Está claro que hay muchas más clases de las de swing. Además hay muchas que tienen el mismo nombre de algunas clases de awt, excepto una J inicial, como, por ejemplo, JFrame, JDialog, etc. Estas clases funcionan igual que las de awt, pero contienen muchos métodos más útiles. Presentamos algunas de estás cosas que están en swing más que en awt. En swing están las Toolbar, es decir, unas pequeñas barras sobre las que hay unos botones. Se pueden mover dentro de las ventanas que las incluyen. Los botones de swing, como casi todos los demás JComponent, pueden tener tanto una etiqueta como una imagen, imagen que puede cambiar según el estado del botón. Además en swing se pueden utilizar los Tooltip, es decir, aquellas sugerenias que salen automáticamente en un componente cuando el ratón se detiene en él durante un rato. Vamos a ver ahora el efecto de estos componentes swing en Orespics. En el Dibujo podemos notar la Toolbar (en la que he ocultado los bordes) de otras 8 Toolbar, una por cada categoría de botones. Se ven los botones que incluyen las imágenes, unas activas y otras no. En el Dibujo se puede ver un Tooltip en uno de los botones. Además podemos notar que incluso en los menús se pueden introducir unas imágenes y esto las convierte en imágenes muy agradables para el usuario. Las toolbar se pueden ocultar fácilmente y se pueden visualizar en la pantalla. En el dibijo notamos el cambio de la imagen del botón con el pingüino, que se hace más grande con swing. Como ya hemos dicho, es posible cambiar el icono según el estado del botón. Otros dos componentes muy interesantes de Swing son los TabbedPane y los Árboles. Los primeros son los controles a tarjetas típicos de las interfaces de Windows, muy agradables. Los segundos muestran las estructuras a árbol como las que se ven cuando se navega en los discos duros utilizando explorer de Windows. Con swing se puede incluso cambiar el llamado Look and Feel de la ventana, es decir, su aspecto, convirtiéndola en algo parecido a una interfaz típica de Java, de Windows, de Macintosh (si está en un aparato Mac) y de Linux (X-Windows - Motif). Con Swing cambia completamente la gestión del texto. Se pueden crear texto de colores, con distintos tipos de caracteres. Muchas más son las posibilidades de Swing, por ejemplo, unos Dialog para navegar en los archivos con unas preview y unos dialog para elegir los colores. Estas opciones ya vienen implementadas en el paquete. A mí me resulta muy interesante saber que el dialog de selección de los colores del Dibujo 16 se crea y se utiliza con una sola orden, es decir, que basta con invocar el método: static JDialog createDialog(Component c, String title, boolean modal, JColorChooser chooserPane, ActionListener okListener, ActionListener cancelListener) de la clase JColorChooser. De todas formas creo que os han quedado claras las potencialidades del paquete que, sin embargo, todavía no está incluido en las versiones de Java de los navegadores y que, por lo tanto, todavía no es posible crear unos apliques swing sin cargar plugins en los navegadores. En los textos de swing es posible introducir incluso imágenes y, además, se pueden leer y decodificar directamente los archivos de html, rtf (Un primo de .doc). En Swing está la clase Jdialog, parecida a la Applet de java.applet. Es una extensión y presenta, además de los métodos de la clase Applet (de la que es una extensión), otros métodos muy interesantes, entre ellos el método void setJMenuBar(JMenuBar menuBar). Obviamente, como hay muchos GUI más en swing, es necesario gestionar los sucesos. Para esto está el paquete de java.awt.event que gestiona dichos sucesos. Fundamentos para el dibujo con Java En el capítulo anterior hemos visto cómo utilizar los componentes GUI de Abstract Window Toolkit para construir unas interfaces gráficas para el usuario de nuestros programas, y hemos dicho que un componente GUI es, en suma, un objeto con unas cualidades gráficas. El componente GUI es de nivel alto en el paquete awt, y se construye utilizando los componentes de nivel bajo del paquete, es decir, las primitivas gráficas y los sucesos. Pensemos, por ejemplo, en un botón. Éste, gráficamente, no es otra cosa que una caja con dentro un texto, que cambia aspecto cuando se aprieta, es decir, cuando escucha su suceso ActionEvent. Utilizando las características de bajo nivel podemos construir nuestros GUI personalizados o modificar los que ya existen según nuestras necesidades. No solamente podemos extender un componente particular, un componente lienzo, en el que podemos dibujar lo que queramos y ponerlo en nuestras interfaces como cualquier otro GUI. Si utilizamos los apliques más que las aplicaciones, podemos evitar utilizar el lienzo porque el aplique trae implícitamente un lienzo. De todas formas, empecemos a ver en concreto cómo se puede dibujar en un componente cualquiera o en un aplique. Primero hablaremos de los apliques y luego pasaremos a los componentes en general. Las funciones paint, repaint y update... En el lenguaje Java es posible dibujar en un aplique simplemente volviendo a definir el método paint. Este método se invoca automáticamente por el sistema cuando la ventana que incluye el aplique pasa a un primer plano. Por lo tanto cada orden gráfica al aplique se dará en el método paint del aplique. void paint (Graphics g) { // Dibujo } Cuando el aplique haya dibujado y se quiera visualizar posibles cambios, es posible invocar el método repaint() o el método update(Graphics g) que borra el área en el que el aplique ha dibujado y vuelve a invocar la paint. El método paint(Graphics g) no se puede invocar directamente. El aplique no sólo tiene el método paint que se puede volver a definir, sino también los objetos de tipo Component, es decir, todos los GUI. Por lo tanto, para cambiar el aspecto gráfico de cualquier componente gráfico, basta con volver a definir el método paint. Entre todos los componentes está el ya citado lienzo sobre el que es posible dibujar. Es un objeto de la clase Canvas. Los que están acostumbrados a programar con otros lenguajes esperan que en la paint haya un inicializador del device en el que se va a dibujar y, después, una serie de instrucciones de tipo drawLine, drawBox etc., sin embargo, os tengo que decir que esto no siempre es verdad, es decir, que en los componentes y en los apliques no hay ningún método gráfico. Llegados a este punto, queda claro incluso que el parámetro g de tipo Graphics de la paint incluye los métodos gráficos que Java puede utilizar y representar, por decirlo de alguna forma, el área en el que se dibujarán las primitivas. Graphics es una clase que no se puede incluir, sino que es recibida por la paint como parámetro. Por lo tanto se puede utilizar sólo en ella y sus métodos son: abstract void clearRect(int x, int y, int width, int height) abstract void clipRect(int x, int y, int width, int height) abstract void copyArea(int x, int y, int width, int height, int dx, int dy) abstract Graphics create() Graphics create(int x, int y, int width, int height) abstract void dispose() void draw3DRect(int x, int y, int width, int height,boolean raised) abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) void drawBytes(byte[] data, int offset, int length, int x, int y) void drawChars(char[] data, int offset, int length, int x, int y) abstract boolean drawImage(…) Ne esistono varie versioni abstract void drawLine(int x1, int y1, int x2, int y2) abstract void drawOval(int x, int y, int width, int height) abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) void drawPolygon(Polygon p) abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) void drawRect(int x, int y, int width, int height) abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) abstract void drawString(AttributedCharacterIterator iterator, int x, int y) abstract void drawString(String str, int x, int y) void fill3DRect(int x, int y, int width, int height, boolean raised) abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) abstract void fillOval(int x, int y, int width, int height) abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) void fillPolygon(Polygon p) abstract void fillRect(int x, int y, int width, int height) abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) void finalize() abstract Shape getClip() abstract Rectangle getClipBounds() Rectangle getClipBounds(Rectangle r) abstract Color getColor() abstract Font getFont() FontMetrics getFontMetrics() abstract FontMetrics getFontMetrics(Font f) boolean hitClip(int x, int y, int width, int height) abstract void setClip(int x, int y, int width, int height) abstract void setClip(Shape clip) abstract void setColor(Color c) abstract void setFont(Font font) abstract void setPaintMode() abstract void setXORMode(Color c1) String toString() abstract void translate(int x, int y) Dentro de poco veremos con detalle todos estos métodos. En Java2 la clase Graphics ha sido potenciada añadiendo la clase Graphics2D, que la amplía añadiendo muchas posibilidades a la gráfica de Java. Para tener una pequeña muestra de las potencialidades de Java podéis ver la Demo que está junto a las JDK. Si, por ejemplo, habéis bajado las JDK1.3 y las habéis instalado en c:jdk1.3, la demo está en: c:jdk1.3demojfcJava2D y para ponerla en marcha hay que escribir del prompt: java -jar Java2Demo.jar Visualización de las imágenes Empezamos viendo los métodos drawImage de la clase Graphics con los que es posible visualizar unas imágenes guardadas con formato gif o jpg. En la clase Graphics hay varios mátodos drawImage que son: abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) abstract boolean drawImage(Image img, int x, int y, ImageObserver observer) abstract boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) abstract boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) Todos los métodos requieren dos parámetros Image y ImageObserver. Image rapresenta la imagen que hay que visualizar. Es una clase abstracta que permite un acceso independiente del formato de imágenes gráficas. Los objetos de esta clase se crean utilizando los métodos de otras clases que crean imágenes, según el contexto en el que se quieran utilizar. Por ejemplo, si quiero dibujar una imagen en un componente Gui, puedo usar el método createImage() de la clase Component. En cambio, si quiero dibujar una imagen en un aplique, es posible usar el método getImage() de la clase Applet. La clase Toolkit tiene tanto createImage() como getImage(). El otro parámetro que necesitan las drawImage() es un objeto que implementa la interfaz ImageObserver, y en la práctica representa el objeto gráfico sobre el que se visualizará la imagen. Component implementa la interfaz ImageObserver y, consecuentemente, la implementan todas sus extensiones: Todos los GUI Los Apliques Todas las ventanas (incluso los Frames) Los demás parámetros son: la posición x en la que se visualizará la imagen en el componente la posición y en la que se visualizará la imagen en el componente el color del fondo Vamos a poner un ejemplo de visualización de una imagen. En primer lugar creamos una imagen como la siguiente: y la guardamos en formato gif usando un editor cualquiera de imágenes llamándola, por ejemplo, pietro.jpg. Ahora creamos el siguiente aplique: import java.awt.*; // Para la clase Graphics import java.applet.*; // Para la clase Applet import java.net.*; // Para las URL /* Como hemos tenido ya la ocasión de decir, los archivos para los apliques no son otra cosa que unas URL */ public class VisualizaImagen extends Applet { Image ImagenPietro; public void init() { setBackground(Color.white); ImagenPietro=getImage(getDocumentBase(),"pietro.jpg"); } public void paint(Graphics g) { g.drawImage(ImagenPietro,0,0,this); getAppletContext().showStatus("Visualizo la imagen pietro.jpg, Applet creada por Pietro Castellucci"); } } La ponemos en un archivo llamado VisualizaImagen.java y la invocamos usando el documento html siguiente (Imagen.html): <html> <head> <title> Applet VisualizaImagen, visualiza la imagen pietro.jpg </title> </head> <body> <APPLET code="VisualizaImagen.class" width=385 height=100> </APPLET> <BR> La de arriba es un aplique, sin embargo la de abajo no lo es. <BR> <IMG SRC="pietro.jpg"> </body> </html> Si ponemos en marcha el aplique nos damos cuenta de que no hay ninguna diferencia entre la imagen cargada en el aplique y la que hemos cargado usando el tag IMG SRC del html, sino el mensaje que aparece cuando pasamos por encima el ratón. Os podría parecer incluso un trabajo inútil para vuestras páginas html. Esto es absolutamente falso porque la imagen cargada en el aplique pertenece a un programa que puede hacer incluso cosas complejas. Por ejemplo, es posible añadir otros componentes GUI al aplique, o elaborar las imágenes mismas, como se ve en el ejemplo sucesivo (VisualizaImagen2.java): import java.awt.*; // Para la clase Graphics import java.applet.*; // Para la clase Applet import java.net.*; // Para las URL import java.awt.event.*; // Para los sucesos /* Como ya hemos dicho, los archivos para los apliques no son otra cosa que unas URL */ public class VisualizaImagen2 extends Applet { Image ImagenPietro; CheckboxGroup gruppo=new CheckboxGroup(); Checkbox b1=new Checkbox("Parar",grupo,true); Checkbox b2=new Checkbox("Animada",grupo,false); Panel p=new Panel(new GridLayout(1,3)); public void init() { setBackground(Color.white); setLayout(new BorderLayout()); ImagenPietro=getImage(getDocumentBase(),"pietro.jpg"); p.add(new Label()); p.add(b1); p.add(b2); b1.addItemListener(new IL()); b2.addItemListener(new IL()); add(p,BorderLayout.SOUTH); } boolean MOVIMIENTO=false; ent número=0; ent inc=1; public void paint(Graphics g) { if (!MOVIMIENTO) { g.drawImage(ImagenPietro,0,0,this); getAppletContext().showStatus("Visualizo la imagen pietro.jpg, Imagen inmóvil, Applet creada Pietro Castellucci"); número=0; } else { g.setPaintMode(); g.drawImage(Elabora(ImagenPietro,número),0,0,this); for (int k=0;k<=99;k++) getAppletContext().showStatus("Visualizo la imagen pietro.jpg, Frame "+número+", Applet creada por Pietro Castellucci"); getAppletContext().showStatus("Visualizo la imagen pietro.jpg, Frame "+número+", Applet creada por Pietro Castellucci"); if (número>=10) inc=-1; if (número<=0) inc=1; número+=inc; } repaint(); } Image Elabora (Image img, int frm) { return img.getScaledInstance(324-(frm*20), 85-(frm*4),img.SCALE_FAST); } public class IL implements ItemListener { public void itemStateChanged(ItemEvent e) { if (b1.getState()) MOVIMIENTO=false; else MOVIMIENTO=true; repaint(); } } } Cargada por el archivo html siguiente: <html> <head> <title> Applet VisualizaImagen, visualiza la imagen pietro.jpg </title> </head> <body> <APPLET code="VisualizaImagen2.class" width=385 height=100> </APPLET> <BR> La de arriba es un aplique, sin embargo la de abajo no lo es <BR> <IMG SRC="pietro.jpg"> </body> </html> La nueva clase Graphics2D pone a disposición otros métodos drawImage, entre los que hay algunos que tienen un parámetro de tipo AffineTransform que, como dice el nombre, es una transformación parecida a la de la imagen, una rotación o una translación, o una combinación de todas. Para tener una versión sin revoloteo basta con añadir la función: public void update(Graphics g) { paint(g); } Dibujo Entendido el funcionamiento de la paint() podemos dibujar cualquier cosa en un aplique o en un componente en general. La cosa más simple que se puede dibujar es la línea. Para hacerlo, utilizamos el método drawLine() de Graphics: drawLine(Iniciox, Inicioy, Finx, Finy) Éste dibuja una línea que parte de un punto (Iniciox, Inicioy) y llega a otro punto (Finx, Finy). Antes de utilizar la drawLine() tenemos que comprender cómo se pueden cambiar los colores de las cosas que dibujamos. Los colores se cambian utilizando el método setColor(Color c) de la clase Graphics. Color es otra clase de awt que permite definir un color. Algunos de sus constructores son: Color(float r, float g, float b), crea un color especificando los valores RGB (Red - Green - Blue, es decir, Rojo - Verde - Azul) con unos valores entre 0 y 1. Color(float r, float g, float b, float a), como el anterior, sólo que permite definir incluso el valor alfa Color(int r, int g, int b) , crea un color especificando los valores RGB (Red - Green - Blue, es decir, Rojo - Verde - Azul) con valores entre 0 y 255. Color(int r, int g, int b, int a), como el anterior, sólo que permite definir incluso el valor alfa Algunos colores simples se dan a través de unas constantes en la clase color: Color.black Color.blue Color.cyan Color.darkGray Color.gray Color.green Color.lightGray Color.magenta Color.orange Color.pink Color.red Color.white Color.yellow En el siguiente aplique se dibujan unas líneas de colores. import java.awt.Graphics; import java.awt.Color; import java.awt.Button; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.Panel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.applet.*; public class líneas extends Applet { final ent SI=14; final ent NO=0; Button R=new Button("Rojo"); Button G=new Button("Verde"); Button B=new Button("Azul"); ent r_=0; ent g_=0; ent b_=0; ent ir=0; ent ig=0; ent ib=0; public void init() { setLayout(new BorderLayout()); Panel np=new Panel(new GridLayout(1,3)); np.add(R); np.add(G); np.add(B); R.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ir=SÍ; ig=NO; ib=NO; repaint(); } } ); G.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ir=NO; ig=SÍ; ib=NO; repaint(); } } ); B.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ir=NO; ig=NO; ib=SÍ; repaint(); } } ); add(np,BorderLayout.SOUTH); } public void paint(Graphics g) { for (ent e=0;e<200;e+=10) { g.setColor(new Color(r_,g_,b_)); r_=r_+ir; g_=g_+ig; b_=b_+ib; g.drawLine(0,i,i,200); } g.setColor(Color.black); g.drawLine(0,0,0,200); g.drawLine(0,200,200,200); r_=0; g_=0; b_=0; } } El archivo lo llamamos líneas.java y lo cargamos con el archivo html siguiente (líneas.html): <html> <head> <title> Applet Líneas (líneas.class) </title> </head> <body> <APPLET code="lineas.class" width=200 height=300> </APPLET> </body> </html> Figuras geométricas y texto RECTÁNGULOS Graphics permite dibujar unos rectángulos especificando dos aristas opuestas usando el método: drawRect(Iniciox,Inicioy,Finx,Finy); Este método es igual a: drawLine(Iniciox,Inicioy,Finx,Inicioy); drawLine(Iniciox,Inicioy,Iniciox,Finy); drawLine(Finx,Inicioy,Finx,Finy); drawLine(Iniciox,Finy,Finx,Finy); Es posible incluso construir rectángulos que den un efecto levantado o ahuecado usando el método draw3DRect(ent x,ent y,int anchura,ent altura,boolean levantado) y rectángulos con las aristas redondeadas con el método drawRoundRect(ent x, ent y, ent anchura, ent altura, ent anchuraArco, ent alturaArco). Además es posible colorear en todas las figuras cerradas usando los métodos fill. Por ejemplo, para colorear un RoundRect hay que utilizar fillRoundRect, que tiene los mismos parámetros que la correspondiente draw. CÍRCULOS Y ELIPSIS Se puede dibujar una elipsis usando la drawOval (ent x, ent y, ent anchura,ent altura) y colorearla usando la fillOval(ent x, ent y, ent anchura,ent altura). Si la altura y la anchura son iguales se dibuja un círculo. POLÍGONOS Usando la drawPolygon se dibuja un polígono genérico. Hay dos tipos de drawPolygon: drawPoligon( ent[] PUNTOSx, ent PUNTOSy, ent NUMEROSPUNTOS); e drawPolygon (Polygon p) La segunda usa un objeto de la clase Polygon que define un polígono. Están las correspondientes fillPolygon. TEXTO Para dibujar una cadena se puede utilizar el método drawString (String TEXTO, ent x, ent y); Un texto se puede dibujar con distintas Font. Para cambiar las Font Graphics pone a disposición el método setFont(Font font), en el que font es el objeto que define el tipo de carácter. Font es la clase que define los caracteres y es posible invocarla creando un Font específico, usando Font(String name, ent style, ent size). Font es una clase bastante complicada, sin embargo es fácil encontrar y usar los Fonts del sistema mientras se pone en marcha programma java: En las viejas versiones de java basta con escribir: String [] NOMBRES=Toolkit.getDefaultToolkit().getFontList(); Mientras en las nuevas versiones de Java (de JDK 1.2 ) String [] NOMBRES=GraphicsEnvironment. getLocalGraphicsEnvironment(). getAvailableFontFamilyNames(); Además es fácil escribir objetos sabiendo los nombres. ARCOS Puedo dibujar unos arcos usando drawArc(ent x, ent y, ent anchura, ent altura, ent ánguloInicial, ent ánguloArco) El uso de todos estos métodos se explica en el ejemplo que ponemos a continuación. El archivo que lo pone en marcha es: <html> <head> <title> Applet grafDemo (grafDemo.class) </title> </head> <body> <APPLET code="grafDemo.class" width=500 height=400> </APPLET> </body> </html> Archivo grafDemo.java El ejemplo, editado en el archivo grafDemo.java, es: import java.awt.Graphics; import java.awt.Color; import java.awt.Button; import java.awt.BorderLayout; import java.awt.GridLayout; import java.awt.FlowLayout; import java.awt.Panel; import java.awt.Label; import java.awt.Choice; import java.awt.Font; import java.awt.Toolkit; import java.awt.Checkbox; import java.awt.Scrollbar; import java.awt.GraphicsEnvironment; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.AdjustmentListener; import java.awt.event.AdjustmentEvent; import java.applet.*; public class grafDemo extends Applet { Scrollbar R=new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 255); Scrollbar G=new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 255); Scrollbar B=new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 255); Choice FN=new Choice(); Choice GR=new Choice(); Checkbox F=new Checkbox("Figuras cerradas llenas",false); ent [] puntosX={1,100,200,300,399,300,200,100}; ent [] puntosY={200,150,50,150,200,100,250,130}; ent puntos=8; public void init() { // Calculo los Font del sistema: // String[] NOMBRES=GraphicsEnvironment. // getLocalGraphicsEnvironment(). // getAvailableFontFamilyNames(); /* Para Java presentes en los browser String [] NOMBRES=Toolkit.getDefaultToolkit().getFontList(); */ String [] NOMBRES=Toolkit.getDefaultToolkit().getFontList(); try { ent índice=0; while (true) { FN.addItem(NOMBRES[índice++]); } } catch (ArrayIndexOutOfBoundsException e) {}; setLayout(new BorderLayout()); Panel np=new Panel(new GridLayout(1,3)); Panel rojo=new Panel(new FlowLayout()); rojo.add(new Label("Rojo")); rojo.add(R); np.add(rojo); Panel verde=new Panel(new FlowLayout()); verde.add(new Label("Verde")); verde.add(G); np.add(verde); Panel azul=new Panel(new FlowLayout()); azul.add(new Label("Azul")); azul.add(B); np.add(azul); R.setUnitIncrement(10); R.setValue(255); G.setUnitIncrement(10); G.setValue(255); B.setUnitIncrement(10); B.setValue(255); add(np,BorderLayout.SOUTH); GR.addItem("Línea"); GR.addItem("Rectángulo"); GR.addItem("Rectángulo 3D"); GR.addItem("Rectángulo Redondeado"); GR.addItem("Círculo"); GR.addItem("Elípses"); GR.addItem("Polígono genérico"); GR.addItem("Texto"); GR.addItem("Arco"); Panel up=new Panel(new GridLayout(1,3)); up.add(GR); up.add(FN); up.add(F); add(up,BorderLayout.NORTH); R.addAdjustmentListener(new AL()); G.addAdjustmentListener(new AL()); B.addAdjustmentListener(new AL()); GR.addItemListener(new IL()); FN.addItemListener(new IL()); F.addItemListener(new IL()); } public void paint(Graphics g) { g.setColor(new Color(255-R.getValue(), 255-G.getValue(), 255-B.getValue() )); g.setFont(new Font(FN.getSelectedItem(),0,40)); boolean filled=F.getState(); int pg=GR.getSelectedIndex(); g.drawString(GR.getSelectedItem(),1,300); if (pg==0) { g.drawLine(1,100,399,300); return; } if (pg==1) { g.drawRect(50,100,250,100); if (filled) g.fillRect(50,100,250,100); return; } if (pg==2) { g.draw3DRect(50,100,250,100,true); return; } if (pg==3) { g.drawRoundRect(50,100,300,100,20,20); if (filled) g.fillRoundRect(50,100,300,100,20,20); return; } if (pg==4) { g.drawOval(100,100,200,200); if (filled) g.fillOval(100,100,200,200); return; } if (pg==5) { g.drawOval(100,100,200,100); if (filled) g.fillOval(100,100,200,100); return; } if (pg==6) { g.drawPolygon(puntosX,puntosY,puntos); if (filled) g.fillPolygon(puntosX,puntosY,puntos); return; } if (pg==7) { g.drawString("Ésta es una cadena",1,200); return; } if (pg==8) { g.drawArc(1,50,398,200,10,270); if (filled) g.fillArc(1,50,398,200,10,270); return; } } public class IL implements ItemListener { public void itemStateChanged(ItemEvent e) { repaint(); } } public class AL implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { repaint(); } } } Notas para redactar el programa Visto que el programa ha sido creado para un navegador (que tiene las versiones de Java) se ha tenido que utilizar String [] NOMBRES=Toolkit.getDefaultToolkit().getFontList(); para obtener los caracteres, si se redacta con JDK 1.2 o 1.3 se tiene que escribir: Ø javac grafDemo.java -deprecation Esto dará un aviso para decir que se usa un método deprecated. Si, en cambio, utilizáis el appletviewer para visualizar el aplique, podéis cambiar String [] NOMBRES=Toolkit.getDefaultToolkit().getFontList(); con String [] NOMBRES=GraphicsEnvironment. getLocalGraphicsEnvironment(). getAvailableFontFamilyNames(); y apreciar la gran cantidad de Font que hay. En el ejemplo, he utilizado los colores de scrollbar, entre ellos los GUI que no hemos visto. Para crear el objeto scrollbar he utilizado el constructor: Scrollbar R=new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 255); que crea una barra (scrollbar) vertical con valores entre 0 y 255 con valor inicial 0. Después he averiguado el valor de la barra con R.setValue(255); he dicho que el incremento por cada clic tiene que ser 10 con R.setUnitIncrement(10); Para escuchar los cambios, utilizo un objeto que implementa la clase AdjustmentListener que, a su vez, escucha los sucesos de tipo AdjustmentEvent. Para asociar el oyente al objeto scrollbar se utiliza el método addAdjustmentListener. El sonido de Java 1.1.x y 1.2.x Hasta ahora hemos intentado crear programas compatibles entre las viejas y las nuevas versiones de Java. Esto para crear unos apliques generales que puedan trabajar en todos los browser (que tienen actualmente Java 1.1.x come motor para los apliques). En el apartado que estamos a punto de tratar, desgraciadamente esto no es posible. Con las viejas versiones de Java se pueden crear unos apliques que leen y tocan archivos de tipo .au, usando los métodos play de applet y la interfaz AudioClip. Todo esto es posible todavía con Java2, versión 1.3, sin embargo se han incluido unos paquetes para gestionar los sonidos que ofrecen potencialidades excepcionales. El sonido en jdk1.3: javax.swing.sampled Los paquetes que se han añadido al lenguaje son 4: javax.sound.midi javax.sound.midi.spi javax.sound.sampled javax.sound.sampled.spi Las partes que acaban por spi sirven para leer archivos sonoros, convertirlos o leer los archivos de instrumentos en el caso de midi. Nosotros no vamos a ver estás partes, sin embargo veremos javax.sound.sampled y javax.sound.midi. El paquete javax.swing.sampled permite grabar, tocar y modificar datos sonoros. El contenido del paquete es: Interfaces Clip DataLine Line LineListener Mixer Port SourceDataLine TargetDataLine Clases AudioFileFormat AudioFileFormat.Type AudioFormat AudioFormat.Encoding AudioInputStream AudioPermission AudioSystem BooleanControl BooleanControl.Type CompoundControl CompoundControl.Type Control Control.Type DataLine.Info EnumControl EnumControl.Type FloatControl FloatControl.Type Line.Info LineEvent LineEvent.Type Mixer.Info Port.Info ReverbType Excepciones LineUnavailableException UnsupportedAudioFileException Vamos a ver cómo utilizar este paquete sólo para tocar un archivo sonoro, por ejemplo, el archivo llamado italian.wav. En primer lugar, tenemos que buscar el archivo y esto lo hacemos, como siempre, introduciendo un objeto de la clase File (de java.io) File sf=new File("italian.wav"); Llegados a este punto, empezamos una try{} catch, en la que incluimos todo lo que tiene que ver con el sonido. Después capturaremos las excepciones: catch(UnsupportedAudioFileException ee){} catch(IOException ea){} catch(LineUnavailableException LUE){}; Para usar el motor sonoro de Java tenemos que empezar por la clase AudioSystem y de ésta obtendremos dos objetos de tipo AudioFileFormat y AudioInputStream AudioFileFormat aff=AudioSystem.getAudioFileFormat(sf); AudioInputStream ais=AudioSystem.getAudioInputStream(sf); que representan el contenido del archivo italian.wav tenemos que crear un objeto AudioFormat de AudioFileFormat para que se pueda tocar AudioFormat af=aff.getFormat(); Nuestro objetivo es crear un objeto que implemente la interfaz Clip, que incluye los métodos play. Para hacer esto escribiremos: DataLine.Info info = new DataLine.Info( Clip.class, ais.getFormat(), ((int) ais.getFrameLength() * af.getFrameSize())); Clip ol = (Clip) AudioSystem.getLine(info); De ol, que es el tipo Clip, podemos tocar y esto se consigue abriendo una línea e invocando los métodos para tocar: ol.open(ais); <-(ABRIR l'input stream, es decir, el archivo italian.wav) y, por lo tanto ol.loop(NÚMERO); en el que NÚMERO indica el número de repeticiones del archivo, en nuestro caso, Clip. LOOP_CONTINUOUSLY para indicar que se tiene que repetir infinitas veces. El ejemplo completo, editado en sonido.java es: import javax.swing.*; import javax.sound.sampled.*; import java.io.*; public class sonido extends JFrame { public sonido() { File sf=new File("italian.wav"); AudioFileFormat aff; AudioInputStream ais; try { aff=AudioSystem.getAudioFileFormat(sf); ais=AudioSystem.getAudioInputStream(sf); AudioFormat af=aff.getFormat(); DataLine.Info info = new DataLine.Info( Clip.class, ais.getFormat(), ((int) ais.getFrameLength() * af.getFrameSize())); Clip ol = (Clip) AudioSystem.getLine(info); ol.open(ais); ol.loop(Clip.LOOP_CONTINUOUSLY); System.out.println("reprodución empezada, apretar CTRL-C para interrumpir"); } catch(UnsupportedAudioFileException ee){} catch(IOException ea){} catch(LineUnavailableException LUE){}; } public static void main(String[] ar) { new sonido(); } } Parece un poco trabajoso, sin embargo, siguiendo estos pasos es posible leer y tocar cada archivo wav. Para hacer otras operaciones, el paquete es bastante complejo y actualmente no hay manuales que expliquen el funcionamiento (excepto la documentación del JDK que, como habéis tenido la oportunidad de ver, no explica cómo utilizar las cosas, sino que las describe y punto). El paquete javax.suond.midi Vamos a ver cómo leer y tocar un archivo midi. Para hacerlo necesitamos el paquete javax.suond.midi que incluye: Interfaces ControllerEventListener MetaEventListener MidiChannel MidiDevice Receiver Sequencer Soundbank Synthesizer Transmitter Clases Instrument MetaMessage MidiDevice.Info MidiEvent MidiFileFormat MidiMessage MidiSystem Patch Sequence Sequencer.SyncMode ShortMessage SoundbankResource SysexMessage Track VoiceStatus Excepciones InvalidMidiDataException MidiUnavailableException La técnica para tocar un archivo midi es parecida a la que se usa para el archivo wav, lo único diferente es que se parte de MidiSystem. En el ejemplo que ponemos a continuación, se toca el archivo sorpresa.mid, que es un archivo midi que me gusta mucho y, creo, le va a gustar a todos mis coetáneos. Hay que editarlo en el archivo midifiles.java import javax.swing.*; import javax.sound.midi.*; import java.io.*; public class midifiles extends JFrame { public midifiles() { try { File f2=new File("sorpresa.mid"); MidiFileFormat mff2=MidiSystem.getMidiFileFormat(f2); Sequence S=MidiSystem.getSequence(f2); Sequencer seq=MidiSystem.getSequencer(); seq.open(); seq.setSequence(S); seq.start(); System.out.println("Sorpresa para todos mis coetáneos"); System.out.println("Apretar CTRL-C para interrumpir"); } catch(MidiUnavailableException ecc){} catch(InvalidMidiDataException ecc2){} catch(IOException ecc3){} ; } public static void main(String[] ar) { new midifiles(); } } Sintetizar los sonidos Como ya he dicho, es posible sintetizar los sonidos. Esto es lo que hace el próximo programa que hay que editar en el archivo sintetizador.java import javax.swing.*; import java.awt.*; import java.awt.event.*; import javax.swing.border.*; import java.yo.*; import java.util.*; import javax.sound.sampled.*; import javax.sound.midi.*; public class sintetizador extends JFrame { ent TIEMPO=50; ent CANAL=0; ent UNS=-1; ent INSTRUMENTO=0; Sintetizador SINTETIZADOR=new Sintetizador(); // GUI JLabel teclado1=new JLabel(new ImageIcon("teclado1.gif")); JLabel teclado2=new JLabel(new ImageIcon("teclado2.gif")); JButton OkB=new JButton("Ok"); JComboBox Instrumentos=new JComboBox(); JComboBox Canales=new JComboBox(); JLabel uns=new JLabel("Última nota tocada"); JTextField msg=new JTextField( "Ninguna nota" ); JSlider SL=new JSlider(); public sintetizador() { setTitle("Sintetizador sonoro de Pietro Castellucci"); setupAspecto(); setupValores(); setupSucesos(); SINTETIZADOR.setInstrumento( Instrumentos.getSelectedIndex(),CANAL); setResizable(false); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); pack(); Toolkit t = Toolkit.getDefaultToolkit(); Dimension d=t.getScreenSize(); Dimension win=getSize(); win=getSize(); setLocation(d.width/2-(win.width/2)-1,d.height/2-(win.height/2)-1); show(); } void setupAspecto() { teclado1.setCursor(new Cursor(Cursor.HAND_CURSOR)); teclado2.setCursor(new Cursor(Cursor.HAND_CURSOR)); OkB.setCursor(new Cursor(Cursor.HAND_CURSOR)); tastiera1.setToolTipText( "Clícame para tocar" ); tastiera2.setToolTipText( "Clícame para tocar" ); OkB.setToolTipText( "Salir del programa" ); JPanel P=new JPanel(new BorderLayout()); JPanel Panel=new JPanel(new GridLayout(2,1)); Panel.add(teclado1); Panel.add(teclado2); P.add(Panel,BorderLayout.CENTER); JPanel SP=new JPanel(new FlowLayout()); Instrumentos.setBorder( BorderFactory.createTitledBorder( "Instrumentos" ) ); Canales.setBorder( BorderFactory.createTitledBorder( "Canales" ) ); SP.add(Instrumentos); // SP.add(Canales); SP.add(uns); msg.setEditable(false); msg.setBackground(P.getBackground()); SP.add(msg); SP.add(OkB); P.add(SP,BorderLayout.SOUTH); SL.setBorder(BorderFactory.createTitledBorder( "Presión " )); SL.setOrientation(JSlider.VERTICAL); SL.setMajorTickSpacing(25); SL.setMaximum(100); SL.setMinimum(0); SL.setMinorTickSpacing(1); SL.setPaintLabels(true); SL.setPaintTicks(true); SL.setPaintTrack(true); SL.setSnapToTicks(true); SL.setValue(TIEMPO); P.add(SL,BorderLayout.EAST); setContentPane(P); } void setupValores() { try { ent e=0; while (true) { String nombre= SINTETIZADOR.Instrumentos[i++].getName(); Instrumentos.addItem(nombre); } } catch (ArrayIndexOutOfBoundsException e) {}; try { int i=0; while (true) { String nombre="Canales"+" "+(i+1); SINTETIZADOR.CANALES[i++].allNotesOff(); Canales.addItem(nombre); } } catch (ArrayIndexOutOfBoundsException e) {}; if (Instrumentos.getItemCount()>0) Instrumentos.setSelectedIndex(INSTRUMENTO); } void setupSucesos() { teclado1.addMouseListener(new Teclado1Listener()); teclado2.addMouseListener(new Teclado2Listener()); Instrumentos.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { SINTETIZADOR.setInstrumento( Instrumentos.getSelectedIndex(),CANALES); } } ); Canales.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { CANALES=Canales.getSelectedIndex(); SINTETIZADOR.cambiaCanal(CANAL); } }); OkB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("¡Gracias por haber tocado conmigo!"); System.exit(0); } } ); } // Sucesos public class Teclado1Listener implements MouseListener { public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) { // System.out.println("Teclado 1 Punto: (x="+e.getX()+",y="+e.getY()+")"); UNS=getNota(e.getX()); // System.out.println("Nota="+nota); TIEMPO=SL.getValue(); SINTETIZADOR.tocar(UNS,TIEMPO,CANAL); msg.setText(""+(UNS+1)); } public void mouseReleased(MouseEvent e) {} } public class Teclado2Listener implements MouseListener { public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) { // System.out.println("Teclado 2 Punto: (x="+e.getX()+",y="+e.getY()+")"); UNS=64+getNota(e.getX()); // System.out.println("Nota="+nota); TIEMPO=SL.getValue(); SINTETIZADOR.suona(UNS,TIEMPO,CANAL); msg.setText(""+(UNS+1)); } public void mouseReleased(MouseEvent e) {} } // Utility fun int getNota (int pos) { int nota; nota=(pos/12); return nota; } public static void main(String[] arg) { new sintetizador(); } //***************************************** // SOUND MANAGER //***************************************** //*********************************************************** // Sintetizador: //*********************************************************** public class Sintetizador { private Synthesizer SYNT; private Sequencer sequencer; private Sequence seq; private Soundbank BANK; public Instrument[] Instrumentos; public MidiChannel[] CANALES; public Sintetizador() { setupSintetizador(); } void setupSintetizador() { try { SYNT=MidiSystem.getSynthesizer(); sequencer=MidiSystem.getSequencer(); seq= new Sequence(Sequence.PPQ, 10); SYNT.open(); BANK = SYNT.getDefaultSoundbank(); if (BANK != null) Instrumentos = SYNT.getDefaultSoundbank().getInstruments(); else Instrumentos = SYNT.getAvailableInstruments(); CANALES=SYNT.getChannels(); } catch(MidiUnavailableException ecc){todonull();} catch(InvalidMidiDataException ecc2){todonull();} ; } void todonull() { SYNT=null; sequencer=null; seq=null; BANK=null; Instrumentos=null; } public void setInstrumento(int str,int can) { SA=str; int prog=Instrumentos[str].getPatch().getProgram(); CANALES[can].programChange(prog); } private int SA=0; public void cambiaCanal(int can) { int prog=Instrumentos[SA].getPatch().getProgram(); CANALES[can].programChange(prog); } public void tocar(ent nota,ent tiempo, ent canal) { CANALES[canal].allNotesOff(); CANALES[canal].noteOn(nota,tiempo); } public void tocar(ent nota,ent tiempo) { tocar(nota,tiempo,0); } public void callar() { CANALES[0].allNotesOff(); } } // End of Sintetizador } Os habéis dado cuenta de que es un programa consistente, sin embargo no tenéis que espantaros porque la mayor parte del código sirve para la gráfica y para escuchar los sucesos. La parte interesante para sintetizar los sonidos la he puesto en la subclase Sintetizador, es decir, en sintetizador.Sintetizador es decir, la siguiente: //*********************************************************** // Sintetizador: //*********************************************************** public class Sintetizador { private Synthesizer SYNT; private Sequencer sequencer; private Sequence seq; private Soundbank BANK; public Instrument[] Instrumentos; public MidiChannel[] CANALES; public Sintetizador() { setupSintetizador(); } void setupSintetizador() { try { SYNT=MidiSystem.getSynthesizer(); sequencer=MidiSystem.getSequencer(); seq= new Sequence(Sequence.PPQ, 10); SYNT.open(); BANK = SYNT.getDefaultSoundbank(); if (BANK != null) Instrumentos = SYNT.getDefaultSoundbank().getInstruments(); else Instrumentos = SYNT.getAvailableInstruments(); CANALES=SYNT.getChannels(); } catch(MidiUnavailableException ecc){todonull();} catch(InvalidMidiDataException ecc2){todonull();} ; } void todonull() { SYNT=null; sequencer=null; seq=null; BANK=null; Instrumentos=null; } public void set nstrumento(ent str,ent can) { SA=str; ent prog=Instrumentos[str].getPatch().getProgram(); CANALES[can].programChange(prog); } private ent SA=0; public void cambiaCanal(ent can) { ent prog=Instrumentos[SA].getPatch().getProgram(); CANALES[can].programChange(prog); } public void tocar(ent nota,ent tiempo, ent canal) { CANALES[canal].allNotesOff(); CANALES[canal].noteOn(nota,tiempo); } public void tocar(ent nota,ent tiempo) { tocar(nota,tiempo,0); } public void callar() { CANALES[0].allNotesOff(); } } // End of Sintetizador El aspecto del programa es: Conclusiones y bibliografía Hemos hecho un resumen de algunas partes del famoso lenguaje Java, muy utilizado para escribir los programas que trabajan en internet y no sólo para eso; empezando por las bases hasta llegar a las interfaces gráficas y, finalmente, al sonido. Éstos son todos aspectos más avanzados de la programación. Quiero disculparme con mis lectores menos expertos por si han tenido problemas para entender algunas partes de la guía y, al mismo tiempo, quiero hacerlo con los más expertos si han encontrado algo demasiado aburrido o simple. Los que han asistido a todo el curso tendrían que ser capaces de escribir unos apliques simples y unas aplicaciones por su cuenta. No creo que hayáis conseguido todavía gestionar grandes aplicaciones complejas, aunque, con un poco de práctica y con los simples conceptos del curso, os váis a convertir en excelentes programadores. Yo estaré siempre disponible a posibles preguntas sobre el curso o a más explicaciones, pero os pido que no me escribáis para los Javascript o para la configuración de los apliques bajados de la red. Para los que quieran profundizar los temas tratados o ver nuevos, os dejo la bibliografía completa en la que me he basado para preparar este curso. Un saludo a todos. fut. Ingeniero Reinaldo Huarayo Carata ![]() |
|
|||||||
![]() |