Manual de Programación Web VI VI CICLO COMPUTACIÓN E INFORMÁTICA Instituto de Educación Superior “San Ignacio de Monterrico” Indice 1. Introduccion a java 2 2. Sintaxis del lenguaje java 14 3. Programacion orientada a objetos con java 50 4. Excepciones 77 5. Colecciones. 99 6. Conectandose a base de datos. 129 7. Diseño gui con awt y swing. 162 8. Sockets 179 Formando Emprendedores De Calidad Para Un Mundo Empresarial 2 Instituto de Educación Superior “San Ignacio de Monterrico” PROGRAMACIÓN WEB III INTRODUCCION A JAVA Historia Java surgió en 1991 cuando un grupo de ingenieros de Sun Microsystems trataron de diseñar un nuevo lenguaje de programación destinado a electrodomésticos. La reducida potencia de cálculo y memoria de los electrodomésticos llevó a desarrollar un lenguaje sencillo capaz de generar código de tamaño muy reducido. Debido a la existencia de distintos tipos de CPUs y a los continuos cambios, era importante conseguir una herramienta independiente del tipo de CPU utilizada. Desarrollaron un código “neutro” que no dependía del tipo de electrodoméstico, el cual se ejecutaba sobre una “máquina hipotética o virtual” denominada Java Virtual Machine (JVM). Era la JVM quien interpretaba el código neutro convirtiéndolo a código particular de la CPU utilizada. Esto permitía lo que luego se ha convertido en el principal lema del lenguaje: “Write Once, Run Everywhere”. A pesar de los esfuerzos realizados por sus creadores, ninguna empresa de electrodomésticos se interesó por el nuevo lenguaje. Como lenguaje de programación para computadores, Java se introdujo a finales de 1995. La clave fue la incorporación de un intérprete Java en la versión 2.0 del programa Netscape Navigator, produciendo una verdadera revolución en Internet. Java 1.1 apareció a principios de 1997, mejorando sustancialmente la primera versión del lenguaje. Java 1.2, más tarde rebautizado como Java 2, nació a finales de 1998. Al programar en Java no se parte de cero. Cualquier aplicación que se desarrolle “se apoya” en un gran número de clases preexistentes. Algunas de ellas hechas por el propio usuario, otras pueden ser comerciales, pero siempre hay un número muy importante de clases que forman parte del propio lenguaje (el API o Application Programming Interface de Java). Java incorpora en el propio lenguaje muchos aspectos que en cualquier otro lenguaje son extensiones propiedad de Formando Emprendedores De Calidad Para Un Mundo Empresarial 3 Instituto de Educación Superior “San Ignacio de Monterrico” empresas de software o fabricantes de ordenadores (threads, ejecución remota, componentes, seguridad, acceso a bases de datos, etc.). Por eso Java es un lenguaje de programación ideal porque incorpora todos estos conceptos de un modo estándar, mucho más sencillo y claro que con las citadas extensiones de otros lenguajes. Esto es consecuencia de haber sido diseñado más recientemente y por un único equipo. El principal objetivo del lenguaje Java es llegar a ser el “nexo universal” que conecte a los usuarios con la información, esté ésta situada en el ordenador local, en un servidor de Web, en una base de datos o en cualquier otro lugar. En conclusión java es un lenguaje originalmente desarrollado por un grupo de ingenieros de Sun, utilizado por Netscape posteriormente como base para Javascript. Si bien su uso se destaca en el Web, sirve para crear todo tipo de aplicaciones (locales, intranet o Internet). ¿Qué es java? Java es un lenguaje de programación muy completo. La compañía Sun describe el lenguaje Java como “simple, orientado a objetos, distribuido, interpretado, robusto, seguro, de arquitectura neutra, portable, de altas prestaciones, multitarea y dinámico”. Los programas desarrollados en Java presentan diversas ventajas frente a los desarrollados en otros lenguajes como C/C++. La ejecución de programas en Java tiene muchas posibilidades: ejecución como aplicación independiente (Stand-alone Application), ejecución como applet, ejecución como servlet, etc. Un applet es una aplicación especial que se ejecuta dentro de un navegador o browser (por ejemplo Netscape Navigator o Internet Explorer) al cargar una página HTML desde un servidor Web. El applet se descarga desde el servidor y no requiere instalación en el ordenador donde se encuentra el browser. Un servlet es una aplicación sin interface gráfica que se ejecuta en un servidor de Internet. La ejecución como aplicación independiente es análoga a los programas desarrollados con otros lenguajes. Formando Emprendedores De Calidad Para Un Mundo Empresarial 4 Instituto de Educación Superior “San Ignacio de Monterrico” Además de incorporar la ejecución como Applet, Java permite fácilmente el desarrollo tanto de arquitecturas cliente-servidor como de aplicaciones distribuidas, consistentes en crear aplicaciones capaces de conectarse a otros ordenadores y ejecutar tareas en varios ordenadores simultáneamente, repartiendo por lo tanto el trabajo. Aunque también otros lenguajes de programación permiten crear aplicaciones de este tipo, Java incorpora en su propio API estas funcionalidades. Características: o Simple. Ofrece la funcionalidad de un lenguaje potente pero sin las características menos usadas y más confusas de éstos. El ejemplo más claro puede ser el recolector de basura, que evita que andemos preocupándonos de liberar memoria. Otro ejemplo podría ser la supresión de los punteros. o Lenguaje de objetos. ¿Por qué "de" objetos y no "orientado a" objetos? Por que, al contrario de otros lenguajes como C++, no es un lenguaje modificado para poder trabajar con objetos sino que es un lenguaje creado para trabajar con objetos desde cero. De hecho, TODO lo que hay en Java son objetos. Java soporta las tres características básicas de la orientación a objetos: encapsulación, herencia y polimorfismo. o Distribuido. Proporciona las librerías y herramientas necesarias para que las aplicaciones puedan ser distribuidas. Se ha desarrollado con extensas capacidades de interconexión a red y soporta varios protocolos de red. o Robusto. Realiza variadas comprobaciones tanto en tiempo de compilación como de ejecución. Entre ellas podemos mencionar la comprobación de tipos y la comprobación de límites de arrays. o Portable. Esto no se refiere a la independencia de la plataforma, si no a la portabilidad en cuanto a desarrollo. Por ejemplo, los enteros son siempre enteros de 32 bits en complemento a 2, con independencia de la plataforma. Formando Emprendedores De Calidad Para Un Mundo Empresarial 5 Instituto de Educación Superior “San Ignacio de Monterrico” o Multiplataforma. Como mencionamos antes, no es necesario recompilar las aplicaciones Java para los distintos sistemas en que van a ser explotadas. o Multihilo. Permite múltiples hilos de ejecución, es decir, muchas actividades simultáneas dentro del mismo programa. Las ventajas de esto son un mejor rendimiento interactivo (el usuario no percibe tanto la ocupación de la máquina) y un mejor comportamiento en tiempo real (aunque sea algo muy limitado por el sistema operativo). o Dinámico. Cuando una aplicación se lanza, no se cargan todas las librerías que requiere, sino que la carga es bajo demanda. Las librerías nuevas o actualizadas no paralizarán las aplicaciones en funcionamiento. Plataformas de desarrollo El software de Java contempla 3 plataformas para el desarrollo de aplicaciones: o Java SE (Java Standard Edition) Es el conjunto de herramientas software que permite el desarrollo y la ejecución de programas Java destinados al lado cliente. Se le suele llamar SDK (Software Development Kit) o Kit de Desarrollo de Programas. Es gratuito y de libre distribución. Se puede descargar desde la página oficial de Sun Microsystems relacionada con Java http://java.sun.com/. Dentro de la plataforma Java SE, se incluye el compilador y la JVM (Java Virtual Machine) o Máquina virtual de Java. También se la conoce como Intérprete de Java. Cada plataforma tiene su propia versión. En la página de Java dentro de Sun puede descargarse el Java SE para Windows, Linux, Solaris, etc. Si se trabaja con sistemas operativos menos corrientes como MacOS de Apple, AIX de IBM, HP-UX de Hewlett-Packard, etc. o Java EE (Java Enterprise Edition) Plataforma del lenguaje Java destinada al desarrollo de aplicaciones empresariales estructuradas típicamente en tres capas: capa de presentación de datos, capa de lógica de negocio y capa de datos Formando Emprendedores De Calidad Para Un Mundo Empresarial 6 Instituto de Educación Superior “San Ignacio de Monterrico” persistentes (bases de datos). Se compone de un conjunto de estándares y bibliotecas Java que permiten la creación de las aplicaciones empresariales anteriormente citadas. Esta plataforma se utiliza sobre todo para programación en servidores. Sin conocer lo fundamental de Java SE, resulta inviable introducirse en Java EE. o Java ME (Java Micro Edition) Plataforma del lenguaje Java destinada al desarrollo de aplicaciones para pequeños dispositivos móviles de memoria limitada, poca capacidad de procesamiento y con interfaces gráficas limitadas. Típicamente teléfonos móviles, PDAs (Personal Assistent Digital), Pockets PCs, televisiones, relojes, sistemas de ayuda para automóviles, tarjetas, etc. Como en Java EE, la base para programar mediante Java ME, es Java SE. El Entorno De Desarrollo De Java Existen distintos programas comerciales que permiten desarrollar código Java. La compañía Sun, creadora de Java, distribuye gratuitamente el Kit de Desarrollo de Java (JDK). Se trata de un conjunto de programas y librerías que permiten desarrollar, compilar y ejecutar programas en Java. Incorpora además la posibilidad de ejecutar parcialmente el programa, deteniendo la ejecución en el punto deseado y estudiando en cada momento el valor de cada una de las variables (con el denominado Debugger). Cualquier programador con un mínimo de experiencia sabe que una parte muy importante (muchas veces la mayor parte) del tiempo destinado a la elaboración de un programa se destina a la detección y corrección de errores. Existe también una versión reducida del JDK, denominada JRE (Java Runtime Environment) destinada únicamente a ejecutar código Java (no permite compilar). Los IDEs (Integrated Development Environment), tal y como su nombre indica, son entornos de desarrollo integrados. En un mismo programa es posible escribir el código Java, compilarlo y ejecutarlo sin tener que cambiar de aplicación. Algunos incluyen una herramienta para realizar Debug gráficamente, frente a la versión que incorpora el JDK basada en la utilización de una consola Formando Emprendedores De Calidad Para Un Mundo Empresarial 7 Instituto de Educación Superior “San Ignacio de Monterrico” (denominada habitualmente ventana de comandos de MS-DOS, en Windows NT/95/98) bastante difícil y pesada de utilizar. Estos entornos integrados permiten desarrollar las aplicaciones de forma mucho más rápida, incorporando en muchos casos librerías con componentes ya desarrollados, los cuales se incorporan al proyecto o programa. Como inconvenientes se pueden señalar algunos fallos de compatibilidad entre plataformas, y ficheros resultantes de mayor tamaño que los basados en clases estándar. Algunos IDEs que podemos citar son: Netbeans, Jdeveloper, Eclipse y JCreator entre otros. Bytecodes Un programa C o C++ es totalmente ejecutable y eso hace que no sea independiente de la plataforma y que su tamaño normalmente se dispare ya que dentro del código final hay que incluir las librerías de la plataforma. Proceso de compilación de un programa C++ Los programas Java no son ejecutables, no se compilan como los programas en C o C++. En su lugar son interpretados por una aplicación conocida como la máquina virtual de Java (JVM). Gracias a ello no tienen porque incluir todo el código y librerías propias de cada sistema. Formando Emprendedores De Calidad Para Un Mundo Empresarial 8 Instituto de Educación Superior “San Ignacio de Monterrico” Previamente el código fuente en Java se tiene que precompilar generando un código (que no es directamente ejecutable) previo conocido como bytecode o Jcode. Ese código (generado normalmente en archivos con extensión class) es el que es ejecutado por la máquina virtual de Java que interpreta las instrucciones de los bytecodes, ejecutando el código de la aplicación. El bytecode se puede ejecutar en cualquier plataforma, lo único que se requiere es que esa plataforma posea un intérprete adecuado (la máquina virtual de esa plataforma). La Java Virtual Machine La existencia de distintos tipos de procesadores y ordenadores llevó a los ingenieros de Sun a la conclusión de que era muy importante conseguir un software que no dependiera del tipo de procesador utilizado. Se planteó la necesidad de conseguir un código capaz de ejecutarse en cualquier tipo de máquina. Una vez compilado no debería ser necesaria ninguna modificación por el hecho de cambiar de procesador o de ejecutarlo en otra máquina. La clave consistió en desarrollar un código “neutro” el cual estuviera preparado para ser ejecutado sobre una “máquina hipotética o virtual”, denominada Java Virtual Machina (JVM). Es esta JVM quien interpreta este código neutro convirtiéndolo a código particular de la CPU utilizada. Se evita tener que realizar un programa diferente para cada CPU o plataforma. La JVM es el intérprete de Java. Ejecuta los “bytecodes” (ficheros compilados con extensión *.class) creados por el compilador de Java (javac.exe). Tiene numerosas opciones entre las que destaca la posibilidad de utilizar el denominado JIT (Just-In-Time Compiler), que puede mejorar entre 10 y 20 veces la velocidad de ejecución de un programa. La JVM, además es un programa muy pequeño y que se distribuye gratuitamente para prácticamente todos los sistemas operativos. A este método de ejecución de programas en tiempo real se le llama Just in Time (JIT). Formando Emprendedores De Calidad Para Un Mundo Empresarial 9 Instituto de Educación Superior “San Ignacio de Monterrico” Proceso de compilación de un programa Java Compilación y ejecución de código java Hay que entender que Java es estricto en cuanto a la interpretación de la programación orientada a objetos. Así, se sobrentiende que un archivo java crea una (y sólo) clase. Por eso al compilar se dice que lo que se está compilando es una clase. javac La compilación del código java se realiza mediante el programa javac incluido en el software de desarrollo de java. La forma de compilar es (desde la línea de comandos): javac archivo.java El resultado de esto es un archivo con el mismo nombre que el archivo java pero con la extensión class. Esto ya es el archivo con el código en forma de bytecodes. Es decir con el código precompilado. java Si la clase es ejecutable (sólo lo son si contienen el método main), el código se puede interpretar usando el programa java del kit de desarrollo. Sintaxis: java archivo.class El compilador Java es una de las herramientas de desarrollo incluidas en el JDK. Realiza un análisis de sintaxis del código escrito en los ficheros fuente de Java (con extensión *.java). Si no encuentra errores en el código Formando Emprendedores De Calidad Para Un Mundo Empresarial 10 Instituto de Educación Superior “San Ignacio de Monterrico” genera los ficheros compilados (con extensión *.class). En otro caso muestra la línea o líneas erróneas. En el JDK de Sun dicho compilador se llama javac.exe. Tiene numerosas opciones, algunas de las cuales varían de una versión a otra. Se aconseja consultar la documentación de la versión del JDK utilizada para obtener una información detallada de las distintas posibilidades. La compilación del código java se realiza mediante el programa javac incluido en el software de desarrollo de java. La forma de compilar es (desde la línea de comandos): javac archivo.java El resultado de esto es un archivo con el mismo nombre que el archivo java pero con la extensión class. Esto ya es el archivo con el código en forma de bytecodes. Es decir con el código precompilado. Si la clase es ejecutable (sólo lo son si contienen el método main), el código se puede interpretar usando el programa java del kit de desarrollo. Sintaxis: java archivo.class Estos comandos hay que escribirlos desde la línea de comandos de en la carpeta en la que se encuentre el programa. Pero antes hay que asegurarse de que los programas del kit de desarrollo son accesibles desde cualquier carpeta del sistema. Para ello hay que comprobar que la carpeta con los ejecutables del kit de desarrollo está incluida en la variable de entorno path. Environment JAVA .java javac Java Byte-code java Java VM .class Compilación de un programa Java Formando Emprendedores De Calidad Para Un Mundo Empresarial 11 Instituto de Educación Superior “San Ignacio de Monterrico” Estos comandos hay que escribirlos desde la línea de comandos en la carpeta en la que se encuentre el programa. Pero antes hay que asegurarse de que los programas del kit de desarrollo son accesibles desde cualquier carpeta del sistema. Para ello hay que comprobar que la carpeta con los ejecutables del kit de desarrollo está incluida en la variable de entorno path. Esto lo podemos comprobar escribiendo path en la línea de comandos. Si la carpeta del kit de desarrollo no está incluida, habrá que hacerlo. Para ello en Windows 2000, 2003 o XP: 1. Pulsar el botón derecho sobre Mi PC y elegir Propiedades 2. Ir al apartado Opciones avanzadas 3. Hacer clic sobre el botón Variables de entorno 4. Añadir a la lista de la variable PATH la ruta a la carpeta con los programas del JDK. 5. Añadir la variable CLASSPATH si es que no existe y el valor a asignarle seria un punto. Ejemplo de contenido de la variable PATH: PATH= C:\Archivos de programa\Java\jdk1.6.0_06\bin; En negrita está señalada la ruta a la carpeta de ejecutables (carpeta bin) del kit de desarrollo. Está carpeta varía según la instalación del JDK Ejemplo de contenido de la variable CLASSPATH: CLASSPATH = .; Javadoc Javadoc es una herramienta muy interesante del kit de desarrollo de Java para generar automáticamente documentación Java. Genera documentación para paquetes completos o para archivos java. Su sintaxis básica es: javadoc archivo.java o paquete Formando Emprendedores De Calidad Para Un Mundo Empresarial 12 Instituto de Educación Superior “San Ignacio de Monterrico” El funcionamiento es el siguiente. Los comentarios que comienzan con los códigos /** se llaman comentarios de documento y serán utilizados por los programas de generación de documentación javadoc. Tipos de archivos • java: Es la extensión de los archivos fuente del lenguaje java, ejem. bienvenido.java, Miapplet.java. • class: El resultado de compilar un archivo fuente java es un archivo en bytecode con extensión class, ejem. bienvenido.class, Miapplet.class. El comando usado para compilar los arhivos con extensión java es javac y para su ejecución el comando java que vienen incluidos en el JDK (Java Development Kit) de Sun Microsystems. • jar/zip: Las clases desarrolladas en java suelen por lo general guardarse en estos tipos de archivos comprimidos. Ejercicio //archivo con el nombre Bienvenido.java class Bienvenido { public static void main(String [] args) { System.out.println("Bienvenido a Java!"); } } Grabar el archivo en la carpeta Demos01 con el nombre Bienvenido.java luego compilar y ejecutar el programa anterior, con los programas javac y java respectivamente. javac Bienvenido.java java Bienvenido Línea 1: es un simple comentario de tipo línea en el que hemos colocado el nombre del fichero. El compilador ignorará todo lo que va desde los caracteres “//” hasta el final de la línea. Los comentarios son de mucha utilidad, tanto para otras personas que tengan que revisar el código, como para nosotros mismos en futuras revisiones. Formando Emprendedores De Calidad Para Un Mundo Empresarial 13 Instituto de Educación Superior “San Ignacio de Monterrico” Línea 2: declara el nombre de la clase. Usamos la palabra reservada class seguida del nombre que queremos darle a nuestra clase, en este caso es “Bienvenido”. Ya sabemos que Java es un lenguaje orientado a objetos, por tanto, nuestro programa ha de ser definido como una clase. Por convención, las clases Java se definen con la primera letra en mayúsculas. Línea 3: declara el método principal (main) utilizando el modificador de acceso public (acceso publico), el modificador static y el tipo que retorna es void (nada) y como parámetro recibe un arreglo de Strings. Linea 4: se invoca al método println(param) que Escribe el valor de param y luego se produce un cambio de línea que equivale a pulsar retornar, al final de toda sentencia se debe colocar el punto y coma (;) que indica el fin de la sentencia. Linea 5 y 6: Se termina el cuerpo del método principal y de la clase. Formando Emprendedores De Calidad Para Un Mundo Empresarial 14 Instituto de Educación Superior “San Ignacio de Monterrico” SINTAXIS DEL LENGUAJE JAVA Consideraciones Todo el código fuente Java se escriben en documentos de texto con extensión .java. Al ser un lenguaje para Internet, la codificación de texto debía permitir a todos los programadores de cualquier idioma escribir ese código. Eso significa que Java es compatible con la codificación Unicode. En la práctica significa que los programadores que usen lenguajes distintos del inglés no tendrán problemas para escribir símbolos de su idioma. Y esto se puede extender para nombres de clase, variables, etc. La codificación Unicode2 usa 16 bits (2 bytes por carácter) e incluye la mayoría de los códigos del mundo. • Los archivos con código fuente en Java deben guardarse con la extensión .java. Como se ha comentado cualquier editor de texto se puede usar para codificar un archivo. • En java el código es sensitivo (case sensitive) es decir existe diferencias entre mayúsculas y minúsculas. • Cuando se programa en Java, se coloca todo el código en métodos. • Cada línea de código debe terminar con punto y coma (;) que indica el fin de la sentencia. • Los comentarios; si son de una línea deben comenzar con: “//” y si ocupan más de una línea deben comenzar con “/*” y terminar con “*/” • A veces se marcan bloques de código, los cuales comienza con { y terminan con } Formando Emprendedores De Calidad Para Un Mundo Empresarial 15 Instituto de Educación Superior “San Ignacio de Monterrico” Ejercicio public class app004 { public static void main(String[] args) { //Comentario de una línea System.out.println(“¡Programacion Web III!”); /* Comentarios de Varias Lineas Sidem Carlos Enrique */ } } Este código escribe “¡Programacion Web III!” en la pantalla. El archivo debería llamarse app004.java ya que esa es la clase pública. El resto define el método main que es el que se ejecutará al lanzarse la aplicación. Ese método utiliza la instrucción que escribe en pantalla; y también se utilizan los comentarios de una y varias líneas. Comentarios En java al igual que en otros lenguajes existen los comentarios y estos pueden ser considerados como un caso especial dentro de los elementos de la sintaxis del lenguaje, ya que aunque estos sean reconocidos por el compilador, éste los ignora. En Java existen tres tipos de comentarios. // Comentario de una línea. /* Comentario de Varias líneas. */ /** Comentario de Documentación Formando Emprendedores De Calidad Para Un Mundo Empresarial 16 Instituto de Educación Superior “San Ignacio de Monterrico” * Esta aplicación realiza lo siguiente: … * @autor Carlos Durand Flores * @version 1.0 */ Los dos primeros tipos de comentarios son los que todo programador conoce y se utilizan del mismo modo. Los comentarios de documentación, indican que ese comentario ha de ser colocado en la documentación que se genera automáticamente cuando se utiliza la herramienta de Java, javadoc. Dichos comentarios sirven como descripción del elemento declarado permitiendo generar una documentación de nuestras clases escrita al mismo tiempo que se genera el código. Los comentarios javadoc comienzan con el símbolo /** y terminan con */ Cada línea javadoc se inicia con un símbolo de asterisco. Dentro se puede incluir cualquier texto. Incluso se pueden utilizar códigos HTML para que al generar la documentación se tenga en cuenta el código HTML indicado. En este tipo de comentario para documentación, se permite la introducción de algunos tokens, palabras clave o etiquetas especiales que harán que la información que les sigue aparezca de forma diferente al resto en la documentación, estos comienzan con el símbolo @ y pueden ser: o @author. Tras esa palabra se indica el autor del documento. o @version. Tras lo cual sigue el número de versión de la aplicación. o @see. Tras esta palabra se indica una referencia a otro código Java relacionado con éste. o @since. Indica desde cuándo esta disponible este código. o @deprecated. Palabra a la que no sigue ningún otro texto en la línea y que indica que esta clase o método esta obsoleta u obsoleto. o @throws. Indica las excepciones que pueden lanzarse en ese código. Formando Emprendedores De Calidad Para Un Mundo Empresarial 17 Instituto de Educación Superior “San Ignacio de Monterrico” o @param. Palabra a la que le sigue texto qué describe a los parámetros que requiere el código para su utilización (el código en este caso es un método de clase). Cada parámetro se coloca en una etiqueta @param distinta, por lo que puede haber varios @param. Para el mismo método. o @return. Tras esta palabra se describe los valores que devuelve el código (el código en este caso es un método de clase) El código javadoc hay que colocarle en tres sitios distintos dentro del código java de la aplicación: 1. Al principio del código de la clase (antes de cualquier código Java). En esta zona se colocan comentarios generales sobre la clase o interfaz que se crea mediante el código Java. Dentro de estos comentarios se pueden utilizar las etiquetas: @author, @version, @see, @since y @deprecated 2. Delante de cada método. Los métodos describen las cosas que puede realizar una clase. Delante de cada método los comentarios javadoc se usan para describir al método en concreto. Además de los comentarios, en esta zona se pueden incluir las etiquetas: @see, @param, @exception, @return, @since y @deprecated 3. Delante de cada atributo. Se describe para qué sirve cada atributo en cada clase. Puede poseer las etiquetas: @since y @deprecated Ejercicio /** Esto es un comentario para probar el javadoc * este texto aparecerá en el archivo HTML generado. * <strong>Realizado en agosto 2008</strong> * * @author Carlos Durand * @version 1.0 */ Formando Emprendedores De Calidad Para Un Mundo Empresarial 18 Instituto de Educación Superior “San Ignacio de Monterrico” public class app005 { //Este comentario no aparecerá en el javadoc /** Este método contiene el código ejecutable de la clase * * @param args Lista de argumentos de la línea de comandos * @return void */ public static void main(String args[]){ System.out.println("¡Mi aplicación app005! "); } } Tras ejecutar la aplicación javadoc, aparece como resultado una página web donde aparecerá la documentación de nuestra clase implementada.. Bloques y ámbitos Bloques Al igual que C, Java utiliza las llaves ({} la primera de inicio y la otra de fin de bloque) para determinar los bloques dentro de un programa. Todo lo que se encuentra entre estas dos llaves se considera un bloque. Los bloques son parte de la sintaxis del lenguaje. Los bloques pueden y suelen anidarse; utilizándose la sangría para clarificar el contenido de un bloque. De este modo, el bloque más externo se sitúa al margen izquierdo del fichero de código fuente, y cada vez que se anida un bloque se indenta (sangra) el texto que lo contienen un número determinado de columnas, normalmente tres o cuatro. El sangrado no tiene ninguna utilidad para el compilador, pero sin ella la lectura del código por parte del programador es casi imposible. Formando Emprendedores De Calidad Para Un Mundo Empresarial 19 Instituto de Educación Superior “San Ignacio de Monterrico” Ámbitos Los bloques además, definen los ámbitos de las variables. El ámbito se refiere a la longevidad de las variables. Una variable existe sólo dentro del bloque donde ha sido declarada, eliminándola el compilador una vez que se sale de dicho bloque. Esto es cierto para todos los tipos de datos de Java. Si el dato es un objeto, y su clase tiene un destructor asociado (distinto al destructor por defecto, que es interno y Java lo maneja por sí solo) este es invocado al salir la variable de ámbito. Cuando una variable sale de ámbito, es eliminada y la memoria que ésta ocupaba es liberada por el recolector de basura (garbage collector). Identificadores Los identificadores sirven para nombrar variables, propiedades, métodos, funciones, clases, interfaces y objetos; o cualquier entidad que el programador necesite identificar para poder usar. Estos identificadores sirven de mucho al programador, si éste les da sentido. En Java, un identificador comienza con una letra, un subrayado (_) o un símbolo de dólar ($). Los siguientes caracteres pueden ser letras o dígitos. No existe una longitud máxima y se distinguen las mayúsculas de las minúsculas, de este modo el compilador puede identificarlos unívocamente. Por ejemplo, si vamos a implementar una clase para manejo del fichero de clientes, sería recomendable que la misma se llamara algo así como ClientManager, o ManejadordeCliente, no tendría sentido, aunque es totalmente válido llamarla Aj23. Existe una serie de palabras reservadas las cuales tienen un significado especial para Java y por lo tanto no se pueden utilizar como nombres identificadores. Dichas palabras son: Formando Emprendedores De Calidad Para Un Mundo Empresarial 20 Instituto de Educación Superior “San Ignacio de Monterrico” abstract char double for int package static throws byvalue boolean class else goto* interface private super transient threadsafe break const* extends if long protected switch try false byte continue final implements native public synchronized void true case default finally import new return this volatile catch do float instanceof null short throw while (*) Son palabras reservadas, pero no se utilizan en la actual implementación del lenguaje Java. Además, el lenguaje se reserva unas cuantas palabras más, pero que hasta ahora no tienen un cometido específico. Son: cast Var future outer generic inner operator rest Serían identificadores válidos: nombre_usuario Nombre_Usuario _variable_del_sistema $transaccion y su uso sería, por ejemplo: int contador_principal; char _lista_de_ficheros; float $cantidad_en_Ptas; Tipos de datos primitivos Estos tipos de datos simples o también llamados tipos primitivos, permiten declarar un variable de tipo primitiva que reserva el espacio de memoria necesario. Formando Emprendedores De Calidad Para Un Mundo Empresarial 21 Instituto de Educación Superior “San Ignacio de Monterrico” <tipo> <idetificador1> [= <valor1>] [, <identificadorN> [= <valorN>]]; O bien, con asignación múltiple: <tipo> <idetificador1>,... ,<identificadorN> = <valor>; • Byte Entero muy pequeño. • Short Entero Corto. • Int Entero normal. • Long Entero largo. • Char Carácter. • Float Número real de precisión simple. • Double Número real de doble precisión. • Boolean Valor lógico. • Void Tipo Vació. De no asignarse un valor de inicio, el compilador por defecto asignará uno. Tipos Precisión Valor Por Defecto Byte 8 bits 0 Short 16 bits 0 Int 32 bits 0 Long 64 bits 0 Char 16 bits \u0000 Float 32 bits +0.0f Double 64 bits +0.0d Boolean - false Enteros Como en otros lenguajes, Java maneja el tipo de datos entero, y con distintos tamaños y capacidades. Es uno de los tipos que la máquina virtual de Java maneja de forma directa. Cada implementación de la Formando Emprendedores De Calidad Para Un Mundo Empresarial 22 Instituto de Educación Superior “San Ignacio de Monterrico” máquina virtual debe cumplir con una serie de reglas de manipulación de enteros (y otros tipos primitivos) que aseguran que nuestros programas Java se comporten de la misma forma en distintas plataformas. Esto asegura la portabilidad de Java. En otros lenguajes, como por ejemplo C o C++, el tamaño y capacidades de los enteros, varía de sistema operativo a sistema operativo, y aún dentro de la misma plataforma, de fabricante de compilador a otro. Eso ha provocado que la portabilidad de muchos programas C/C++ se haya visto dificultada por este estado de cosas. Si bien se ha avanzado mucho en solucionar el problema, los diseñadores del lenguaje Java se plantearon desde el comienzo solucionarlo. Para eso adoptaron un formato estándar de los tipos primitivos para todo Java, independiente de la máquina donde se ejecute. Hay cuatro tipos enteros en Java: byte Valores que pueden tener entre -128 a +127, y ocupan como era de esperarse un byte (8 bits) en memoria short Valores entre -32768 a 32767, que ocupan 2 bytes (16 bits) en memoria int El tipo entero normal, con un rango de valores de entre -2147483648 a 2147483647, que ocupa 4 bytes (32 bits) en memoria long El tamaño más grande de entero que maneje Java, con valores entre -9223372036854775808 hasta 9223372036854775807. Ocupan 8 bytes (64 bits). Algunos ejemplos de declaraciones: byte cantidadAhorrada; short cantidadClientes; int cantidadDeudas; Formando Emprendedores De Calidad Para Un Mundo Empresarial 23 Instituto de Educación Superior “San Ignacio de Monterrico” long deudaExterna; Debemos tratar ahora el tema de escribir, en el código, valores correspondientes a los tipos enteros. Los literales enteros (recordemos que esto incluye a los byte y a los short, así como los int y long), se escriben en dígitos decimales, octales o hexadecimales. Espero que Ud. esté familiarizado con los primeros (en caso contrario, deberá repasar su cultura general). Como expresiones decimales válidas tenemos: 10 123 Las expresiones octales (en base 8), comienzan con un dígito 0 (cero), y las hexadecimales (en base 16, con dígitos 0-9 y a-f o A-F) con un 0x o 0X (Cero y equis). Las siguientes expresiones entonces son equivalentes: 12 014 0x0c 0x0C Cuando un literal entero termina con l o L (letra ele), es un entero largo (del tipo long). En caso contrario, se toma de tipo int (entero simple). Una constante int se puede asignar a short o byte directamente, si es posible acomodarla en ese tipo. Podemos declarar una variable y darle un valor inicial, en la misma sentencia: long numeroGrande = 99999999L; El valor a asignar se coloca luego de un signo de igual. Es común poner un valor inicial a una variable local a un método. En caso de no hacerlo, si usamos la variable sin valor inicial, el compilador nos advierte del problema. El siguiente código, inserto en un método, no compilaría: int k; k=k+1; Formando Emprendedores De Calidad Para Un Mundo Empresarial 24 Instituto de Educación Superior “San Ignacio de Monterrico” Pueden declararse varias variables en la misma sentencia, y hasta asignárseles valores: int x, y; int xInicial = 0, yInicial = 0; Como el compilador no toma como especiales los cambios de línea, podemos escribir este tipo de declaraciones de la forma: int xInicial = 0, yInicial = 0, zInicial = 0; Los valores del tipo byte o short, se inicializan de manera similar: byte unOcteto = 7; short unValor = 2300; Notemos que no hizo falta declarar al 7 como byte, o al 2300 como entero corto. El compilador se encarga de tomarlos como valores byte y short, respectivamente. Java no solamente especifica la forma de tratar a los enteros, sino también cómo se graban o serializan en algún medio externo. Esto hace posible que lo grabemos en un archivo como entero, luego lo podamos recuperar de la misma forma, aún desde otra plataforma. Esto no es así de fácil en otros lenguajes: muchos compiladores C graban enteros de una forma en la plataforma Motorola, y de otra forma en plataforma Intel, aún cuando sean del mismo tamaño. Los de formato Intel, se graban con el byte menos significativo primero, y en el formato Motorola, se graban en orden inverso al de Intel. Números en coma flotante En Java, como en otros lenguajes, se manejan también números reales, en los formatos de punto flotante. Estos números no son enteros, tienen distinta precisión, y su rango de valores es mayor. Se guardan como los enteros, en formato binario, así que su representación decimal es aproximada (en cuanto a la parte no entera). Formando Emprendedores De Calidad Para Un Mundo Empresarial 25 Instituto de Educación Superior “San Ignacio de Monterrico” float Pueden albergar valores desde -3.4E38 hasta +3.4E38, ocupando 4 bytes en memoria. Tienen una precisión aproximada de 7 dígitos. double Contienen valores desde -1.7E308 hasta +1.7E308, consumiendo 8 bytes en la memoria. La precisión aproximada es de 17 dígitos. El menor valor almacenable está cercano a los +- 4.9E-324. Las operaciones sobre los valores de punto flotante, siguen las reglas de estándard IEEE 754. Notamos que estos valores pueden manejar un exponente en base 10. Valores válidos son: 1.0 3.1415 1.0f 99.10F Los dos primeros se asumen de tipo double, mientras los segundos, al terminar en f o F, el compilador los procesa como del tipo float. El separador decimal es el punto. En caso de necesitar expresar un exponente en base 10, se usa la letra E seguida del exponente: 314e-2 0.314E1 Para declarar variables de tipo flotante, se sigue la misma convención que en las variables de tipo entero: double valorReal; double deuda = 1.496E8; float sueldo = 1.2E-2f; Como antes, podemos declarar más de una variable en la misma sentencia: float valor1 = 190.2f, Formando Emprendedores De Calidad Para Un Mundo Empresarial 26 Instituto de Educación Superior “San Ignacio de Monterrico” valor2 = 192.3f; Booleanos Los valores booleanos (o lógicos) sirven para indicar si algo es verdadero (true) o falso (false). En C se puede utilizar cualquier valor lógico como si fuera un número; así verdadero es el valor -1 y falso el 0. Eso no es posible en Java. Si a un valor booleano no se le da un valor inicial, se toma como valor inicial el valor false. Por otro lado, a diferencia del lenguaje C, no se pueden en Java asignar números a una variable booleana (en C, el valor false se asocia al número 0, y cualquier valor distinto de cero se asocia a true). Caracteres Los valores de tipo carácter sirven para almacenar símbolos de escritura (en Java se puede almacenar cualquier código Unicode). Los valores Unicode son los que Java utiliza para los caracteres. Ejemplo: char letra; letra=’C’; //Los caracteres van entre comillas letra=67; //El código Unicode de la C es el 67. Esta línea //hace lo mismo que la anterior Cadena de caracteres Más conocidas por el nombre de Strings, no es un tipo primitivo de dato: es en realidad una clase, la clase String. Sin embargo, se pueden escribir constantes de este tipo, encerrándolas entre comillas dobles. Pueden contener todo tipo de caracteres, pudiendo ser algunas secuencias 'escape' como las vistas más arriba: "Hola, mundo\n" "Esta frase tiene \"comillas dobles\" y \'comillas simples\'" Formando Emprendedores De Calidad Para Un Mundo Empresarial 27 Instituto de Educación Superior “San Ignacio de Monterrico” La doble comilla inicial y la final de un String deben estar en la misma línea del código fuente. Como hemos visto en algunos ejemplos, el operador + sirve para concatenar strings. Si uno de los dos operandos del + no es un String, lo convierte automáticamente a una cadena de caracteres. Variables Una variable es un nombre que contiene un valor que puede cambiar a lo largo del programa es decir representan direcciones de memoria en las que podemos alojar temporalmente la información que necesitemos, y por otro, nos ofrecen la facilidad de referirnos a ellas mediante un nombre. Las variables son los contenedores de los datos que utiliza un programa. Cada variable ocupa un espacio en la memoria RAM del ordenador para almacenar un dato determinado. Una declaración de variable se compone de dos partes: su tipo y su nombre. Adicionalmente puede indicarse un valor para su inicialización. El tipo de la variable determina los valores que puede contener y las operaciones que se podrán realizar con ella. Según el tipo de información que contienen pueden ser variables de tipos primitivos o variables de referencia. Las variables tienen un nombre (un identificador) que sólo puede contener letras, números y el carácter de subrayado (también vale el símbolo $). El nombre puede contener cualquier carácter Unicode. Desde el punto de vista del papel que desempeñan, las variables pueden ser: • Variable miembro de una clase: Se definen dentro de la clase y fuera de cualquier método. • Variables locales: Se definen dentro de un método o, más en general, dentro de cualquier bloque entre llaves ({ }). Se crean dentro del bloque y se destruyen al finalizar dicho bloque. Declaración de variables Formando Emprendedores De Calidad Para Un Mundo Empresarial 28 Instituto de Educación Superior “San Ignacio de Monterrico” Antes de poder utilizar una variable, ésta se debe declarar. Lo cual se debe hacer de esta forma: TipoDeDato NombreVariable; Donde tipo es el tipo de datos que almacenará la variable (texto, números enteros,...) y nombrevariable es el nombre con el que se conocerá la variable. Ejemplo: int dias; boolean decision; También se puede hacer que la variable tome un valor inicial al declarar: int dias=365; Y también se puede declarar más de una variable a la vez: int dias=365, anio=23, semanas; Al declarar una variable se puede incluso utilizar una expresión: int a=13, b=18; int c=a+b; Alcance o ámbito Se denomina visibilidad, ámbito o alcance de una variable, a la parte de una aplicación en la que dicha variable es accesible. En otras palabras, la parte en la cual puede usarse dicha variable es decir hace referencia a la duración de una variable. Como norma general, podemos decir que las variables declaradas dentro de un bloque (entre llaves) son visibles y existen dentro de ese bloque. Por ejemplo, las variables declaradas al principio de una función existen mientras se ejecute la función, las declaradas dentro de un bloque “if” sólo serán accesibles y válidas dentro de dicho bloque y las variables miembro de una clase son válidas mientras existe el objeto. Formando Emprendedores De Calidad Para Un Mundo Empresarial 29 Instituto de Educación Superior “San Ignacio de Monterrico” En el ejemplo: if(z>1) { int x=12; } System.out.println(x); //Error Existe un error, porque la variable se usa fuera del bloque en el que se creo. Eso no es posible, porque una variable tiene como ámbito el bloque de código en el que fue creada (salvo que sea una propiedad de un objeto). Variables Miembro Las variables miembro de una clase declarada como pública (public), serán accesibles mediante una referencia a un objeto de dicha clase usando el operador punto. Por su parte, las declaradas como privadas (private) no son accesibles desde otras clases. Las funciones miembro de una clase tienen acceso directo a todas las variables miembro de la clase sin necesidad de anteponer el nombre de un objeto de la clase. Sin embargo, las funciones miembro de una clase “B” derivada de otra “A”, tienen acceso a todas las variables miembro de “A” declaradas como públicas o protegidas (protected), pero no a las declaradas como privadas. Una clase hija sólo puede acceder directamente a las variables y función miembro de su clase padre declaradas como públicas o protegidas. Otra característica del lenguaje Java consiste en que es posible declarar una variable dentro de un bloque con el mismo nombre que una variable miembro, pero no con el nombre de otra variable local que ya existiera. La variable declarada dentro del bloque oculta a la variable miembro en ese bloque. Para acceder a la variable miembro oculta sería necesario que usáramos el operador this. Formando Emprendedores De Calidad Para Un Mundo Empresarial 30 Instituto de Educación Superior “San Ignacio de Monterrico” Sintaxis: [modificador] <Tipo> <nombreDeVariable> [= <inicializador>]; Convenciones y nomenclatura Con respecto a los nombres de variables, las reglas del lenguaje Java son muy amplias y permiten mucha libertad, pero es habitual seguir ciertas normas que faciliten la lectura y el mantenimiento de los programas. Por convención, se recomienda seguir las siguientes reglas: o Normalmente se emplean nombres con minúsculas salvo las excepciones que se enumeran en los siguientes puntos. o En los nombres que se componen de varias palabras es aconsejable colocar una detrás de otra poniendo en mayúscula la primera letra de cada palabra. o Los nombres de las clases y las interfaces empiezan siempre por mayúscula. o Los nombres de objetos, métodos y variables empiezan siempre por minúscula. o Los nombres de las variables finales (las constantes) se definen siempre con mayúsculas. Ejercicio # class Circunferencia { // Es una clase private final double PI = 3.14159; // Es una variable final private double elRadio; // Es una variable miembro de la clase public void establecerRadio(double radio) { // Son un método y una variable local elRadio = radio; } public double calcularLongitud() { // Es un método Formando Emprendedores De Calidad Para Un Mundo Empresarial 31 Instituto de Educación Superior “San Ignacio de Monterrico” return (2 * PI * elRadio); } } Modificadores Los modificadores son elementos del lenguaje que se colocan delante de la definición de variables locales, dato miembro, métodos o clases y que alteran o condicionan el significado del elemento. Dentro de este grupo de modificadores tenemos un subgrupo denominado modificadores de acceso. Modificadores de Acceso public (Acceso libre) El campo es accesible de forma general sin restricciones. Significa que toda definición será accesible de cualquier punto, ya sea un método, campo o clase; Es decir indica que el elemento puede ser utilizado dentro de la clase, dentro de las clases descendientes y desde cualquier objeto de la clase. protected (Clases Heredadas y misma Clase) El campo es accesible desde cualquier subclase (tanto dentro como fuera del paquete) y a todo método dentro del paquete. Es decir indica que el elemento puede ser utilizado dentro de la clase, dentro de las clases descendientes, mas no desde un objeto de la clase. private (Solo en la misma Clase) El calificador private indica que dicho componente será accesible únicamente dentro de la Clase en cuestión, si se intenta accesar cualquier elemento de este tipo dentro de otra Clase será generado un error de compilación. El calificador private suele utilizarse en Clases que serán modificadas continuamente, esto permite evitar futuros quebrantos en otras Clases como fue mencionado al inicio. default (Clase en Librería y misma Clase) Formando Emprendedores De Calidad Para Un Mundo Empresarial 32 Instituto de Educación Superior “San Ignacio de Monterrico” Tambien denominado Package o tambien denominado Friendly, El campo es accesible a todos los métodos dentro del paquete. Es el acceso por defecto. Cuando no es empleado ninguno de los calificadores de acceso mencionados anteriormente los elementos son considerados amigables, esto implica que todo campo/método carente de calificador será accesible dentro de todas Clases pertenecientes a su misma librería ("package"). Modificadores static Una de los posibles usos del modificador static es compartir el valor de una variable miembro entre objetos de una misma clase. Si declaramos una variable miembro de una clase, todos los objetos que declaremos basandonos en esa clase compartiran el valor de aquellas variables a las que se les haya aplicado el modificador static, y se podrá modificar el valor de este desde todas. Un caso en el que nos podría ser muy util este modificador, es en una clase que nos diga la cantidad de objetos que se han creado basandose en ella. Podemos escribir una línea de código en el constructor que incremente la variable contador con el modificador static, y así cada vez que se declare un objeto el contador se incrementará. Desde cualquier objeto podremos consultar el valor del contador. El código sería algo así. class Clase { static int contador; Clase() { contador++; } int getContador() { return contador; } } class Codigo { Formando Emprendedores De Calidad Para Un Mundo Empresarial 33 Instituto de Educación Superior “San Ignacio de Monterrico” public static void main(String[] args) { Clase uno = new Clase(); Clase dos = new Clase(); Clase tres = new Clase(); Clase cuatro = new Clase(); System.out.println("Hemos declarado" + uno.getContador() + " objetos."); } } Esto tiene varias ventajas, porque además de ahorrarnos algunas posiciones de memoria (porque todos objetos comparten la misma) podemos crear variables compartidas, cosa que abre la puerta a diversas posibilidades de uso. final El modificador final se aplica a clases, métodos y variables. El significado de final varia de contexto a contexto, pero la idea en si es la misma, la característica no puede ser cambiada. Esto quiere decir que de una clase final no se puede derivar otra clase, una variable final no puede ser modificada una vez que se le ha asignado el valor y un método final no puede ser sobreescrito. Aunque una variable final no puede ser cambiada, su contenido puede ser modificado. Si esta variable apunta a un objeto, es perfectamente válido modificar las propiedades del objeto. abstract El modificador abstract puede ser aplicado a clases y métodos. Una clase que es abstracta no puede ser instanciada (esto es, no se puede llamar a su constructor). Las clases abstractas se pueden utilizar como mecanismo para trasladar la implementación de los métodos a la subclase. De esta forma, para poder utilizar una clase abstracta se tiene que derivar y se tienen que implementar los métodos abstractos definidos en ellas. El compilador obligará a implementar todos los métodos abstractos de la clase o declarar la clase derivada abstracta. Si una clase tiene uno o más métodos abstractos el compilador insiste en que se debe declarar Formando Emprendedores De Calidad Para Un Mundo Empresarial 34 Instituto de Educación Superior “San Ignacio de Monterrico” abstracta. También debe ser abstracta la clase si hereda uno o más métodos abstractos para el cual no provee implementación o si la clase declara una interfase pero no proporciona implementaciones para cada uno de sus métodos. native Este modificador solo puede referirse a métodos, indica que el cuerpo de un método se encuentra fuera de la máquina virtual de Java, en una librería. El código nativo esta escrito en otro lenguaje de programación, típicamente C o C++ y compilado para una sola plataforma por lo que la independencia de plataforma de Java es violada cuando utilizamos este tipo de métodos. transient El modificador transient es solo aplicado a variables. Una variable transient no es almacenada como parte del estado persistente de su objeto. Muchos objetos que son declarados con interfases Serializable o Externalizable pueden tener su estado serializado y escribir a destinos fuera de la máquina virtual. Esto se logra pasando el objeto a el método writeObject() de la clase ObjectOutputStream. Si el objeto es pasado a un FileOutputStream, entonces el estado del objeto es escrito en un fichero. Si el objeto es pasado a un socket OutputStream, entonces el estado del objeto será escrito en una red. En ambos casos el objeto puede ser reconstruido leyendo de un ObjectInputStream. Cuando hay cierta información que no se desea enviar como parte del objeto, como puede ser cierta información delicada que quizás por razones de seguridad no deben ser enviadas a través de un canal inseguro (una contraseña), se declara transient y esta no es escrita durante la serialización. synchronized Este modificador es utilizado para controlar el acceso a código crítico en programas de hilado múltiple. Es casi imprescindible su uso cuando Formando Emprendedores De Calidad Para Un Mundo Empresarial 35 Instituto de Educación Superior “San Ignacio de Monterrico” utilizamos hilado múltiple y debemos acceder a propiedades o recursos desde varios hilos a la vez. volatile Solo las variables pueden ser declaradas volatile. Esto quiere decir que pueden ser modificadas de forma asincrónica para que el compilador tome las precauciones necesarias. Este tipo de variables tiene especial interés para los ambientes con varios procesadores ya que cuando un hilo modifica una variable de un objeto, este puede tener una copia local de la variable y modificar esta. El problema surge cuando otro hilo lee la variable y el valor recibido es el de la variable local al hilo. Esta puede diferir de la variable local del otro hilo e inclusive de la variable almacenada en una suerte de "memoria principal". Al especificar la variable como volatile se le indica a el compilador que la variable debe ser leída y escrita directamente de la "memoria principal" cada vez que se necesita evitando tener copias locales distintas. Características No todos los modificadores pueden ser aplicados a todas las características. Las clases de nivel superior no pueden ser protegidas y los métodos no pueden ser transient. En cambio la palabra clave static es tan general que se puede aplicar hasta en bloques de código flotantes. La siguiente tabla muestra las posibles combinaciones entre características y modificadores: Modificador Clase Variable Método Constructor Bloque flotante public yes yes yes yes no protected no yes yes yes no default yes yes yes yes yes private no yes yes yes no final yes yes yes no no abstract yes no yes no no static no yes yes no yes native no no yes no no transient no yes no no no volatile no yes no no no synchronized no no yes no yes Formando Emprendedores De Calidad Para Un Mundo Empresarial 36 Instituto de Educación Superior “San Ignacio de Monterrico” Constantes Constantes Literales Por su parte, las constantes literales son representaciones literales de datos en el código fuente. Estos valores se emplean para inicializar variables o para usarlas en expresiones en las que se requieren valores constantes. Las constantes literales pueden usarse para referirse explícitamente a uno de los tipos siguientes: int, long, float, double, boolean, char, String y null. En caso de que necesitáramos representar un tipo primitivo distinto de los indicados, podríamos hacer una conversión explícita al tipo deseado. Por otra parte, los objetos no pueden ser representados mediante estas constantes, y por tanto, no existen constantes literales de tipo de referencia. Constantes Enteras Las constantes enteras se representan por un valor que está formado sólo por dígitos numéricos y no tienen la coma decimal. Si el número no comienza por cero, representa un número decimal (en base 10). Para representar constantes de tipo “long” se emplea el mismo método pero añadiendo al final la letra “L”. Está permitido el empleo de la letra “L” en minúsculas, pero no debe usarse ya que puede confundirse con el número uno. Además de representaciones en formato decimal, se pueden representar constantes en los formatos octal y hexadecimal. Las constantes octales se representan anteponiendo un cero al propio número, que además, sólo podrá estar formado por los dígitos del 0 al 7. Por su parte, las constantes hexadecimales se representan anteponiendo 0x o 0X y permitiendo que los dígitos del número puedan ser del 0 al 9 y las letras de la A a la F (en mayúsculas o minúsculas). Las constantes octales y hexadecimales son de tipo entero a menos que estén seguidas por la letra “L”, en cuyo caso serían de tipo long. Formando Emprendedores De Calidad Para Un Mundo Empresarial 37 Instituto de Educación Superior “San Ignacio de Monterrico” int a = 1002; // Tipo int long b = 1002L; // Tipo long int c = 053; // Tipo int en octal int d = 053L; // Tipo long en octal int e = 0X002B ; // Tipo int en hexadecimal int f = 0X002BL; // Tipo long en hexadecimal El tipo char es un tipo entero, no obstante se trata de un tipo especial pensado para almacenar caracteres Unicode. Estos últimos son similares a los caracteres ASCII en que están pensados para almacenar símbolos, números y caracteres. Sin embargo, el rango del tipo char es mucho mayor porque permite representar caracteres de casi todos los idiomas del mundo. Las constantes literales de tipo char pueden representarse de dos formas distintas: • Encerrando un único carácter entre comillas simples. • Utilizando una secuencia de escape, que es muy útil para representar caracteres que no pueden escribirse mediante el teclado. La secuencia de escape se representa por una barra inclinada inversa (\) seguida de un número octal de tres dígitos, o bien por la barra seguida de la letra “u” y de un número hexadecimal de cuatro dígitos. La secuencia de escape también debe ir encerrada entre comillas simples. También existe una serie de secuencias de escape especiales que se utilizan para representar algunos de los caracteres ASCII más comunes. En la siguiente tabla se relacionan todos. Carácter \n \t \r \f \b \\ \’ \” Significado Nueva Línea Tabulador Retroceso de Carro Comienzo de Pagina Borrado a la Izquierda El Carácter \ El Carácter ’ El Carácter ” Formando Emprendedores De Calidad Para Un Mundo Empresarial 38 Instituto de Educación Superior “San Ignacio de Monterrico” Constantes De Coma Flotante Se expresan mediante un valor numérico que incluya al menos una posición decimal. A menos que se indique otra cosa, las constantes de coma flotante serán de tipo double. Para denotar que la constante sea de tipo float es necesario posponer la letra “f” o “F”. Por claridad, también pueden expresarse las constantes de tipo double posponiendo la letra “d” o “D” al número. Este tipo de constantes también pueden expresarse mediante la notación exponencial, que define un número en dos partes: la mantisa y el exponente. La mantisa es un número en coma flotante con un dígito significativo y cierto número de decimales. El exponente es un número que representa la potencia de 10 que multiplica a la mantisa. Entre la mantisa y el exponente se debe poner la letra “e” o “E”. double a = 123.4; // Tipo double float b = 123.4F; // Tipo float double c = 4.502e-12; // Tipo double en notación exponencial. Constantes De Cadena Las constantes de cadena se representan por una secuencia de caracteres encerrados entre comillas dobles. Dentro de ellas se pueden incluir las secuencias de escape que antes mencionamos. Veamos algunos ejemplos: String a = “Texto simple”; // Texto simple String b = “Esta es la letra \u0041”; // Usando una secuencia de escape para la letra “A” String c = “El proceso ha terminado.\nPulse una tecla”; // Texto en dos líneas Constantes Booleanas Existen dos: true y false. Al contrario que en otros lenguajes de programación, ninguna de estas constantes puede ser representada mediante un valor entero usando como criterio la igualdad a cero. Formando Emprendedores De Calidad Para Un Mundo Empresarial 39 Instituto de Educación Superior “San Ignacio de Monterrico” Constante “null” Existe una constante nula (null) que podemos usarla para representar el valor null. Operadores Los datos se manipulan muchas veces utilizando operaciones con ellos. Los datos se suman, se restan,... y a veces se realizan operaciones más complejas. Operadores Aritméticos Son: Operador significado + Suma - Resta * Producto / División % Módulo (resto) Hay que tener en cuenta que el resultado de estos operadores varía notablemente si usamos enteros o si usamos números de coma flotante. Por ejemplo: double resultado1, d1=14, d2=5; int resultado2, i1=14, i2=5; resultado1= d1 / d2; resultado2= i1 / i2; resultado1 valdrá 2.8 mientras que resultado2 valdrá 2. Es más incluso: double resultado; int i1=7,i2=2; resultado=i1/i2; //Resultado valdrá 3 resultado=(double)i1/(double)i2; //Resultado valdrá 3.5 Formando Emprendedores De Calidad Para Un Mundo Empresarial 40 Instituto de Educación Superior “San Ignacio de Monterrico” El operador del módulo (%) para calcular el resto de una división entera. Ejemplo: int resultado, i1=14, i2=5; resultado = i1 % i2; //El resultado será 4 Operadores Condicionales o relacionales Sirven para comparar valores. Siempre devuelven valores booleanos. Son: Operador significado < Menor > Mayor >= Mayor o igual <= Menor o igual == Igual != Distinto Operadores Lógicos Los operadores lógicos (AND, OR y NOT), sirven para evaluar condiciones complejas. NOT sirve para negar una condición. Ejemplo: boolean mayorDeEdad, menorDeEdad; int edad = 21; mayorDeEdad = edad >= 18; //mayorDeEdad será true menorDeEdad = !mayorDeEdad; //menorDeEdad será false El operador && (AND) sirve para evaluar dos expresiones de modo que si ambas son ciertas, el resultado será true sino el resultado será false. Ejemplo: boolean carnetConducir=true; int edad=20; boolean puedeConducir= (edad>=18) && carnetConducir; //Si la edad es de al menos 18 años y carnetConducir es //true, puedeConducir es true Formando Emprendedores De Calidad Para Un Mundo Empresarial 41 Instituto de Educación Superior “San Ignacio de Monterrico” El operador || (OR) sirve también para evaluar dos expresiones. El resultado será true si al menos uno de las expresiones es true. Ejemplo: boolean nieva =true, llueve=false, graniza=false; boolean malTiempo= nieva || llueve || graniza; Operadores de Asignación Permiten asignar valores a una variable. El fundamental es “=”. Pero sin embargo se pueden usar expresiones más complejas como: x += 3; En el ejemplo anterior lo que se hace es sumar 3 a la x (es lo mismo x+=3, que x=x+3). Eso se puede hacer también con todos estos operadores: += |= -= ^= *= %= /= >>= &= <<= También se pueden concatenar asignaciones: x1 = x2 = x3 = 5; Otros operadores de asignación son “++” (incremento) y “- -” (decremento). Ejemplo: x++; //esto es x=x+1; x--; //esto es x=x-1; Pero hay dos formas de utilizar el incremento y el decremento. Se puede usar por ejemplo x++ o ++x La diferencia estriba en el modo en el que se comporta la asignación. Formando Emprendedores De Calidad Para Un Mundo Empresarial 42 Instituto de Educación Superior “San Ignacio de Monterrico” Ejemplo: int x=5, y=5, z; z=x++; //z vale 5, x vale 6 z=++y; //z vale 6, y vale 6 Operador (?) Este operador (conocido como if de una línea) permite ejecutar una instrucción u otra según el valor de la expresión. Sintaxis: expresionlogica?valorSiVerdadero:valorSiFalso; Ejemplo: paga=(edad>18)?6000:3000; En este caso si la variable edad es mayor de 18, la paga será de 6000, sino será de 3000. Se evalúa una condición y según es cierta o no se devuelve un valor u otro. Nótese que esta función ha de devolver un valor y no una expresión correcta. Es decir, no funcionaría: (edad>18)? paga=6000: paga=3000; /ERROR!!!! Conversión Entre Tipos (Casting) Hay veces en las que se deseará realizar algo como: int a;byte b=12; a=b; La duda está en si esto se puede realizar. La respuesta es que sí. Sí porque un dato byte es más pequeño que uno int y Java le convertirá de forma implícita. Sin embargo en: int a=1; byte b; b=a; El compilador devolverá error aunque el número 1 sea válido para un dato byte. Para ello hay que hacer un casting. Eso significa poner el tipo deseado entre paréntesis delante de la expresión. Formando Emprendedores De Calidad Para Un Mundo Empresarial 43 Instituto de Educación Superior “San Ignacio de Monterrico” int a=1; byte b; b= (byte) a; //No da error En el siguiente ejemplo: byte n1=100, n2=100, n3; n3= n1 * n2 /100; Aunque el resultado es 100, y ese resultado es válido para un tipo byte; lo que ocurrirá en realidad es que ocurrirá un error. Eso es debido a que primero multiplica 100 * 100 y como eso da 10000, no tiene más remedio el compilador que pasarlo a entero y así quedará aunque se vuelva a dividir. La solución correcta sería: n3 = (byte) (n1 * n2 / 100); Más ejemplos de CASTING int a = 24; byte b = (byte) a; short c = (short) a; long d = (long) a; Estructuras De Control De Flujo Las estructuras de control en Java son básicamente la misma que en C, con excepción del goto, que no existe. Por ejemplo: public final String toString() { if (y<0) return x+"-i"+(-y); else return +x+"+i"+y; } if Permite crear estructuras condicionales simples; en las que al cumplirse una condición se ejecuta una serie de instrucciones. Se puede hacer que otro Formando Emprendedores De Calidad Para Un Mundo Empresarial 44 Instituto de Educación Superior “San Ignacio de Monterrico” conjunto de instrucciones se ejecute si la condición es falsa. La condición es cualquier expresión que devuelva un resultado de true o false. Sintaxis: if (condición) instrucción si es true; else instrucción si es false; O bien: if (condición) { instrucciones que se ejecutan si la condición es true } else { instrucciones que se ejecutan si la condición es false } La parte else es opcional. Ejemplo: if ((diasemana>=1) && (diasemana<=5)){ trabajar = true; } else { trabajar = false; } Se pueden anidar varios if a la vez. De modo que se comprueban varios valores. Ejemplo: if (diasemana==1) dia=”Lunes”; else if (diasemana==2) dia=”Martes”; else if (diasemana==3) dia=”Miércoles”; else if (diasemana==4) dia=”Jueves”; else if (diasemana==5) dia=”Viernes”; else if (diasemana==6) dia=”Sábado”; else if (diasemana==7) dia=”Domingo”; else dia=”?”; switch Formando Emprendedores De Calidad Para Un Mundo Empresarial 45 Instituto de Educación Superior “San Ignacio de Monterrico” Permite ejecutar una serie de operaciones para el caso de que una variable tenga un valor entero dado. La ejecución saltea todos los case hasta que encuentra uno con el valor de la variable, y ejecuta desde allí hasta el final del case o hasta que encuentre un break, en cuyo caso salta al final del case. El default permite poner una serie de instrucciones que se ejecutan en caso de que la igualdad no se de para ninguno de los case. Sintaxis: switch (expresión) { case valor1: sentencias si la expresiona es igual al valor1; [break] case valor2: sentencias si la expresiona es igual al valor2; [break] . . default: sentencias que se ejecutan si no se cumple ninguna de las anteriores } Esta instrucción evalúa una expresión (que debe ser short, int, byte o char), y según el valor de la misma ejecuta instrucciones. Cada case contiene un valor de la expresión; si efectivamente la expresión equivale a ese valor, se ejecutan las instrucciones de ese case y de los siguientes. La instrucción break se utiliza para salir del switch. De tal modo que si queremos que para un determinado valor se ejecuten las instrucciones de un apartado case y sólo las de ese apartado, entonces habrá que finalizar ese case con un break. El bloque default sirve para ejecutar instrucciones para los casos en los que la expresión no se ajuste a ningún case. Ejemplo: Formando Emprendedores De Calidad Para Un Mundo Empresarial 46 Instituto de Educación Superior “San Ignacio de Monterrico” switch (diasemana) { case 1: dia=”Lunes”; break; case 2: dia=”Martes”; break; case 3: dia=”Miércoles”; break; case 4: dia=”Jueves”; break; case 5: dia=”Viernes”; break; case 6: dia=”Sábado”; break; case 7: dia=”Domingo”; break; default: dia=”?”; } Ejemplo 2: switch (diasemana) { case 1: case 2: case 3: case 4: case 5: laborable=true; break; case 6: case 7: laborable=false; } while Formando Emprendedores De Calidad Para Un Mundo Empresarial 47 Instituto de Educación Superior “San Ignacio de Monterrico” La instrucción while permite crear bucles. Un bucle es un conjunto de sentencias que se repiten si se cumple una determinada condición. Los bucles while agrupan instrucciones las cuales se ejecutan continuamente hasta que una condición que se evalúa sea falsa. La condición se mira antes de entrar dentro del while y cada vez que se termina de ejecutar las instrucciones del while Sintaxis: while (condición) { sentencias que se ejecutan si la condición es true } Ejemplo (cálculo de factorial de un número, el factorial de 4 sería: 4*3*2*1): //factorial de 4 int n=4, factorial=1, temporal=n; while (temporal>0) { factorial*=temporal--; } do while Crea un bucle muy similar al anterior, en la que también las instrucciones del bucle se ejecutan hasta que una condición pasa a ser falsa. La diferencia estriba en que en este tipo de bucle la condición se evalúa después de ejecutar las instrucciones; lo cual significa que al menos el bucle se ejecuta una vez. Sintaxis: do { instrucciones } while (condición) for Es un bucle más complejo especialmente pensado para rellenar arrays o para ejecutar instrucciones controladas por un contador. Una vez más se ejecutan una serie de instrucciones en el caso de que se cumpla una determinada condición. Sintaxis: Formando Emprendedores De Calidad Para Un Mundo Empresarial 48 Instituto de Educación Superior “San Ignacio de Monterrico” for (expresiónInicial; condición; expresiónEncadavuelta) { instrucciones; } La expresión inicial es una instrucción que se ejecuta una sola vez: al entrar por primera vez en el bucle for (normalmente esa expresión lo que hace es dar valor inicial al contador del bucle). La condición es cualquier expresión que devuelve un valor lógico. En el caso de que esa expresión sea verdadera se ejecutan las instrucciones. Cuando la condición pasa a ser falsa, el bucle deja de ejecutarse. La condición se valora cada vez que se terminan de ejecutar las instrucciones del bucle. Después de ejecutarse las instrucciones interiores del bucle, se realiza la expresión que tiene lugar tras ejecutarse las instrucciones del bucle (que, generalmente, incrementa o decrementa al contador). Luego se vuelve a evaluar la condición y así sucesivamente hasta que la condición sea falsa. Ejemplo (factorial): //factorial de 4 int n=4, factorial=1, temporal=n; for (temporal=n;temporal>0;temporal--){ factorial *=temporal; } Sentencias De Salida De Un Bucle break Es una sentencia que permite salir del bucle en el que se encuentra inmediatamente. Hay que intentar evitar su uso ya que produce malos hábitos al programar. continue Instrucción que siempre va colocada dentro de un bucle y que hace que el flujo del programa ignore el resto de instrucciones del bucle; dicho de otra forma, va Formando Emprendedores De Calidad Para Un Mundo Empresarial 49 Instituto de Educación Superior “San Ignacio de Monterrico” hasta la siguiente iteración del bucle. Al igual que ocurría con break, hay que intentar evitar su uso. Ejemplo # import java.io.*; class Bucles { public static void main (String argv[ ]) { int i=0; for (i=1; i<5; i++) { System.out.println("antes "+i); if (i==2) continue; if (i==3) break; System.out.println("después "+i); } } } La salida es: antes 1 después 1 antes 2 antes 3 i comienza en 1 (imprime "antes" y "después"); cuando pasa a 2, el continue salta al principio del bucle (no imprime el "después"). Finalmente, cuando "i" vale 3, el break da por terminado el bucle for. Formando Emprendedores De Calidad Para Un Mundo Empresarial 50 Instituto de Educación Superior “San Ignacio de Monterrico” PROGRAMACION ORIENTADA A OBJETOS CON JAVA Clase La primer característica de un programa Java es que este debe definir una Clase que lleve por nombre el mismo nombre del archivo fuente, en este caso si el nombre del archivo se llama Basico.java este debe incluir una definición de una Clase llamada Basico, nótese que ambos nombres coinciden en su sintaxis, esto es, ambos inician con letra mayúscula lo cual es una convención llevada acabo para la definición de Clases. Para definir una Clase se utiliza la palabra reservada class así como un calificador de acceso. Dentro de la definición de la Clase se deben incluir los respectivos métodos que podrán ser invocados. Ejemplo (Basico.java): public class Basico { public static void main(String args[]) { System.out.println("Un despliegue de Datos"); } } En el caso del ejemplo Basico.java únicamente se define el método main, dicho método es invocado por "default" al ejecutarse la Clase. Nótese que el método inicia con letra minúscula, esta es otra convención utilizada para diferenciarse de las distintas clases. La definición del método indica lo siguiente: o Primero se definen los modificadores del método en este caso son public y static. o También se define el valor de retorno del método: void, lo cual indica que no será retornado ningún valor. Formando Emprendedores De Calidad Para Un Mundo Empresarial 51 Instituto de Educación Superior “San Ignacio de Monterrico” o Le sigue el nombre del método: main. o Dentro de paréntesis se incluyen los parámetros de entrada para el método (String args[]). o Finalmente el Paquete.Clase.Método System.out.println envía un mensaje a la pantalla. Definir atributos de la clase (variables, propiedades o datos de las clases) Cuando se definen los datos de una determinada clase, se debe indicar el tipo de propiedad que es (String, int, double, int[][],...) y el modificador de acceso (public, private,...). El modificador indica en qué partes del código ese dato será visible. Ejemplo: class Persona { public String nombre; //Se puede acceder desde cualquier clase private int contraseña; //Sólo se puede acceder desde la clase Persona protected String dirección; //Acceden a esta propiedad esta clase y sus descendientes Por lo general las propiedades de una clase suelen ser privadas o protegidas, a no ser que se trate de un valor constante, en cuyo caso se declararán como públicos. Las variables locales de una clase pueden ser inicializadas. class auto{ public nRuedas=4; Para poder acceder a los atributos de un objeto, se utiliza esta sintaxis: Formando Emprendedores De Calidad Para Un Mundo Empresarial 52 Instituto de Educación Superior “San Ignacio de Monterrico” objeto.atributo ejemplo: Miauto.velocidad; Creación De Constructores Un constructor es un método que es llamado automáticamente al crear un objeto de una clase, es decir al usar la instrucción new. Un constructor no es más que un método que tiene el mismo nombre que la clase. Con lo cual para crear un constructor basta definir un método en el código de la clase que tenga el mismo nombre que la clase. Ejemplo: class Ficha { private int casilla; Ficha() { //constructor casilla = 1; } public void avanzar(int n) { casilla += n; } public int casillaActual(){ return casilla; } } public class app { public static void main(String[] args) { Ficha ficha1 = new Ficha(); ficha1.avanzar(3); System.out.println(ficha1.casillaActual());//Da 4 } } Formando Emprendedores De Calidad Para Un Mundo Empresarial 53 Instituto de Educación Superior “San Ignacio de Monterrico” En la línea Ficha ficha1 = new Ficha(); es cuando se llama al constructor, que es el que coloca inicialmente la casilla a 1. Pero el constructor puede tener parámetros: class Ficha { private int casilla; //Valor inicial de la propiedad Ficha(int n) { //constructor casilla = n; } public void avanzar(int n) { casilla += n; } public int casillaActual(){ return casilla; } } public class app { public static void main(String[] args) { Ficha ficha1 = new Ficha(6); ficha1.avanzar(3); System.out.println(ficha1.casillaActual());//Da 9 } } En este otro ejemplo, al crear el objeto ficha1, se le da un valor a la casilla, por lo que la casilla vale al principio 6. Hay que tener en cuenta que puede haber más de un constructor para la misma clase. Al igual que ocurre con los métodos, los constructores se pueden sobrecargar. De este modo en el código anterior de la clase Ficha se podrían haber colocado los dos constructores que hemos visto, y sería entonces posible este código: Ficha ficha1= new Ficha(); //La propiedad casilla de la //ficha valdrá 1 Formando Emprendedores De Calidad Para Un Mundo Empresarial 54 Instituto de Educación Superior “San Ignacio de Monterrico” Ficha ficha1= new Ficha(6); //La propiedad casilla de la //ficha valdrá 6 Cuando se sobrecargan los constructores (se utilizan varias posibilidades de constructor), se pueden hacer llamadas a constructores mediante el objeto this Métodos Los métodos se utilizan de la misma forma que los atributos, excepto porque los métodos poseen siempre paréntesis, dentro de los cuales pueden ir valores necesarios para la ejecución del método (parámetros): objeto.método(argumentosDelMétodo) Los métodos siempre tienen paréntesis (es la diferencia con las propiedades) y dentro de los paréntesis se colocan los argumentos del método. Que son los datos que necesita el método para funcionar. Por ejemplo: Miauto.avanza(5); Lo cual podría hacer que el auto avance a 5 Km/h. Definir Métodos De Clase (Operaciones O Funciones De Clase) Un método es una llamada a una operación de un determinado objeto. Al realizar esta llamada (también se le llama enviar un mensaje), el control del programa pasa a ese método y lo mantendrá hasta que el método finalice o se haga uso de return. Para que un método pueda trabajar, normalmente hay que pasarle unos datos en forma de argumentos o parámetros, cada uno de los cuales se separa por comas. Ejemplos: balón.botar(); //sin argumentos miCoche.acelerar(10); ficha.comer(posición15); //posición 15 es una variable que se pasa como argumento Formando Emprendedores De Calidad Para Un Mundo Empresarial 55 Instituto de Educación Superior “San Ignacio de Monterrico” partida.empezarPartida(“18:15”,colores); Los métodos de la clase se definen dentro de ésta. Hay que indicar un modificador de acceso (public, private, protected o ninguno, al igual que ocurre con las variables y con la propia clase) y un tipo de datos, que indica qué tipo de valores devuelve el método. Esto último se debe a que los métodos son funciones que pueden devolver un determinado valor (un entero, un texto, un valor lógico,...) mediante el comando return. Si el método no devuelve ningún valor, entonces se utiliza el tipo void que significa que no devuelve valores (en ese caso el método no tendrá instrucción return). El último detalle a tener en cuenta es que los métodos casi siempre necesitan datos para realizar la operación, estos datos van entre paréntesis y se les llama argumentos. Al definir el método hay que indicar que argumentos se necesitan y de qué tipo son. Ejemplo: public class vehiculo { /** Función principal */ int ruedas; private double velocidad=0; String nombre; /** Aumenta la velocidad*/ public void acelerar(double cantidad) { velocidad += cantidad; } /** Disminuye la velocidad*/ public void frenar(double cantidad) { velocidad -= cantidad; } /** Devuelve la velocidad*/ Formando Emprendedores De Calidad Para Un Mundo Empresarial 56 Instituto de Educación Superior “San Ignacio de Monterrico” public double obtenerVelocidad(){ return velocidad; } public static void main(String args[]){ vehiculo miCoche = new vehiculo(); miCoche.acelerar(12); miCoche.frenar(5); System.out.println(miCoche.obtenerVelocidad()); } // Da 7.0 } Argumentos por valor y por referencia En todos los lenguajes éste es un tema muy importante. Los argumentos son los datos que recibe un método y que necesita para funcionar. Ejemplo: public class Matemáticas { public double factorial(int n){ double resultado; for (resultado=n;n>1;n--) resultado*=n; return resultado; } public static void main(String args[]){ Matemáticas m1=new Matemáticas(); double x=m1.factorial(25);//Llamada al método } } En el ejemplo anterior, el valor 25 es un argumento requerido por el método factorial para que éste devuelva el resultado (que será el factorial de 25). En el código del método factorial, este valor 25 es copiado a la variable n, que es la encargada de almacenar y utilizar este valor. Formando Emprendedores De Calidad Para Un Mundo Empresarial 57 Instituto de Educación Superior “San Ignacio de Monterrico” Se dice que los argumentos son por valor, si la función recibe una copia de esos datos, es decir la variable que se pasa como argumento no estará afectada por el código. Ejemplo: class prueba { public void metodo1(int entero){ entero=18; } public static void main(String args[]){ int x=24; prueba miPrueba = new prueba(); miPrueba.metodo1(x); System.out.println(x); //Escribe 24, no 18 } Este es un ejemplo de paso de parámetros por valor. La variable x se pasa como argumento o parámetro para el método metodo1, allí la variable entero recibe una copia del valor de x en la variable entero, y a esa copia se le asigna el valor 18. Sin embargo la variable x no está afectada por esta asignación. Sin embargo en este otro caso: class prueba { public void metodo1(int[] entero){ entero[0]=18; } public static void main(String args[]){ int x[]={24,24}; prueba miPrueba = new prueba(); miPrueba.metodo1(x); System.out.println(x[0]); //Escribe 18, no 24 Aquí sí que la variable x está afectada por la asignación entero[0]=18. La razón es porque en este caso el método no recibe el valor de esta variable, Formando Emprendedores De Calidad Para Un Mundo Empresarial 58 Instituto de Educación Superior “San Ignacio de Monterrico” sino la referencia, es decir la dirección física de esta variable. Entero no es una replica de x, es la propia x llamada de otra forma. Los tipos básicos (int, double, char, boolean, float, short y byte) se pasan por valor. También se pasan por valor las variables String. Los objetos y arrays se pasan por referencia. Devolución de valores Los métodos pueden devolver valores básicos (int, short, double, etc.), Strings, arrays e incluso objetos. En todos los casos es el comando return el que realiza esta labor. En el caso de arrays y objetos, devuelve una referencia a ese array u objeto. Ejemplo: class FabricaArrays { public int[] obtenArray(){ int array[]= {1,2,3,4,5}; return array; } } public class returnArray { public static void main(String[] args) { FabricaArrays fab=new FabricaArrays(); int nuevoArray[]=fab.obtenArray(); } } Sobrecarga De Métodos Una propiedad de la POO es el polimorfismo. Java posee esa propiedad ya que admite sobrecargar los métodos. Esto significa crear distintas variantes del mismo método. Ejemplo: class Matemáticas{ Formando Emprendedores De Calidad Para Un Mundo Empresarial 59 Instituto de Educación Superior “San Ignacio de Monterrico” public double suma(double x, double y) { return x+y; } public double suma(double x, double y, double z){ return x+y+z; } public double suma(double[] array){ double total =0; for(int i=0; i<array.length;i++){ total+=array[i]; } return total; } La clase matemáticas posee tres versiones del método suma. una versión que suma dos números double, otra que suma tres y la última que suma todos los miembros de un array de doubles. Desde el código se puede utilizar cualquiera de las tres versiones según convenga. Método principal main El método principal main de una Clase Java es inalterable, en este sentido inalterable se refiere a sus características: o Siempre debe incluir los calificadores: public y static. o Nunca puede retornar un valor como resultado, es decir siempre debe indicar el tipo de dato void como retorno. o Su parámetro de entrada siempre será un arreglo de String (String[]) el cual es tomado de la línea de comandos o una fuente alterna. Aunque no es un obligacion definir el método main dentro de toda Clase Java, dicho método representa el único mecanismo automático para realizar tareas al invocarse una Clase, esto es, al momento de ejecutarse determinada Clase siempre será ejecutado todo el contenido dentro de dicho método. Formando Emprendedores De Calidad Para Un Mundo Empresarial 60 Instituto de Educación Superior “San Ignacio de Monterrico” Para generar una Clase compilada (Byte-Code) se utiliza el comando javac : javac Basico.java Lo anterior genera un archivo llamado Basico.class; para ejecutar este Byte-Code es empleado el comando java: java Basico Al invocar el comando anterior será ejecutada la Clase Basico.class. Nótese que el comando java recibe el nombre de la Clase sin la extensión .class. Nota: Si se utiliza un IDE de desarrollo este trae opciones para compilar y ejecutar la clase y mostrara los resultados en su propia interfase en algún panel de salida. La Referencia This La palabra this es una referencia al propio objeto en el que estamos. Ejemplo: class punto { int posX, posY;//posición del punto punto(posX, posY){ this.posX=posX; this.posY=posY; } En el ejemplo hace falta la referencia this para clarificar cuando se usan las propiedades posX y posY, y cuando los argumentos con el mismo nombre. Ejemplo: class punto { int posX, posY; /**Suma las coordenadas de otro punto*/ public void suma(punto punto2){ posX = punto2.posX; Formando Emprendedores De Calidad Para Un Mundo Empresarial 61 Instituto de Educación Superior “San Ignacio de Monterrico” posY = punto2.posY; } /** Dobla el valor de las coordenadas del punto*/ public void dobla(){ suma(this); } En el ejemplo anterior, la función dobla, dobla el valor de las coordenadas pasando el propio punto como referencia para la función suma (un punto sumado a sí mismo, daría el doble). Los posibles usos de this son: o this. Referencia al objeto actual. Se usa por ejemplo pasarle como parámetro a un método cuando es llamado desde la propia clase. o this.atributo. Para acceder a una propiedad del objeto actual. o this.método(parámetros). Permite llamar a un método del objeto actual con los parámetros indicados. o this(parámetros). Permite llamar a un constructor del objeto actual. Esta llamada sólo puede ser empleada en la primera línea de un constructor. Manejo de Objetos Se les llama instancias de clase. Son un elemento en sí de la clase, Un objeto se crea utilizando el llamado constructor de la clase. El constructor es el método que permite iniciar el objeto. Creación De Objetos De La Clase Una vez definida la clase, se pueden utilizar objetos de la clase. Normalmente consta de dos pasos. Su declaración, y su creación. La declaración consiste en indicar que se va a utilizar un objeto de una clase determinada. Y se hace igual que cuando se declara una variable simple. Ejemplo: Formando Emprendedores De Calidad Para Un Mundo Empresarial 62 Instituto de Educación Superior “San Ignacio de Monterrico” Auto Miauto; Eso declara el objeto Miauto como objeto de tipo Auto; se supone que previamente se ha definido la clase Auto. Para poder utilizar un objeto, hay que crearle de verdad. Eso consiste en utilizar el operador new. Ejemplo: noriaDePalencia = new Noria(); Al hacer esta operación el objeto reserva la memoria que necesita y se inicializa el objeto mediante su constructor. Organización de las Clases Paquetes (Packages) son un grupo o librería de clases desarrolladas en java que se utilizan para algo específico. Las principales clases de java son las siguientes (incluidas en el JDK): o java.applet: Creado para soportar la creación de applet Java, el paquete java.applet permite a las aplicaciones ser descargadas sobre una red y ejecutarse dentro de una sandbox.. o java.awt: La Abstract Window Toolkit contiene rutinas para soportar operaciones básicas GUI y utiliza ventanas básicas desde el sistema nativo subyacente. o java.awt.event: Soporte y procesamiento de eventos para controles awt. o java.io: El paquete java.io contiene clases que soportan entrada/salida. Las clases del paquete son principalmente streams; sin embargo, se incluye una clase para ficheros de acceso aleatorio. o java.lang: El paquete Java java.lang contiene clases fundamentales e interfaces fuertemente relacionadas con el lenguaje y el sistema runtime. Esto incluye las clases raíz que forman la jerarquía de clases, tipos relacionados con la definición del lenguaje, excepciones básicas, Formando Emprendedores De Calidad Para Un Mundo Empresarial 63 Instituto de Educación Superior “San Ignacio de Monterrico” funciones matemáticas, Hilos, funciones de seguridad, así como también alguna información sobre el sistema nativo subyacente. o java.net: El paquete java.net suminista rutinas especiales IO para redes, permitiendo las peticiones HTTP, así como también otras transacciones comunes. o java.util: Las estructuras de datos que agregan objetos son el foco del paquete java.util. En el paquete está incluida la API Collections, una jerarquía organizada de estructura de datos influenciada fuertemente por consideraciones de patrones de diseño. o javax.swing: Swing es una colección de rutinas que se construyen sobre java.awt para suministrar un toolkit de widgets independiente de plataforma. o javax.swing.event: Soporte y procesamiento de eventos para controles swing. o java.math: El paquete java.math soporta aritmética multiprecision (incluyendo operaciones aritméticas modulares) y suministra generadores de números primos multiprecision usados para la generación de claves criptográficas. Paquetes Un paquete es una colección de clases e interfaces relacionadas. El compilador de Java usa los paquetes para organizar la compilación y ejecución. Es decir, un paquete es una biblioteca. De hecho el nombre completo de una clase es el nombre del paquete en el que está la clase, punto y luego el nombre de la clase. Es decir si la clase Coche está dentro del paquete locomoción, el nombre completo de Coche es locomoción.Coche. A veces resulta que un paquete está dentro de otro paquete, entonces habrá que indicar la ruta completa a la clase. Por ejemplo locomoción.motor.Motor1300 Mediante el comando import, se evita tener que colocar el nombre completo. El comando import se coloca antes de definir la clase. Ejemplo: Formando Emprendedores De Calidad Para Un Mundo Empresarial 64 Instituto de Educación Superior “San Ignacio de Monterrico” import locomoción.motor.Motor1300; Gracias a esta instrucción para utilizar la clase Motor1300 no hace falta indicar el paquete en el que se encuentra, basta indicar sólo Motor1300. Ejemplo: import locomoción.*; //Importa todas las clases del paquete locomoción Esta instrucción no importa el contenido de los paquetes interiores a locomoción (es decir que si la clase Coche está dentro del paquete motor, no sería importada con esa instrucción, ya que el paquete motor no ha sido importado, sí lo sería la clase locomoción.BarcoDeVela). Por ello en el ejemplo lo completo sería: import locomoción.*; import locomoción.motor.*; Cuando desde un programa se hace referencia a una determinada clase se busca ésta en el paquete en el que está colocada la clase y, sino se encuentra, en los paquetes que se han importado al programa. Si ese nombre de clase se ha definido en un solo paquete, se usa. Si no es así podría haber ambigüedad por ello se debe usar un prefijo delante de la clase con el nombre del paquete. Es decir: paquete.clase O incluso: paquete1.paquete2... ...clase En el caso de que el paquete sea subpaquete de otro más grande. Las clases son visibles en el mismo paquete a no ser que se las haya declarado con el modificador private. Organización de los paquetes Los paquetes en realidad son subdirectorios cuya raíz debe ser absolutamente accesible por el sistema operativo. Para ello es necesario usar la variable de entorno CLASSPATH de la línea de comandos. Esta variable se suele definir en el archivo autoexec.bat o en Formando Emprendedores De Calidad Para Un Mundo Empresarial 65 Instituto de Educación Superior “San Ignacio de Monterrico” MI PC en el caso de las últimas versiones de Windows. Hay que añadirla las rutas a las carpetas que contienen los paquetes (normalmente todos los paquetes se suelen crear en la misma carpeta), a estas carpetas se las llama filesystems. Así para el paquete prueba.reloj tiene que haber una carpeta prueba, dentro de la cual habrá una carpeta reloj y esa carpeta prueba tiene que formar parte del classpath. Una clase se declara perteneciente aun determinado paquete usando la instrucción package al principio del código (sin usar esta instrucción, la clase no se puede compilar). Si se usa package tiene que ser la primera instrucción del programa Java: //Clase perteneciente al paquete tema5 que está en ejemplos package ejemplos.tema5; En los entornos de desarrollo o IDEs puede (NetBeans, JBuilder,...) se uno despreocupar de la variable classpath ya que poseen mecanismos de ayuda para gestionar los paquetes. Pero hay que tener en cuenta que si se compila a mano mediante el comando java (véase proceso de compilación, página i) se debe añadir el modificador -cp para que sean accesibles las clases contenidas en paquetes del classpath (por ejemplo java -cp prueba.java). El uso de los paquetes permite que al compilar sólo se compile el código de la clase y de las clases importadas, en lugar de compilar todas las librerías. Sólo se compila lo que se utiliza. Diagrama de Paquetes Formando Emprendedores De Calidad Para Un Mundo Empresarial 66 Instituto de Educación Superior “San Ignacio de Monterrico” Uso de los métodos print y println println(“mensaje”) Escribe el “mensaje” y luego se produce un cambio de línea que equivale a pulsar [Return] print(“mensaje”) Escribe el “mensaje” y el cursor se sitúa al final del mensaje y en la misma línea. Para recordarlo piensa en la siguiente “fórmula”: Esto seria una equivalencia más explicativa: println = print + ln (línea nueva) El comando \n dentro del mensaje del método print o println también realiza un salto de línea en el mensaje. Ejercicio 4 class J001 { public static void main(String [] args) { System.out.println("Hola\nQue tal"); System.out.println("Uno\nDos\nTres\nCuatro"); } } Ejercicio 5 class J002 { Formando Emprendedores De Calidad Para Un Mundo Empresarial 67 Instituto de Educación Superior “San Ignacio de Monterrico” public static void main(String [] args) { System.out.println("Hola"); System.out.println("Adios"); System.out.println(""); System.out.print("Pues vale"); System.out.print("Eso"); System.out.print("Vale"); } } Instrucción Import Hay código que se puede utilizar en los programas que realicemos en Java. Se importa clases de objetos que están contenidas, a su vez, en paquetes estándares. Por ejemplo la clase Date es una de las más utilizadas, sirve para manipular fechas. Si alguien quisiera utilizar en su código objetos de esta clase, necesita incluir una instrucción que permita utilizar esta clase. La sintaxis de esta instrucción es: import paquete.subpaquete.subsubapquete....clase Esta instrucción se coloca en la parte superior del código. Para la clase Date sería: import java.util.Date Lo que significa, importar en el código la clase Date que se encuentra dentro del paquete util que, a su vez, está dentro del gran paquete llamado java. También se puede utilizar el asterisco en esta forma: import java.util.* Esto significa que se va a incluir en el código todas las clases que están dentro del paquete util de java. Ejemplo: Formando Emprendedores De Calidad Para Un Mundo Empresarial 68 Instituto de Educación Superior “San Ignacio de Monterrico” // J003.java = dibuja 2 circunferencias import java.awt.*; import java.awt.event.*; class J003 extends Frame { public J003() { setTitle("Dos circunferencias"); } public static void main(String [] args) { Frame f=new J003(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(300,200); f.setVisible(true); } public void paint(Graphics g){ g.setColor(Color.red); g.drawOval(10,30,30,30); g.setColor(Color.blue); g.drawOval(35,30,30,30); g.drawString("Dos circunferencias",40,100); } } f.setSize(600,400) Determina el tamaño de la ventana: 1ª coordenada = 600 = anchura en píxels, 2ª coordenada = 400 = altura en píxels. g.setColor(Color,red) Establece el rojo como color de dibujo. Formando Emprendedores De Calidad Para Un Mundo Empresarial 69 Instituto de Educación Superior “San Ignacio de Monterrico” g.drawOval(10,30,30,30) Dibuja una circunferencia cuyo “vértice” superior izquierdo se encuentra en el punto 10,30. La tercera coordenada es el diametro horizontal y la cuarta el diámetro vertical. Como los dos diámetros son iguales, resulta una circunferencia. Si fueran distintos, seria una elipse. g.drawString(“Dos circunferencias”,40,100) “Dibuja” el texto que hay entre comillas en el punto de coordenadas 40,100 (1ª coordenada= distáncia horizontal desde el borde izquierdo, 2ª coordenada = distáncia vertical desde el borde superior). Interfaces La limitación de que sólo se puede heredar de una clase, hace que haya problemas ya que muchas veces se deseará heredar de varias clases. Aunque ésta no es la finalidad directa de las interfaces, sí que tiene cierta relación. Mediante interfaces se definen una serie de comportamientos de objeto. Estos comportamientos puede ser “implementados” en una determinada clase. No definen el tipo de objeto que es, sino lo que puede hacer (sus capacidades). Por ello lo normal es que el nombre de las interfaces terminen con el texto “able” (configurable, modificable, cargable, etc). Por ejemplo en el caso de la clase Coche, esta deriva de la superclase Vehículo, pero además puesto que es un vehículo a motor, puede implementar métodos de una interfaz llamada por ejemplo arrancable. Se dirá entonces que la clase Coche es arrancable. Utilizar interfaces Para hacer que una clase utilice una interfaz, se añade detrás del nombre de la clase la palabra implements seguida del nombre del interfaz. Se pueden poner varios nombres de interfaces separados por comas (solucionando, en cierto modo, el problema de la herencia múltiple). class Coche extends vehiculo implements arrancable { public void arrancar (){ } Formando Emprendedores De Calidad Para Un Mundo Empresarial 70 Instituto de Educación Superior “San Ignacio de Monterrico” public void detenerMotor(){ } Hay que tener en cuenta que la interfaz arrancable no tiene porque tener ninguna relación de herencia con la clase vehículo, es más se podría implementar el interfaz arrancable a una bomba de agua. Creación de interfaces Una interfaz en realidad es una serie de constantes y métodos abstractos. Cuando una clase implementa un determinado interfaz debe anular los métodos abstractos de éste, redefiniéndolos en la propia clase. Esta es la base de una interfaz, en realidad no hay una relación sino que hay una obligación por parte de la clase que implemente la interfaz de redefinir los métodos de ésta. Una interfaz se crea exactamente igual que una clase (se crean en archivos propios también), la diferencia es que la palabra interface sustituye a la palabra class y que sólo se pueden definir en un interfaz constantes y métodos abstractos. Todas las interfaces son abstractas y sus métodos también son todos abstractos y públicos (no hace falta poner el modificar abstract se toma de manera implícita). Las variables se tienen obligatoriamente que inicializar. Ejemplo: interface arrancable(){ boolean motorArrancado=false; void arrancar(); void detenerMotor(); } Los métodos son simples prototipos y toda variable se considera una constante (a no ser que se redefina en una clase que implemente esta interfaz, lo cual no tendría mucho sentido). Formando Emprendedores De Calidad Para Un Mundo Empresarial 71 Instituto de Educación Superior “San Ignacio de Monterrico” Subinterfaces Una interfaz puede heredarse de otra interfaz, como por ejemplo en: interface dibujable extends escribible, pintable { Dibujable es subinterfaz de escribible y pintable. Es curioso, pero los interfaces sí admiten herencia múltiple. Esto significa que la clase que implemente el interfaz dibujable deberá incorporar los métodos definidos en escribible y pintable. Variables de interfaz Al definir una interfaz, se pueden crear después variables de interfaz. Se puede interpretar esto como si el interfaz fuera un tipo especial de datos (que no de clase). La ventaja que proporciona es que pueden asignarse variables interfaz a cualquier objeto de una clase que implementa la interfaz. Esto permite cosas como: Arrancable motorcito; //motorcito es una variable de tipo arrancable Coche c=new Coche(); //Objeto de tipo coche BombaAgua ba=new BombaAgua(); //Objeto de tipo BombaAgua motorcito=c; //Motorcito apunta a c motorcito.arrancar() //Se arrancará c motorcito=ba; //Motorcito apunta a ba motorcito=arrancar; //Se arranca la bomba de agua El juego que dan estas variables es impresionante, debido a que fuerzan acciones sobre objetos de todo tipo, y sin importar este tipo; siempre Formando Emprendedores De Calidad Para Un Mundo Empresarial 72 Instituto de Educación Superior “San Ignacio de Monterrico” y cuando estos objetos pertenezcan a clases que implementen el interfaz. Interfaces como funciones de retroinvocación En C++ una función de retroinvocación es un puntero que señala a un método o a un objeto. Se usan para controlar eventos. En Java se usan interfaces para este fin. Ejemplo: interface Escribible { void escribe(String texto); } class Texto implements Escribible { public void escribe(texto){ System.out.println(texto); } } class Prueba { Escribible escritor; public Prueba(Escribible e){ escritor=e; } public void enviaTexto(String s){ escritor.escribe(s); } } En el ejemplo escritor es una variable de la interfaz Escribible, cuando se llama a su método escribe, entonces se usa la implementación de la clase texto. Herencia Es una de las armas fundamentales de la programación orientada a objetos. Permite crear nuevas clases que heredan características presentas en clases anteriores. Esto facilita enormemente el trabajo porque ha permitido crear clases Formando Emprendedores De Calidad Para Un Mundo Empresarial 73 Instituto de Educación Superior “San Ignacio de Monterrico” estándar para todos los programadores y a partir de ellas crear nuestras propias clases personales. Esto es más cómodo que tener que crear nuestras clases desde cero. Para que una clase herede las características de otra hay que utilizar la palabra clave extends tras el nombre de la clase. A esta palabra le sigue el nombre de la clase cuyas características se heredarán. Sólo se puede tener herencia de una clase (a la clase de la que se hereda se la llama superclase y a la clase heredada se la llama subclase). Ejemplo: class coche extends vehiculo { } //La clase coche parte de la definición de vehículo Métodos y propiedades no heredados Por defecto se heredan todos los métodos y propiedades protected y public (no se heredan los private). Además si se define un método o propiedad en la subclase con el mismo nombre que en la superclase, entonces se dice que se está redefiniendo el método, con lo cual no se hereda éste, sino que se reemplaza por el nuevo. Ejemplo: class vehiculo { public int velocidad; public int ruedas; Formando Emprendedores De Calidad Para Un Mundo Empresarial 74 Instituto de Educación Superior “San Ignacio de Monterrico” public void parar() { velocidad = 0; } public void acelerar(int kmh) { velocidad += kmh; } } class coche extends vehiculo{ public int ruedas=4; public int gasolina; public void repostar(int litros) { gasolina+=litros; } } public class app { public static void main(String[] args) { coche coche1=new coche(); coche.acelerar(80);//Método heredado coche.repostar(12); Anulación De Métodos Como se ha visto, las subclases heredan los métodos de las superclases. Pero es más, también los pueden sobrecargar para proporcionar una versión de un determinado método. Por último, si una subclase define un método con el mismo nombre, tipo y argumentos que un método de la superclase, se dice entonces que se sobrescribe o anula el método de la superclase. Ejemplo: Formando Emprendedores De Calidad Para Un Mundo Empresarial 75 Instituto de Educación Superior “San Ignacio de Monterrico” Super A veces se requiere llamar a un método de la superclase. Eso se realiza con la palabra reservada super. Si this hace referencia a la clase actual, super hace referencia a la superclase respecto a la clase actual, con lo que es un método imprescindible para poder acceder a métodos anulados por herencia. Ejemplo public class vehiculo{ double velocidad; public void acelerar(double cantidad){ velocidad+=cantidad; } } public class coche extends vehiculo{ double gasolina; public void acelerar(double cantidad){ super.acelerar(cantidad); gasolina*=0.9; } Formando Emprendedores De Calidad Para Un Mundo Empresarial 76 Instituto de Educación Superior “San Ignacio de Monterrico” En el ejemplo anterior, la llamada super.acelerar(cantidad) llama al método acelerar de la clase vehículo (el cual acelerará la marcha). Es necesario redefinir el método acelerar en la clase coche ya que aunque la velocidad varía igual que en la superclase, hay que tener en cuenta el consumo de gasolina. Se puede incluso llamar a un constructor de una superclase, usando la sentencia super(). Ejemplo: public class vehiculo{ double velocidad; public vehiculo(double v){ velocidad=v; } } public class coche extends vehiculo{ double gasolina; public coche(double v, double g){ super(v); //Llama al constructor de la clase vehiculo gasolina=g } } Por defecto Java realiza estas acciones: o Si la primera instrucción de un constructor de una subclase es una sentencia que no es ni super ni this, Java añade de forma invisible e implícita una llamada super() al constructor por defecto de la superclase, luego inicia las variables de la subclase y luego sigue con la ejecución normal. o Si se usa super(..) en la primera instrucción, entonces se llama al constructor seleccionado de la superclase, luego inicia las propiedades de la subclase y luego sigue con el resto de sentencias del constructor. o Finalmente, si esa primera instrucción es this(..), entonces se llama al constructor seleccionado por medio de this, y después continúa con las Formando Emprendedores De Calidad Para Un Mundo Empresarial 77 Instituto de Educación Superior “San Ignacio de Monterrico” sentencias del constructor. La inicialización de variables la habrá realizado el constructor al que se llamó mediante this. Formando Emprendedores De Calidad Para Un Mundo Empresarial 78 Instituto de Educación Superior “San Ignacio de Monterrico” EXCEPCIONES Introducción a las excepciones Uno de los problemas más importantes al escribir aplicaciones es el tratamiento de los errores. Errores no previstos que distorsionan la ejecución del programa. Las excepciones de Java hacen referencia e este hecho. Se denomina excepción a una situación que no se puede resolver y que provoca la detención del programa; es decir una condición de error en tiempo de ejecución (es decir cuando el programa ya ha sido compilado y se está ejecutando). Ejemplos: • • • El archivo que queremos abrir no existe Falla la conexión a una red La clase que se desea utilizar no se encuentra en ninguno de los paquetes reseñados con import Los errores de sintaxis son detectados durante la compilación. Pero las excepciones pueden provocar situaciones irreversibles, su control debe hacerse en tiempo de ejecución y eso presenta un gran problema. En Java se puede preparar el código susceptible a provocar errores de ejecución de modo que si ocurre una excepción, el código es lanzado (throw) a una determinada rutina previamente preparada por el programador, que permite manipular esa excepción. Si la excepción no fuera capturada, la ejecución del programa se detendría irremediablemente. En Java hay muchos tipos de excepciones (de operaciones de entrada y salida, de operaciones irreales. El paquete java.lang.Exception y sus subpaquetes contienen todos los tipos de excepciones. Cuando se produce un error se genera un objeto asociado a esa excepción. Este objeto es de la clase Exception o de alguna de sus herederas. Este objeto se pasa al código que se ha definido para manejar la excepción. Dicho código puede manipular las propiedades del objeto Exception. Formando Emprendedores De Calidad Para Un Mundo Empresarial 79 Instituto de Educación Superior “San Ignacio de Monterrico” Hay una clase, la java.lang.Error y sus subclases que sirven para definir los errores irrecuperables más serios. Esos errores causan parada en el programa, por lo que el programador no hace falta que los manipule. Estos errores les produce el sistema y son incontrolables para el programador. Las excepciones son fallos más leves, y más manipulables. La estructura Try – match. Las sentencias que tratan las excepciones son try y catch. La sintaxis es: try { instrucciones que se ejecutan salvo que haya un error } catch (ClaseExcepción objetoQueCapturaLaExcepción) { instrucciones que se ejecutan si hay un error} Puede haber más de una sentencia catch para un mismo bloque try. Ejemplo: try { readFromFile(“arch”); } catch(FileNotFoundException e) { //archivo no encontrado } catch (IOException e) { } Jerarquía de Excepciones Formando Emprendedores De Calidad Para Un Mundo Empresarial 80 Instituto de Educación Superior “San Ignacio de Monterrico” Dentro del bloque try se colocan las instrucciones susceptibles de provocar una excepción, el bloque catch sirve para capturar esa excepción y evitar el fin de la ejecución del programa. Desde el bloque catch se maneja, en definitiva, la excepción. Cada catch maneja un tipo de excepción. Cuando se produce una excepción, se busca el catch que posea el manejador de excepción adecuado, será el que utilice el mismo tipo de excepción que se ha producido. Esto puede causar problemas si no se tiene cuidado, ya que la clase Exception es la superclase de todas las demás. Por lo que si se produjo, por ejemplo, una excepción de tipo AritmethicException y el primer catch captura el tipo genérico Exception, será ese catch el que se ejecute y no los demás. Por eso el último catch debe ser el que capture excepciones genéricas y los primeros deben ser los más específicos. Lógicamente si vamos a tratar a todas las excepciones (sean del tipo que sean) igual, entonces basta con un solo catch que capture objetos Exception. Manejo de excepciones Formando Emprendedores De Calidad Para Un Mundo Empresarial 81 Instituto de Educación Superior “San Ignacio de Monterrico” Siempre se debe controlar una excepción, de otra forma nuestro software está a merced de los fallos. En la programación siempre ha habido dos formas de manejar la excepción: • Interrupción. En este caso se asume que el programa ha encontrado un error irrecuperable. La operación que dio lugar a la excepción se anula y se entiende que no hay manera de regresar al código que provocó la excepción. Es decir, la operación que dio pies al error, se anula. • Reanudación. Se puede manejar el error y regresar de nuevo al código que provocó el error. La filosofía de Java es del tipo interrupción, pero se puede intentar emular la reanudación encerrando el bloque try en un while que se repetirá hasta que el error deje de existir. Ejemplo: boolean indiceNoValido=true; int i; //Entero que tomará números aleatorios de 0 a 9 String texto[]={“Uno”,”Dos”,”Tres”,”Cuatro”,”Cinco”}; while(indiceNoValido){ try{ i=Math.round(Math.random()*9); System.out.println(texto[i]; indiceNoValido=false; }catch(ArrayIndexOutOfBoundsException exc){ System.out.println(“Fallo en el índice”); } } En el código anterior, el índice i calcula un número del 0 al 9 y con ese número el código accede al array texto que sólo contiene 5 elementos. Esto producirá muy a menudo una excepción del tipo ArrayIndexOutOfBoundsException que es manejada por el catch correspondiente. Normalmente no se continuaría Formando Emprendedores De Calidad Para Un Mundo Empresarial 82 Instituto de Educación Superior “San Ignacio de Monterrico” intentando. Pero como tras el bloque catch está dentro del while, se hará otro intento y así hasta que no haya excepción, lo que provocará que indiceNovalido valga true y la salida, al fin, del while. Como se observa en la ¡Error! No se encuentra el origen de la referencia., la clase Exception es la superclase de todos los tipos de excepciones. Esto permite utilizar una serie de métodos comunes a todas las clases de excepciones: • String getMessage(). Obtiene el mensaje descriptivo de la excepción o una indicación específica del error ocurrido: try{ } catch (IOException ioe){ System.out.println(ioe.getMessage()); } • String toString(). Escribe una cadena sobre la situación de la excepción. Suele indicar la clase de excepción y el texto de getMessage(). • void printStackTrace(). Escribe el método y mensaje de la excepción (la llamada información de pila). El resultado es el mismo mensaje que muestra el ejecutor (la máquina virtual de Java) cuando no se controla la excepción. Uso del throws Al llamar a métodos, ocurre un problema con las excepciones. El problema es, si el método da lugar a una excepción, ¿quién la maneja? ¿El propio método? ¿O el código que hizo la llamada al método? Con lo visto hasta ahora, sería el propio método quien se encargara de sus excepciones, pero esto complica el código. Por eso otra posibilidad es hacer que la excepción la maneje el código que hizo la llamada. Formando Emprendedores De Calidad Para Un Mundo Empresarial 83 Instituto de Educación Superior “San Ignacio de Monterrico” Esto se hace añadiendo la palabra throws tras la primera línea de un método. Tras esa palabra se indica qué excepciones puede provocar el código del método. Si ocurre una excepción en el método, el código abandona ese método y regresa al código desde el que se llamó al método. Allí se posará en el catch apropiado para esa excepción. Ejemplo: void usarArchivo (String archivo) throws IOException, InterruptedException {... En este caso se está indicando que el método usarArchivo puede provocar excepciones del tipo IOException y InterruptedException. Esto significará, además, que el que utilice este método debe preparar el catch correspondiente para manejar los posibles errores. Ejemplo: try{ objeto.usarArchivo(“C:\texto.txt”);//puede haber excepción } catch(IOException ioe){.. } catch(InterruptedException ie){... } ...//otros catch para otras posibles excepciones Uso del throw Esta instrucción nos permite lanzar a nosotros nuestras propias excepciones (o lo que es lo mismo, crear artificialmente nosotros las excepciones). Ante: throw new Exception(); Formando Emprendedores De Calidad Para Un Mundo Empresarial 84 Instituto de Educación Superior “San Ignacio de Monterrico” El flujo del programa se dirigirá a la instrucción try/catch más cercana. Se pueden utilizar constructores en esta llamada (el formato de los constructores depende de la clase que se utilice): throw new Exception(“Error grave, grave”); Eso construye una excepción con el mensaje indicado. throw permite también relanzar excepciones. Esto significa que dentro de un catch podemos colocar una instrucción throw para lanzar la nueva excepción que será capturada por el catch correspondiente: try{ } catch(ArrayIndexOutOfBoundsException exc){ throw new IOException(); } catch(IOException){ } El segundo catch capturará también las excepciones del primer tipo Uso del finally La cláusula finally está pensada para limpiar el código en caso de excepción. Su uso es: try{ }catch (FileNotFoundException fnfe){ }catch(IOException ioe){ }catch(Exception e){ }finally{ ...//Instrucciones de limpieza } Formando Emprendedores De Calidad Para Un Mundo Empresarial 85 Instituto de Educación Superior “San Ignacio de Monterrico” Las sentencias finally se ejecutan tras haberse ejecutado el catch correspondiente. Si ningún catch capturó la excepción, entonces se ejecutarán esas sentencias antes de devolver el control al siguiente nivel o antes de romperse la ejecución. Hay que tener muy en cuenta que las sentencias finally se ejecutan independientemente de si hubo o no excepción. Es decir esas sentencias se ejecutan siempre, haya o no excepción. Son sentencias a ejecutarse en todo momento. Por ello se coloca en el bloque finally código común para todas las excepciones (y también para cuando no hay excepciones). Clases Envoltorio (wrapper) No son más que clases que modelan los tipos de datos primitivos tales como enteros y flotantes, precisamente estos tipos primitivos son los únicos elementos en Java que no son clases. Cabe destacar una diferencia notable entre los tipos primitivos y sus wrapper: Los tipos primitivos se pasan como argumento a los métodos por valor, mientras que los objetos se pasan por referencia. Esto implica una ventaja en cuanto a eficiencia. Por otra parte estos wrapper ofrecen métodos de conversión muy convenientes. Estas clases se encuentran en java.lang, derivan de Number que deriva de Object. Cada una de estas clases contiene un valor primitivo del tipo relacionado, por ejemplo un objeto Integer contiene un int como atributo. En Java se dice que todo es considerado un objeto. Para hacer que esta filosofía sea más real se han diseñado una serie de clases relacionadas con los tipos básicos. El nombre de estas clases es: Clase java.lang.Void java.lang.Boolean java.lang.Character java.lang.Byte java.lang.Short java.lang.Integer Representa al tipo básico.. void boolean char byte short int Formando Emprendedores De Calidad Para Un Mundo Empresarial 86 Instituto de Educación Superior “San Ignacio de Monterrico” java.lang.Long java.lang.Float java.lang.Double long float double Hay que tener en cuenta que no son equivalentes a los tipos básicos. La creación de estos tipos lógicamente requiere usar constructores, ya que son objetos y no tipos básicos. Double n=new Double(18.3); Double o=new Double(“18.5”); El constructor admite valores del tipo básico relacionado e incluso valores String que contengan texto convertible a ese tipo básico. Si ese texto no es convertible, ocurre una excepción del tipo NumberFormatException. La conversión de un String a un tipo básico es una de las utilidades básicas de estas clases, por ello estas clases poseen el método estático valueOf entre otros para convertir un String en uno de esos tipos. Ejemplos: String s=”2500”; Integer a=Integer.valueOf(s); Short b=Short.valueOf(s); Double c=Short.valueOf(s); Byte d=Byte.valueOf(s);//Excepción!!! Hay otro método en cada una de esas clases que se llama parse. La diferencia estriba en que en los métodos parse la conversión se realiza hacia tipos básicos (int, double, float, boolean,...) y no hacia las clase anteriores. Ejemplo: String s=”2500”; int y=Integer.parseInt(s); short z=Short.parseShort(s); double c=Short.parseDouble(s); byte x=Byte.parseByte(s); Formando Emprendedores De Calidad Para Un Mundo Empresarial 87 Instituto de Educación Superior “San Ignacio de Monterrico” Estos métodos son todos estáticos. Todas las clases además poseen métodos dinámicos para convertir a otros tipos (intValue, longValue,... o el conocido toString). Todos estos métodos lanzan excepciones del tipo NumberFormatException, que habrá que capturar con el try y el catch pertinentes. Además han redefinido el método equals para comparar objetos de este tipo. Además poseen el método compareTo que permite comparar dos elementos de este tipo. Algunos métodos de Integer. El constructor de un wrapper está sobrecargado para aceptar el tipo de dato primitivo que va a contener o un objeto String. Integer(int) Integer(String) Integer(int) Integer(String) Entre sus métodos encontramos algunos que nos permiten recuperar el tipo primitivo que queramos. • doubleValue() • floatValue() • longValue() • intValue() • shortValue() • byteValue() También tenemos conversores con la clase String. • String toString() • Integer valueOf(String) Formando Emprendedores De Calidad Para Un Mundo Empresarial 88 Instituto de Educación Superior “San Ignacio de Monterrico” Además todos estos métodos pueden utilizarse en forma estática, esto es, no es necesario crear una instancia para utilizarlos. Aunque esto no es del todo cierto, ya que por ejemplo como sucede con el método valueOf(), retorna una instancia del wrapper. Por lo tanto estas clases wrapper son fabricantes de objetos (ver factory pattern). Estas clases también declaran constantes que definen su valor máximo, su valor mínimo, etc. Conversiones entre distintos tipos de datos básicos He creado esta clase para conversiones con el fin de agrupar estos métodos y mostrar los simples pasos de conversión que debemos seguir. Básicamente el procedimiento en cada uno de los métodos es el mismo: crear una instancia del wrapper del argumento, con el valor de dicho argumento, y luego solicitar el tipo de dato primitivo a retornar por el método de conversión. final class Cast { /*float a int*/ public static int toInt(float f) { int i; i = Float.valueOf(f).intValue(); return i; } /*double a int*/ public static int toInt(double d) { int i; i = Double.valueOf(d).intValue(); return i; } /*int a double*/ Formando Emprendedores De Calidad Para Un Mundo Empresarial 89 Instituto de Educación Superior “San Ignacio de Monterrico” public static double toDouble(int i) { double d; d = Integer.valueOf(i).doubleValue(); return d; } /*String a double*/ public static double toDouble(String s) { double d; d = Double.valueOf(s).doubleValue(); return d; } /*float a String*/ public static String toString(float f) { String s; s = Float.valueOf(f).toString(); return s; } } Si bien esta clase está incompleta (le faltarían muchos métodos más para ampliar su utilidad) podemos hacer un ejemplo y ver como trabaja. int i; float f; double d; String s = "9.543"; d = Cast.toDouble(s); // d = 9.543 i = Cast.toInt(d); // i = 9 f = i; // f = 9.0 s = Cast.toString(f); // s = "9.0" Formando Emprendedores De Calidad Para Un Mundo Empresarial 90 Instituto de Educación Superior “San Ignacio de Monterrico” En conclusión estas clases son muy valiosas, aprender a utilizarlas es muy facil (sus métodos son muy similares, recuerden que derivan de Number) y nos proveen de la funcionalidad extra que se necesita. La clase String Para Java las cadenas de texto son objetos especiales. Los textos deben manejarse creando objetos de tipo String. Ejemplo: String texto1 = “¡Prueba de texto!”; Las cadenas pueden ocupar varias líneas utilizando el operador de concatenación “+”. String texto2 =”Este es un texto que ocupa “ + “varias líneas, no obstante se puede “+ “perfectamente encadenar”; También se pueden crear objetos String sin utilizar constantes entrecomilladas, usando otros constructores: char[] palabra = {‘P’,’a’,’l’,’b’,’r’,’a’};//Array de char String cadena = new String(palabra); byte[] datos = {97,98,99}; String codificada = new String (datos, “8859_1”); En el último ejemplo la cadena codificada se crea desde un array de tipo byte que contiene números que serán interpretados como códigos Unicode. Al asignar, el valor 8859_1 indica la tabla de códigos a utilizar. Comparación Entre Objetos String Los objetos String no pueden compararse directamente con los operadores de comparación. En su lugar se deben utilizar estas expresiones: Formando Emprendedores De Calidad Para Un Mundo Empresarial 91 Instituto de Educación Superior “San Ignacio de Monterrico” • cadena1.equals(cadena2). El resultado es true si la cadena1 es igual a la cadena2. Ambas cadenas son variables de tipo String. • cadena1.equalsIgnoreCase(cadena2). Como la anterior, pero en este caso no se tienen en cuentas mayúsculas y minúsculas. • s1.compareTo(s2). Compara ambas cadenas, considerando el orden alfabético. Si la primera cadena es mayor en orden alfabético que la segunda devuelve 1, si son iguales devuelve 0 y si es la segunda la mayor devuelve -1. Hay que tener en cuenta que el orden no es el del alfabeto español, sino que usa la tabla ASCII, en esa tabla la letra ñ es mucho mayor que la o. • s1.compareToIgnoreCase(s2). Igual que la anterior, sólo que además ignora las mayúsculas (disponible desde Java 1.2) String.valueOf Este método pertenece no sólo a la clase String, sino a otras y siempre es un método que convierte valores de una clase a otra. En el caso de los objetos String, permite convertir valores que no son de cadena a forma de cadena. Ejemplos: String numero = String.valueOf(1234); String fecha = String.valueOf(new Date()); En el ejemplo se observa que este método pertenece a la clase String directamente, no hay que utilizar el nombre del objeto creado (como se verá más adelante, es un método estático). Métodos De Las Variables De Cadena Son métodos que poseen las propias variables de cadena. Para utilizarlos basta con poner el nombre del método y sus parámetros después del nombre de la variable String. Es decir: variableString.método(argumentos) length Formando Emprendedores De Calidad Para Un Mundo Empresarial 92 Instituto de Educación Superior “San Ignacio de Monterrico” Permite devolver la longitud de una cadena (el número de caracteres de la cadena): String texto1=”Prueba”; System.out.println(texto1.length());//Escribe 6 Concatenar Cadenas Se puede hacer de dos formas, utilizando el método concat o con el operador +. Ejemplo: String s1=”Buenos ”, s2=”días”, s3, s4; s3 = s1 + s2; s4 = s1.concat(s2); charAt Devuelve un carácter de la cadena. El carácter a devolver se indica por su posición (el primer carácter es la posición 0) Si la posición es negativa o sobrepasa el tamaño de la cadena, ocurre un error de ejecución, una excepción tipo IndexOutOfBounds-Exception. Ejemplo: String s1=”Prueba”; char c1=s1.charAt(2); //c1 valdrá ‘u’ substring Da como resultado una porción del texto de la cadena. La porción se toma desde una posición inicial hasta una posición final (sin incluir esa posición final). Si las posiciones indicadas no son válidas ocurre una excepción de tipo IndexOutOfBounds-Exception. Se empieza a contar desde la posición 0. Ejemplo: String s1=”Buenos días”; String s2=s1.substring(7,10); //s2 = día indexOf Formando Emprendedores De Calidad Para Un Mundo Empresarial 93 Instituto de Educación Superior “San Ignacio de Monterrico” Devuelve la primera posición en la que aparece un determinado texto en la cadena. En el caso de que la cadena buscada no se encuentre, devuelve -1. El texto a buscar puede ser char o String. Ejemplo: String s1=”Quería decirte que quiero que te vayas”; System.out.println(s1.indexOf(“que”)); //Da 15 Se puede buscar desde una determinada posición. En el ejemplo anterior: System.out.println(s1.indexOf(“que”,16)); //Ahora da 26 lastIndexOf Devuelve la última posición en la que aparece un determinado texto en la cadena. Es casi idéntica a la anterior, sólo que busca desde el final. Ejemplo: String s1=”Quería decirte que quiero que te vayas”; System.out.println(s1.lastIndexOf(“que”); //Da 26 También permite comenzar a buscar desde una determinada posición. endsWith Devuelve true si la cadena termina con un determinado texto. Ejemplo: String s1=”Quería decirte que quiero que te vayas”; System.out.println(s1.endsWith(“vayas”); //Da true startsWith Devuelve true si la cadena empieza con un determinado texto. replace Cambia todas las apariciones de un carácter por otro en el texto que se indique y lo almacena como resultado. El texto original no se cambia, por lo que hay que asignar el resultado de replace a un String para almacenar el texto cambiado: String s1=”Mariposa”; System.out.println(s1.replace(‘a’,’e’));//Da Meripose System.out.println(s1);//Sigue valiendo Mariposa Formando Emprendedores De Calidad Para Un Mundo Empresarial 94 Instituto de Educación Superior “San Ignacio de Monterrico” replaceAll Modifica en un texto cada entrada de una cadena por otra y devuelve el resultado. El primer parámetro es el texto que se busca (que puede ser una expresión regular), el segundo parámetro es el texto con el que se reemplaza el buscado. La cadena original no se modifica. String s1=”Cazar armadillos”; System.out.println(s1.replace(“ar”,”er”));//Da Cazer ermedillos System.out.println(s1);//Sigue valiendo Cazar armadilos toUpperCase Devuelve la versión en mayúsculas de la cadena. toLowerCase Devuelve la versión en minúsculas de la cadena. toCharArray Obtiene un array de caracteres a partir de una cadena. La clase Arrays En el paquete java.utils se encuentra una clase estática llamada Arrays. Una clase estática permite ser utilizada como si fuera un objeto (como ocurre con Math). Esta clase posee métodos muy interesantes para utilizar sobre arrays. Su uso es Arrays.método(argumentos); fill Permite rellenar todo un array unidimensional con un determinado valor. Sus argumentos son el array a rellenar y el valor deseado: int valores[]=new int[23]; Arrays.fill(valores,-1);//Todo el array vale -1 También permite decidir desde que índice hasta qué índice rellenamos: Arrays.fill(valores,5,8,-1);//Del elemento 5 al 7 valdrán -1 Formando Emprendedores De Calidad Para Un Mundo Empresarial 95 Instituto de Educación Superior “San Ignacio de Monterrico” equals Compara dos arrays y devuelve true si son iguales. Se consideran iguales si son del mismo tipo, tamaño y contienen los mismos valores. sort Permite ordenar un array en orden ascendente. Se pueden ordenar sólo una serie de elementos desde un determinado punto hasta un determinado punto. int x[]={4,5,2,3,7,8,2,3,9,5}; Arrays.sort(x);//Estará ordenado Arrays.sort(x,2,5);//Ordena del 2º al 4º elemento binarySearch Permite buscar un elemento de forma ultrarrápida en un array ordenado (en un array desordenado sus resultados son impredecibles). Devuelve el índice en el que está colocado el elemento. Ejemplo: int x[]={1,2,3,4,5,6,7,8,9,10,11,12}; Arrays.sort(x); System.out.println(Arrays.binarySearch(x,8));//Da 7 El método System.arraysCopy La clase System también posee un método relacionado con los arrays, dicho método permite copiar un array en otro. Recibe cinco argumentos: el array que se copia, el índice desde que se empieza a copia en el origen, el array destino de la copia, el índice desde el que se copia en el destino, y el tamaño de la copia (número de elementos de la copia). int uno[]={1,1,2}; int dos[]={3,3,3,3,3,3,3,3,3}; System.arraycopy(uno, 0, dos, 0, uno.length); for (int i=0;i<=8;i++){ System.out.print(dos[i]+" "); } //Sale 112333333 Formando Emprendedores De Calidad Para Un Mundo Empresarial 96 Instituto de Educación Superior “San Ignacio de Monterrico” Arrays Unidimensionales Un array es una colección de valores de un mismo tipo engrosados en la misma variable. De forma que se puede acceder a cada valor independientemente. Para Java además un array es un objeto que tiene propiedades que se pueden manipular. Los arrays solucionan problemas concernientes al manejo de muchas variables que se refieren a datos similares. Por ejemplo si tuviéramos la necesidad de almacenar las notas de una clase con 18 alumnos, necesitaríamos 18 variables, con la tremenda lentitud de manejo que supone eso. Solamente calcular la nota media requeriría una tremenda línea de código. Almacenar las notas supondría al menos 18 líneas de código. Gracias a los arrays se puede crear un conjunto de variables con el mismo nombre. La diferencia será que un número (índice del array) distinguirá a cada variable. En el caso de las notas, se puede crear un array llamado notas, que representa a todas las notas de la clase. Para poner la nota del primer alumno se usaría notas[0], el segundo sería notas[1], etc. (los corchetes permiten especificar el índice en concreto del array). La declaración de un array unidimensional se hace con esta sintaxis. tipo nombre[]; Ejemplo: double cuentas[]; //Declara un array que almacenará valores doubles Declara un array de tipo double. Esta declaración indica para qué servirá el array, pero no reserva espacio en la RAM al no saberse todavía el tamaño del mismo. Tras la declaración del array, se tiene que iniciar. Eso lo realiza el operador new, que es el que realmente crea el array indicando un tamaño. Cuando Formando Emprendedores De Calidad Para Un Mundo Empresarial 97 Instituto de Educación Superior “San Ignacio de Monterrico” se usa new es cuando se reserva el espacio necesario en memoria. Un array no inicializado es un array null. Ejemplo: int notas[]; //sería válido también int[] notas; notas = new int[3]; //indica que el array constará de tres //valores de tipo int //También se puede hacer todo a la vez //int notas[]=new int[3]; En el ejemplo anterior se crea un array de tres enteros (con los tipos básicos se crea en memoria el array y se inicializan los valores, los números se inician a 0). Los valores del array se asignan utilizando el índice del mismo entre corchetes: notas[2]=8; También se pueden asignar valores al array en la propia declaración: int notas[] = {8, 7, 9}; int notas2[]= new int[] {8,7,9};//Equivalente a la anterior Esto declara e inicializa un array de tres elementos. En el ejemplo lo que significa es que notas[0] vale 8, notas[1] vale 7 y notas[2] vale 9. En Java (como en otros lenguajes) el primer elemento de un array es el cero. El primer elemento del array notas, es notas[0]. Se pueden declarar arrays a cualquier tipo de datos (enteros, booleanos, doubles, ... e incluso objetos). La ventaja de usar arrays (volviendo al caso de las notas) es que gracias a un simple bucle for se puede rellenar o leer fácilmente todos los elementos de un array: //Calcular la media de las 18 notas suma=0; for (int i=0;i<=17;i++){ suma+=nota[i]; } Formando Emprendedores De Calidad Para Un Mundo Empresarial 98 Instituto de Educación Superior “San Ignacio de Monterrico” media=suma/18; A un array se le puede inicializar las veces que haga falta: int notas[]=new notas[16]; ... notas=new notas[25]; Pero hay que tener en cuenta que el segundo new hace que se pierda el contenido anterior. Realmente un array es una referencia a valores que se almacenan en memoria mediante el operador new, si el operador new se utiliza en la misma referencia, el anterior contenido se queda sin referencia y, por lo tanto se pierde. Un array se puede asignar a otro array (si son del mismo tipo): int notas[]; int ejemplo[]=new int[18]; notas=ejemplo; En el último punto, notas equivale a ejemplo. Esta asignación provoca que cualquier cambio en notas también cambie el array ejemplos. Es decir esta asignación anterior, no copia los valores del array, sino que notas y ejemplo son referencias al mismo array. Ejemplo: int notas[]={3,3,3}; int ejemplo[]=notas; ejemplo= notas; ejemplo[0]=8; System.out.println(notas[0]);//Escribirá el número 8 Arrays Multidimensionales Los arrays además pueden tener varias dimensiones. Entonces se habla de arrays de arrays (arrays que contienen arrays) Ejemplo: int notas[][]; Formando Emprendedores De Calidad Para Un Mundo Empresarial 99 Instituto de Educación Superior “San Ignacio de Monterrico” notas es un array que contiene arrays de enteros notas = new int[3][12];//notas está compuesto por 3 arrays //de 12 enteros cada uno notas[0][0]=9;//el primer valor es 0 Puede haber más dimensiones incluso (notas[3][2][7]). Los arrays multidimensionales se pueden inicializar de forma más creativa incluso. Ejemplo: int notas[][]=new int[5][];//Hay 5 arrays de enteros notas[0]=new int[100]; //El primer array es de 100 enteros notas[1]=new int[230]; //El segundo de 230 notas[2]=new int[400]; notas[3]=new int[100]; notas[4]=new int[200]; Hay que tener en cuenta que en el ejemplo anterior, notas[0] es un array de 100 enteros. Mientras que notas, es un array de 5 arrays de enteros. Se pueden utilizar más de dos dimensiones si es necesario. Longitud De Un Array Los arrays poseen un método que permite determinar cuánto mide un array. Se trata de length. Ejemplo (continuando del anterior): System.out.println(notas.length); //Sale 5 System.out.println(notas[2].length); //Sale 400 La clase Math. Se echan de menos operadores matemáticos más potentes en Java. Por ello se ha incluido una clase especial llamada Math dentro del paquete java.lang Para poder utilizar esta clase, se debe incluir esta instrucción: import java.lang.Math; Esta clase posee métodos muy interesantes para realizar cálculos matemáticos complejos. Por ejemplo: double x= Math.pow(3,3); //x es 27 Math posee dos constantes, que son: Formando Emprendedores De Calidad Para Un Mundo Empresarial 100 Instituto de Educación Superior “San Ignacio de Monterrico” Math.E Math.PI para la base exponencial, aproximadamente 2.72 para PI, aproximadamente 3.14 Por otro lado posee numerosos métodos que son: Método Math.abs( x ) Math.sin( double a ) Math.cos( double a ) Math.tan( double a ) Math.asin( double r ) Math.acos( double r ) Math.atan( double r ) Math.atan2(double a,double b) Math.exp( double x ) Math.log( double x ) Math.sqrt( double x ) Math.ceil( double a ) Math.floor( double a ) Math.rint( double a ) Math.pow( double x,double y ) Math.round( x ) Math.random() Math.max( a,b ) Math.min( a,b ) COLECCIONES. Descripción Para int, long, float y double. Devuelve el seno del ángulo a en radianes. Devuelve el coseno del ángulo a en radianes. Devuelve la tangente del ángulo a en radianes. Devuelve el ángulo cuyo seno es r. Devuelve el ángulo cuyo coseno es r. Devuelve el ángulo cuya tangente es r. Devuelve el ángulo cuya tangente es a/b. Devuelve e elevado a x. Devuelve el logaritmo natural de x. Devuelve la raíz cuadrada de x. Devuelve el número completo más pequeño mayor o igual que a. Devuelve el número completo más grande menor o igual que a. Devuelve el valor double truncado de a Devuelve x elevado a y. Para double y float. Devuelve un double entre 0 y 1. Para int, long, float y double. Para int, long, float y double. Estructuras estáticas de datos y estructuras dinámicas En prácticamente todos los lenguajes de computación existen estructuras para almacenar colecciones de datos. Esto es una serie de datos agrupados a los que se puede hacer referencia con un único nombre. Ejemplo de ello son los arrays. El problema del uso de los arrays es que es una estructura estática, esto significa que se debe saber el número de elementos que formarán parte de esa Formando Emprendedores De Calidad Para Un Mundo Empresarial 101 Instituto de Educación Superior “San Ignacio de Monterrico” colección a priori, es decir en tiempo de compilación hay que decidir el tamaño de un array. Las estructuras dinámicas de datos tienen la ventaja de que sus integrantes se deciden en tiempo de ejecución y que el número de elementos es ilimitado. Estas estructuras dinámicas son clásicas en la programación y son las colas, pilas, listas enlazadas, árboles, grafos, etc. En muchos lenguajes se implementan mediante punteros; como Java no posee punteros se crearon clases especiales para implementar estas funciones. En Java desde la primera versión se incluyeron las clases: vector, Stack, Hashtable, BitSet y la interfaz Enumeration. En Java 2 se modificó este funcionamiento y se potenció la creación de estas clases. Trabajo con Colecciones Cuando se necesitan características más sofisticadas para almacenar objetos, que las que proporciona un simple array, Java pone a disposición las clases colección: Vector, BitSet, Stack y Hashtable. Entre otras características, las clases colección se redimensionan automáticamente, por lo que se puede colocar en ellas cualquier número de objetos, sin necesidad de tener que ir controlando continuamente en el programa la longitud de la colección. La gran desventaja del uso de las colecciones en Java es que se pierde la información de tipo cuando se coloca un objeto en una colección. Esto ocurre porque cuando se escribió la colección, el programador de esa colección no tenía ni idea del tipo de datos específicos que se iban a colocar en ella, y teniendo en mente el hacer una herramienta lo más general posible, se hizo que manejase directamente objetos de tipo Object, que es el objeto raíz de todas las clases en Java. La solución es perfecta, excepto por dos razones: 1. Como la información de tipo se pierde al colocar un objeto en la colección, cualquier tipo de objeto se va a poder colar en ella, es decir, si la colección está destinada a contener animales mamíferos, nada impide que se pueda colar un coche en ella. Formando Emprendedores De Calidad Para Un Mundo Empresarial 102 Instituto de Educación Superior “San Ignacio de Monterrico” 2. Por la misma razón de la pérdida de tipo, la única cosa que sabe la colección es que maneja un Object. Por ello, hay que colocar siempre un moldeo al tipo adecuado antes de utilizar cualquier objeto contenido en una colección. La verdad es que no todo es tan negro, Java no permite que se haga uso inadecuado de los objetos que se colocan en una colección. Si se introduce un coche en una colección de animales mamíferos, al intentar extraer el coche se obtendrá una excepción. Y del mismo modo, si se intenta colocar un moldeo al coche que se está sacando de la colección para convertirlo en animal mamífero, también se obtendrá una excepción en tiempo de ejecución. Ejemplo # import java.util.*; class Coche { private int numCoche; Coche( int i ) { numCoche = i; } void print() { System.out.println( "Coche #"+numCoche ); } } class Barco { private int numBarco; Barco( int i ) { numBarco = i; } void print() { System.out.println( "Barco #"+numBarco ); } Formando Emprendedores De Calidad Para Un Mundo Empresarial 103 Instituto de Educación Superior “San Ignacio de Monterrico” } public class java411 { public static void main( String args[] ) { Vector coches = new Vector(); for( int i=0; i < 7; i++ ) coches.addElement( new Coche( i ) ); //No hay ningun problema en añadir un barco a los coches coches.addElement( new Barco( 7 ) ); for( int i=0; i < coches.size(); i++ ) (( Coche )coches.elementAt( i ) ).print(); //El barco solamente es detectado en tiempo de ejecucion } } Como se puede observar, el uso de un Vector es muy sencillo: se crea uno, se colocan elementos en él con el método addElement() y se recuperan con el método elementAt(). Vector tiene el método size() que permite conocer cuántos elementos contiene, para evitar el acceso a elementos fuera de los límites del Vector y obtener una excepción. Las clases Coche y Barco son distintas, no tienen nada en común excepto que ambas son Object. Si no se indica explícitamente de la clase que se está heredando, automáticamente se hereda de Object. La clase Vector maneja elementos de tipo Object, así que no solamente es posible colocar en ella objetos Coche utilizando el método addElement(), sino que también se pueden colocar elementos de tipo Barco sin que haya ningún problema ni en tiempo de compilación ni a la hora de ejecutar el programa. Cuando se recupere un objeto que se supone es un Coche utilizando el método elementAt() de la clase Vector, hay que colocar un moldeo para convertir el objeto Object en el Coche que se espera, luego hay que colocar toda la expresión entre paréntesis para forzar la evaluación del moldeo antes de llamar al método print() de la clase Coche, sino habrá un error de sintaxis. Posteriormente, ya en tiempo de ejecución, cuando se Formando Emprendedores De Calidad Para Un Mundo Empresarial 104 Instituto de Educación Superior “San Ignacio de Monterrico” intente moldear un objeto Barco a un Coche, se generará una excepción, tal como se puede comprobar en las siguientes líneas, que reproducen la salida de la ejecución del ejemplo: %java java411 Coche #0 Coche #1 Coche #2 Coche #3 Coche #4 Coche #5 Coche #6 java.lang.ClassCastException: Barco at java411.main(java411.java:54) Lo cierto es que esto es un fastidio, porque puede ser la fuente de errores que son muy difíciles de encontrar. Si en una parte, o en varias partes, del programa se insertan elementos en la colección, y se descubre en otra parte diferente del programa que se genera una excepción es porque hay algún elemento erróneo en la colección, así que hay que buscar el sitio donde se ha insertado el elemento de la discordia, lo cual puede llevar a intensas sesiones de depuración. Así que, para enredar al principio, es mejor empezar con clases estandarizadas en vez de aventurarse en otras más complicadas, a pesar de que estén menos optimizadas. Jerarquia de Colecciones Formando Emprendedores De Calidad Para Un Mundo Empresarial 105 Instituto de Educación Superior “San Ignacio de Monterrico” Enumeraciones En cualquier clase de colección, debe haber una forma de meter cosas y otra de sacarlas; después de todo, la principal finalidad de una colección es almacenar cosas. En un Vector, el método addElement() es la manera en que se colocan objetos dentro de la colección y llamando al método elementAt() es cómo se sacan. Vector es muy flexible, se puede seleccionar cualquier cosa en cualquier momento y seleccionar múltiples elementos utilizando diferentes índices. Si se quiere empezar a pensar desde un nivel más alto, se presenta un inconveniente: la necesidad de saber el tipo exacto de la colección para utilizarla. Esto no parece que sea malo en principio, pero si se empieza implementando un Vector a la hora de desarrollar el programa, y posteriormente se decide cambiarlo a List, por eficiencia, entonces sí es problemático. El concepto de enumerador, o iterador, que es su nombre más común en C++ y OOP, puede utilizarse para alcanzar el nivel de abstracción que se necesita en este caso. Es un objeto cuya misión consiste en moverse a través de una secuencia de objetos y seleccionar aquellos objetos adecuados sin que el Formando Emprendedores De Calidad Para Un Mundo Empresarial 106 Instituto de Educación Superior “San Ignacio de Monterrico” programador cliente tenga que conocer la estructura de la secuencia. Además, un iterador es normalmente un objeto ligero, lightweight, es decir, que consumen muy pocos recursos, por lo que hay ocasiones en que presentan ciertas restricciones; por ejemplo, algunos iteradores solamente se puede mover en una dirección. La Enumeration en Java es un ejemplo de un iterador con esas características, y las cosas que se pueden hacer son: o Crear una colección para manejar una Enumeration utilizando el método elements(). Esta Enumeration estará lista para devolver el primer elemento en la secuencia cuando se llame por primera vez al método nextElement(). o Obtener el siguiente elemento en la secuencia a través del método nextElement(). o Ver si hay más elementos en la secuencia con el método hasMoreElements(). Y esto es todo. No obstante, a pesar de su simplicidad, alberga bastante poder. Para ver cómo funciona, el ejemplo java412.java, es la modificación de anterior, en que se utilizaba el método elementAt() para seleccionar cada uno de los elementos. Ahora se utiliza una enumeración para el mismo propósito, y el único código interesante de este nuevo ejemplo es el cambio de las líneas del ejemplo original for( int i=0; i < coches.size(); i++ ) (( Coche )coches.elementAt( i ) ).print(); Por estas otras en que se utiliza la enumeración para recorrer la secuencia de objetos while( e.hasMoreElements() ) (( Coche )e.nextElement()).print(); Con la Enumeration no hay que preocuparse del número de elementos que contenga la colección, ya que del control sobre ellos se encargan los métodos hasMoreElements() y nextElement(). Formando Emprendedores De Calidad Para Un Mundo Empresarial 107 Instituto de Educación Superior “San Ignacio de Monterrico” Tipos de Colecciones Con el JDK 1.0 y 1.1 se proporcionaban librerías de colecciones muy básicas, aunque suficientes para la mayoría de los proyectos. En el JDK 1.2 en adelante ya se amplía esto y, además, las anteriores colecciones han sufrido un profundo rediseño. A continuación se verán cada una de ellas por separado para dar una idea del potencial que se ha incorporado a Java. Vector El Vector es muy simple y fácil de utilizar. Aunque los métodos más habituales en su manipulación son addElement() para insertar elementos en el Vector, elementAt() para recuperarlos y elements() para obtener una Enumeration con el número de elementos del Vector, lo cierto es que hay más métodos, pero no es el momento de relacionarlos todos, así que, al igual que sucede con todas las librerías de Java, se remite al lector a que consulte la documentación electrónica que proporciona Javasoft, para conocer los demás métodos que componen esta clase. Las colecciones estándar de Java contienen el método toString(), que permite obtener una representación en forma de String de sí mismas, incluyendo los objetos que contienen. Dentro de Vector, por ejemplo, toString() va saltando a través de los elementos del Vector y llama al método toString() para cada uno de esos elementos. En caso, por poner un ejemplo, de querer imprimir la dirección de la clase, parecería lógico referirse a ella simplemente como this (los programadores C++ estarán muy inclinados a esta posibilidad), así que tendríamos el código que muestra el ejemplo java413.java y que se reproduce en las siguientes líneas. import java.util.*; public class java413 { public String toString() { return( "Direccion del objeto: "+this+"\n" ); } Formando Emprendedores De Calidad Para Un Mundo Empresarial 108 Instituto de Educación Superior “San Ignacio de Monterrico” public static void main( String args[] ) { Vector v = new Vector(); for( int i=0; i < 10; i++ ) v.addElement( new java413() ); System.out.println( v ); } } El ejemplo no puede ser más sencillo, simplemente crea un objeto de tipo java413 y lo imprime; sin embargo, a la hora de ejecutar el programa lo que se obtiene es una secuencia infinita de excepciones. Lo que está pasando es que cuando se le indica al compilador: "Direccion del objeto: "+this El compilador ve un String seguido del operador + y otra cosa que no es un String, así que intenta convertir this en un String. La conversión la realiza llamando al método toString() que genera una llamada recursiva, llegando a llenarse la pila. Si realmente se quiere imprimir la dirección del objeto en este caso, la solución pasa por llamar al método toString() de la clase Object. Así, si en vez de this se coloca super.toString(), el ejemplo funcionará. En otros casos, este método también funcionará siempre que se esté heredando directamente de Object o, aunque no sea así, siempre que ninguna clase padre haya sobreescrito el método toString(). BitSet Se llama así lo que en realidad es un Vector de bits. Lo que ocurre es que está optimizado para uso de bits. Bueno, optimizado en cuanto a tamaño, porque en lo que respecta al tiempo de acceso a los elementos, es bastante más lento que el acceso a un array de elementos del mismo tipo básico. Formando Emprendedores De Calidad Para Un Mundo Empresarial 109 Instituto de Educación Superior “San Ignacio de Monterrico” Además, el tamaño mínimo de un BitSet es de 64 bits. Es decir, que si se está almacenando cualquier otra cosa menor, por ejemplo de 8 bits, se estará desperdiciando espacio. En un Vector normal, la colección se expande cuando se añaden más elementos. En el BitSet ocurre los mismo pero ordenadamente. El ejemplo java414.java, muestra el uso de esta colección. Se utiliza el generador de números aleatorios para obtener un byte, un short y un int, que son convertidos a su patrón de bits e incorporados al BitSet. Stack Un Stack es una Pila, o una colección de tipo LIFO (last-in, first-out). Es decir, lo último que se coloque en la pila será lo primero que se saque. Como en todas las colecciones de Java, los elementos que se introducen y sacan de la pila son Object, así que hay que tener cuidado con el moldeo a la hora de sacar alguno de ellos. Los diseñadores de Java, en vez de utilizar un Vector como bloque para crear un Stack, han hecho que Stack derive directamente de Vector, así que tiene todas las características de un Vector más alguna otra propia ya del Stack. El ejemplo siguiente, java415.java, es una demostración muy simple del uso de una Pila que consisten en leer cada una de las líneas de un array y colocarlas en un String. Cada línea en el array diasSemana se inserta en el Stack con push() y posteriormente se retira con pop(). Para ilustrar una afirmación anterior, también se utilizan métodos propios de Vector sobre el Stack. Esto es posible ya que en virtud de la herencia un Stack es un Vector, así que todas las operaciones que se realicen sobre un Vector también se podrán realizar sobre un Stack, como por ejemplo, elementAt(). Formando Emprendedores De Calidad Para Un Mundo Empresarial 110 Instituto de Educación Superior “San Ignacio de Monterrico” Hashtable Un Vector permite selecciones desde una colección de objetos utilizando un número, luego parece lógico pensar que hay números asociados a los objetos. Bien, entonces ¿qué es lo que sucede cuando se realizan selecciones utilizando otros criterios? Un Stack podría servir de ejemplo: su criterio de selección es "lo último que se haya colocado en el Stack". Si rizamos la idea de "selección desde una secuencia", nos encontramos con un mapa, un diccionario o un array asociativo. Conceptualmente, todo parece ser un vector, pero en lugar de acceder a los objetos a través de un número, en realidad se utiliza otro objeto. Esto nos lleva a utilizar claves y al procesado de claves en el programa. Este concepto se expresa en Java a través de la clase abstracta Dictionary. El interfaz para esta clase es muy simple: • size(), indica cuántos elementos contiene, • isEmpty(), es true si no hay ningún elemento, • put( Object clave,Object valor), añade un valor y lo asocia con una clave • get( Object clave ), obtiene el valor que corresponde a la clave que se indica • remove( Object clave ), elimina el par clave-valor de la lista • keys(), genera una Enumeration de todas las claves de la lista • elements(), genera una Enumeration de todos los valores de la lista Todo es lo que corresponde a un Diccionario (Dictionary), que no es excesivamente difícil de implementar. El ejemplo java416.java es una aproximación muy simple que utiliza dos Vectores, uno para las claves y otro para los valores que corresponden a esas claves. Formando Emprendedores De Calidad Para Un Mundo Empresarial 111 Instituto de Educación Superior “San Ignacio de Monterrico” import java.util.*; public class java416 extends Dictionary { private Vector claves = new Vector(); private Vector valores = new Vector(); public int size() { return( claves.size() ); } public boolean isEmpty() { return( claves.isEmpty() ); } public Object put( Object clave,Object valor ) { claves.addElement( clave ); valores.addElement( valor ); return( clave ); } public Object get( Object clave ) { int indice = claves.indexOf( clave ); // El metodo indexOf() devuelve -1 // si no encuentra la clave que se // esta buscando if( indice == -1 ) return( null ); return( valores.elementAt( indice ) ); } public Object remove(Object clave) { int indice = claves.indexOf( clave ); if( indice == -1 ) return( null ); Formando Emprendedores De Calidad Para Un Mundo Empresarial 112 Instituto de Educación Superior “San Ignacio de Monterrico” claves.removeElementAt( indice ); Object valorRetorno = valores.elementAt( indice ); valores.removeElementAt( indice ); return( valorRetorno ); } public Enumeration keys() { return( claves.elements() ); } public Enumeration elements() { return( valores.elements() ); } // Ahora es cuando se prueba el ejemplo public static void main( String args[] ) { java416 ej = new java416(); for( char c='a'; c <= 'z'; c++ ) ej.put( String.valueOf( c ),String.valueOf( c ).toUpperCase() ) ; char[] vocales = { 'a','e','i','o','u' }; for( int i=0; i < vocales.length; i++ ) System.out.println( "Mayusculas: " + ej.get( String.valueOf( vocales[i] ) ) ); } } La primera cosa interesante que se puede observar en la definición de java416 es que extiende a Dictionary. Esto significa que java416 es un tipo de Diccionario, con lo cual se pueden realizar las mismas peticiones y llamar a los mismos métodos que a un Diccionario. A la hora de construirse un Diccionario propio todo lo que se necesita es rellenar todos Formando Emprendedores De Calidad Para Un Mundo Empresarial 113 Instituto de Educación Superior “San Ignacio de Monterrico” los métodos que hay en Dictionary. Se deben sobreescribir todos ellos, excepto el constructor, porque todos son abstractos. Los Vectores claves y valores están relacionados a través de un número índice común. Es decir, si se llama al método put() con la clave "león" y el valor "rugido" en la asociación de animales con el sonido que producen, y ya hay 100 elementos en la clase java416, entonces "león" será el elemento 101 de claves y "rugido" será el elemento 101 de valores. Y cuando se pasa al método get() como parámetro "león", genera el número índice con claves.indexOf(), y luego utiliza este índice para obtener el valor asociado en el vector valores. Para mostrar el funcionamiento, en main() se utiliza algo tan simple como mapear las letras minúsculas y mayúsculas, que aunque se pueda hacer de otras formas más eficientes, sí sirve para mostrar el funcionamiento de la clase, que es lo que se pretende por ahora. La librería estándar de Java solamente incorpora una implementación de un Dictionary, la Hashtable. Esta Hashtable tiene el mismo interfaz básico que la clase del ejemplo anterior java416, ya que ambas heredan de Dictionary, pero difiere en algo muy importante: la eficiencia. Si en un Diccionario se realiza un get() para obtener un valor, se puede observar que la búsqueda es bastante lenta a través del vector de claves. Aquí es donde la Hashtable acelera el proceso, ya que en vez de realizar la tediosa búsqueda línea a línea a través del vector de claves, utiliza un valor especial llamado código hash. El código hash es una forma de conseguir información sobre el objeto en cuestión y convertirlo en un int relativamente único para ese objeto. Todos los objetos tienen un código hash y hashCode() es un método de la clase Object. Una Hashtable coge el hashCode() del objeto y lo utiliza para cazar rápidamente la clave. El resultado es una impresionante reducción del tiempo de búsqueda. La tabla Hash es un Diccionario muy rápido y que un Diccionario es una herramienta muy útil. Formando Emprendedores De Calidad Para Un Mundo Empresarial 114 Instituto de Educación Superior “San Ignacio de Monterrico” Para ver el funcionamiento de la tabla Hash está el ejemplo java417.java, que intenta comprobar la aleatoriedad del método Math.random(). Idealmente, debería producir una distribución perfecta de números aleatorios, pero para poder comprobarlo sería necesario generar una buena cantidad de números aleatorios y comprobar los rangos en que caen. Una Hashtable es perfecta para este propósito al asociar objetos con objetos, en este caso, los valores producidos por el método Math.random() con el número de veces en que aparecen esos valores. import java.util.*; class Contador { int i = 1; public String toString() { return( Integer.toString( i ) ); } } class java417 { public static void main( String args[] ) { Hashtable ht = new Hashtable(); for( int i=0; i < 10000; i++ ) { // Genera un número cuasi-aleatorio entre 0 y 20 Integer r = new Integer( (int)( Math.random()*20 ) ); if( ht.containsKey( r ) ) ( (Contador)ht.get( r ) ).i++; else ht.put( r,new Contador() ); } System.out.println( ht ); } } Formando Emprendedores De Calidad Para Un Mundo Empresarial 115 Instituto de Educación Superior “San Ignacio de Monterrico” En el método main() del ejemplo, cada vez que se genera un número aleatorio, se convierte en objeto Integer para que pueda ser manejado por la tabla Hash, ya que no se pueden utilizar tipos básicos con una colección, porque solamente manejan objetos. El método containsKey() comprueba si la clave se encuentra ya en la colección. En caso afirmativo, el método get() obtiene el valor asociado a la clave, que es un objeto de tipo Contador. El valor i dentro del contador se incrementa para indicar que el número aleatorio ha aparecido una vez más. Si la clave no se encuentra en la colección, el método put() colocará el nuevo par clave-valor en la tabla Hash. Como Contador inicializa automáticamente su variable i a 1 en el momento de crearla, ya se indica que es la primera vez que aparece ese número aleatorio concreto. Para presentar los valores de la tabla Hash, simplemente se imprimen. El método toString() de Hashtable navega a través de los pares clave-valor y llama a método toString() de cada uno de ellos. El método toString() de Integer está predefinido, por lo que no hay ningún problema en llamar a toString() para Contador. Un ejemplo de ejecución del programa sería la salida que se muestra a continuación: %java java417 {19=526, 18=533, 17=460, 16=513, 15=521, 14=495, 13=512, 12=483, 11=488, 10=487, 9=514, 8=523, 7=497, 6=487, 5=489, 3=509, 2=503, 1=475, 0=505} Al lector le puede parecer superfluo el uso de la clase Contador, que parece que no hace nada que no haga ya la clase Integer. ¿Por qué no utilizar int o Integer? Pues bien, int no puede utilizarse porque como ya se ha indicado antes, las colecciones solamente manejan objetos, por ello están las clases que envuelven a esos tipos básicos y los convierten en objetos. Sin embargo, la única cosa que pueden hacer estas clases es inicializar los objetos a un valor determinado y leer ese valor. Es decir, no Formando Emprendedores De Calidad Para Un Mundo Empresarial 116 Instituto de Educación Superior “San Ignacio de Monterrico” hay modo alguno de cambiar el valor de un objeto correspondiente a un tipo básico, una vez que se ha creado. Esto hace que la clase Integer sea inútil para resolver el problema que plantea el ejemplo, así que la creación de la clase Contador es imprescindible. Quizás ahora que el lector sabe que no puede colocar objetos creados a partir de las clases correspondientes a tipos básicos en colecciones, estas clases tengan un poco menos de valor, pero... la vida es así, por un lado da y por otro quita... y Java no va a ser algo diferente. En el ejemplo se utiliza la clase Integer, que forma parte de la librería estándar de Java como clave para la tabla Hash, y funciona perfectamente porque tiene todo lo necesario para funcionar como clave. Pero un error muy común se presenta a la hora de crear clases propias para que funcionen como claves. Por ejemplo, supóngase que se quiere implementar un sistema de predicción del tiempo en base a objetos de tipo Oso y tipo Prediccion, para detectar cuando entra la primavera. Tal como se muestra en el ejemplo java418.java, la cosa parece muy sencilla, se crean las dos clases y se utiliza Oso como clave y Prediccion como valor. Cada Oso tendrá un número de identificación, por lo que sería factible buscar una Prediccion en la tabla Hash de la forma: "Dime la Prediccion asociada con el Oso número 3". La clase Prediccion contiene un booleano que es inicializado utilizando Math.random(), y una llamada al método toString() convierte el resultado en algo legible. En el método main(), se rellena una Hashtable con los Osos y sus Predicciones asociadas. Cuando la tabla Hash está completa, se imprime. Y ya se hace la consulta anterior sobre la tabla para buscar la Prediccion que corresponde al Oso número 3. Esto parece simple y suficiente, pero no funciona. El problema es que Oso deriva directamente de la clase raíz Object, que es lo que ocurre cuando no se especifica una clase base, que en última instancia se hereda de Object. Luego es el método hashCode() de Object el que se utiliza para generar el código hash para cada objeto que, por defecto, utiliza la dirección de ese objeto. Así, la primera instancia de Oso(3) no va a producir un código hash igual que producirá una segunda instancia de Formando Emprendedores De Calidad Para Un Mundo Empresarial 117 Instituto de Educación Superior “San Ignacio de Monterrico” Oso(3), con lo cual no se puede utilizar para obtener buenos resultados de la tabla. Se puede seguir pensando con filosofía ahorrativa y decir que todo lo que se necesita es sobreescribir el método hashCode() de la forma adecuada y ya está. Pero, esto tampoco va a funcionar hasta que se haga una cosa más: sobreescribir el método equals(), que también es parte de Object. Este es el método que utiliza la tabla Hash para determinar si la clave que se busca es igual a alguna de las claves que hay en la tabla. De nuevo, el método Object.equals() solamente compara direcciones de objetos, por lo que un Oso(3) probablemente no sea igual a otro Oso(3). Por lo tanto, a la hora de escribir clases propias que vayan a funcionar como clave en una Hastable, hay que sobreescribir los métodos hashCode() y equals(). El ejemplo java419.java ya se incorporan estas circunstancias. import java.util.*; // Si se crea una clase que utilice una clave en una Tabla Hash, es // imprescindible sobreescribir los metodos hashCode() y equals() // Utilizamos un oso para saber si está hibernando en su temporada de // invierno o si ya tine que despertarse porque le llega la primavera class Oso2 { int numero; Oso2( int n ) { numero = n; } public int hashCode() { return( numero ); } Formando Emprendedores De Calidad Para Un Mundo Empresarial 118 Instituto de Educación Superior “San Ignacio de Monterrico” public boolean equals( Object obj ) { if( (obj != null) && (obj instanceof Oso2) ) return( numero == ((Oso2)obj).numero ); else return( false ); } } // En función de la oscuridad, o claridad del día, pues intenta saber si // ya ha la primavera ha asomado a nuestras puertas class Prediccion { boolean oscuridad = Math.random() > 0.5; public String toString() { if( oscuridad ) return( "Seis semanas mas de Invierno!" ); else return( "Entrando en la Primavera!" ); } } public class java419 { public static void main(String args[]) { Hashtable ht = new Hashtable(); for( int i=0; i < 10; i++ ) ht.put( new Oso2( i ),new Prediccion() ); System.out.println( "ht = "+ht+"\n" ); System.out.println( "Comprobando la prediccion para el oso #3:"); Oso2 oso = new Oso2( 3 ); if( ht.containsKey( oso ) ) System.out.println( (Prediccion)ht.get( oso ) ); } } Formando Emprendedores De Calidad Para Un Mundo Empresarial 119 Instituto de Educación Superior “San Ignacio de Monterrico” El método hashCode() devuelve el número que corresponde a un Oso como un identificador, siendo el programador el responsable de que no haya dos números iguales. El método hashCode() no es necesario que devuelva un identificador, sino que eso es necesario porque equals() debe ser capaz de determinar estrictamente cuando dos objetos son equivalentes. El método equals() realiza dos comprobaciones adicionales, una para comprobar si el objeto es null, y, en caso de que no lo sea, comprobar que sea una instancia de Oso, para poder realizar las comparaciones, que se basan en los números asignados a cada objeto Oso. Cuando se ejecuta este nuevo programa, sí se produce la salida correcta. Hay muchas clases de la librería de Java que sobreescriben los métodos hashCode() y equals() basándose en el tipo de objetos que son capaces de crear. Las tablas Hash son utilizadas también por muchas clases de la librería estándar de Java, por ejemplo, para obtener las propiedades del sistema se usa la clase Properties que hereda directamente de Hashtable. Y además, contiene una segunda Hashtable en donde guarda las propiedades del sistema que se usan por defecto. Para el autor, las colecciones son una de las herramientas más poderosas que se pueden poner en manos de un programador. Por ello, las colecciones que incorporaba Java, adolecían de precariedad y de demasiada rapidez en su desarrollo. Por todo ello, para quien escribe esto ha sido una tremenda satisfacción comprobar las nuevas colecciones que incorpora el JDK 1.2, y ver que incluso las antiguas han sido rediseñadas. Probablemente, las colecciones, junto con la librería Swing, son las dos cosas más importantes que aporta la versión 1.2 del JDK, y ayudarán enormemente a llevar a Java a la primera línea de los lenguajes de programación. Hay cambios de diseño que hacen su uso más simple. Por ejemplo, muchos nombres son más cortos, más claros y más fáciles de entender; e Formando Emprendedores De Calidad Para Un Mundo Empresarial 120 Instituto de Educación Superior “San Ignacio de Monterrico” incluso algunos de ellos han sido cambiados totalmente para adaptarse a la terminología habitual. El rediseño también incluye la funcionalidad, pudiendo encontrar ahora listas enlazadas y colas. El diseño de una librería de colecciones es complicado y difícil. En C++, la Standard Template Library (STL) cubría la base con muchas clases diferentes. Desde luego, esto es mejor que cuando no hay nada, pero es difícil de trasladar a Java porque el resultado llevaría a tal cantidad de clases que podría ser muy confuso. En el otro extremo, hay librerías de colecciones que constan de una sola clase, Collection, que actúa como un Vector y una Hashtable al mismo tiempo. Los diseñadores de las nuevas colecciones han intentado mantener un difícil equilibrio: por un lado disponer de toda la funcionalidad que el programador espera de una buena librería de colecciones, y, por otro, que sea tan fácil de aprender y utilizar como la STL y otras librerías similares. El resultado puede parecer un poco extraño en ocasiones, pero, al contrario que en las librerías anteriores al JDK 1.2, no son decisiones accidentales, sino que están tomadas a conciencia en función de la complejidad. Es posible que se tarde un poco en sentirse cómodo con algunos de los aspectos de la librería, pero de seguro que el programador intentará adoptar rápidamente estos nuevos métodos. Hay que reconocer que Joshua Bloch de Sun, ha hecho un magnífico trabajo en el rediseño de esta librería. La nueva librería de colecciones parte de la premisa de almacenar objetos, y diferencia dos conceptos en base a ello: • Colección (Collection): un grupo de elementos individuales, siempre con alguna regla que se les puede aplicar. Una List almacenará objetos en una secuencia determinada y, un Set no permitirá elementos duplicados. • Mapa (Map): un grupo de parejas de objetos clave- valor, como la Hastable ya vista. En principio podría parecer que esto es una Collection de parejas, pero cuando se intenta Formando Emprendedores De Calidad Para Un Mundo Empresarial 121 Instituto de Educación Superior “San Ignacio de Monterrico” implementar, este diseño se vuelve confuso, por lo que resulta mucho más claro tomarlo como un concepto separado. Además, es conveniente consultar porciones de un Map creando una Collection que represente a esa porción; de este modo, un Map puede devolver un Set de sus claves, una List de sus valores, o una List de sus parejas clave-valor. Los Mapas, al igual que los arrays, se pueden expandir fácilmente en múltiples dimensiones sin la incorporación de nuevos conceptos: simplemente se monta un Map cuyos valores son Mapas, que a su vez pueden estar constituidos por Mapas, etc. Las Colecciones y los Mapas pueden ser implementados de muy diversas formas, en función de las necesidades concretas de programación, por lo que puede resultar útil el siguiente diagrama de herencia de las nuevas colecciones que utiliza la notación gráfica propugnada por la metodología OMT (Object Modeling Technique). El diagrama está hecho a partir de la versión beta del JDK 1.2, así que puede haber cosas cambiadas con respecto a la versión final. Quizás también, un primer vistazo puede abrumar al lector, pero a lo largo de la sección se comprobará que es bastante simple, porque solamente hay tres colecciones: Map, List y Set; y solamente dos o tres implementaciones de cada una de ellas. Las cajas punteadas representan interfaces y las sólidas representan clases normales, excepto aquellas en que el texto interior comienza por Abstract, que representan clases abstractas. Las flechas indican que una clase puede generar objetos de la clase a la que apunta; por ejemplo, cualquier Collection puede producir un Iterator, mientras que una List puede producir un ListIterator (al igual que un Iterator normal, ya que List hereda de Collection). Los interfaces que tienen que ver con el almacenamiento de datos son: Collection, Set, List y Map. Normalmente, un programador creará casi todo su código para entenderse con estos interfaces y solamente necesitará indicar específicamente el tipo de datos que se están usando en el momento de la creación. Por ejemplo, una Lista se puede crear de la siguiente forma: List lista = new LinkedList(); Formando Emprendedores De Calidad Para Un Mundo Empresarial 122 Instituto de Educación Superior “San Ignacio de Monterrico” Desde luego, también se puede decidir que lista sea una lista enlazada, en vez de una lista genérica, y precisar más el tipo de información de la lista. Lo bueno, y la intención, del uso de interfaces es que si ahora se decide cambiar la implementación de la lista, solamente es necesario cambiar el punto de creación, por ejemplo: List lista = new ArrayList(); el resto del código permanece invariable. En la jerarquía de clases, se pueden ver algunas clases abstractas que pueden confundir en un principio. Son simplemente herramientas que implementan parcialmente un interfaz. Si el programador quiere hacer su propio Set, por ejemplo, no tendría que empezar con el interfaz Set e implementar todos los métodos, sino que podría derivar directamente de AbstractSet y ya el trabajo para crear la nueva clase es mínimo. Sin embargo, la nueva librería de colecciones contiene suficiente funcionalidad para satisfacer casi cualquier necesidad, así que en este Tutorial se ignorarán las clases abstractas. Por lo tanto, a la hora de sacar provecho del diagrama es suficiente con lo que respecta a los interfaces y a las clases concretas. Lo normal será construir un objeto correspondiente a una clase concreta, moldearlo al correspondiente interfaz y ya usas ese interfaz en el resto del código. El ejemplo java420.java es muy simple y consiste en una colección de objetos String que se imprimen. import java.util.*; public class java420 { public static void main( String args[] ) { Collection c = new ArrayList(); for( int i=0; i < 10; i++ ) c.add( Integer.toString( i ) ); Iterator it = c.iterator(); while( it.hasNext() ) System.out.println( it.next() ); } } Formando Emprendedores De Calidad Para Un Mundo Empresarial 123 Instituto de Educación Superior “San Ignacio de Monterrico” Como las nuevas colecciones forman parte del paquete java.util, no es necesario importar ningún paquete adicional para utilizarlas. A continuación se comentan los trozos interesantes del código del ejemplo. La primera línea del método main() crea un objeto ArrayList y lo moldea a una Collection. Como este ejemplo solamente utiliza métodos de Collection, cualquier objeto de una clase derivada de Collection debería funcionar, pero se ha cogido un ArrayList porque es el caballo de batalla de las colecciones y viene a tomar el relevo al Vector. El método add(), como su nombre sugiere, coloca un nuevo elemento en la colección. Sin embargo, la documentación indica claramente que add() "asegura que la colección contiene el elemento indicado". Esto es para que un Set tenga significado, ya que solamente añadirá el elemento si no se encuentra en la colección. Con un ArrayList, o cualquier otra lista ordenada, add() significa siempre "colocarlo dentro". Todas las colecciones pueden producir un Iterator invocando al método iterator(). Un Iterator viene a ser equivalente a una Enumeration, a la cual reemplaza, excepto en los siguientes puntos: 1. Utiliza un nombre que está históricamente aceptado y es conocido en toda la literatura de programación orientada a objetos 2. Utiliza nombres de métodos más cortos que la Enumeration: hasNext() en vez de hasMoreElements(), o next() en lugar de nextElement() 3. Añade un nuevo método, remove(), que permite eliminar el último elemento producido por el Iterator. Solamente se puede llamar a remove() una vez por cada llamada a next() En el ejemplo se utiliza un Iterator para desplazarse por la colección e ir imprimiendo cada uno de sus elementos. Colecciones Formando Emprendedores De Calidad Para Un Mundo Empresarial 124 Instituto de Educación Superior “San Ignacio de Monterrico” A continuación se indican los métodos que están disponibles para las colecciones, es decir, lo que se puede hacer con un Set o una List, aunque las listas tengan funcionalidad añadida que ya se verá, y Map no hereda de Collection, así que se tratará aparte. boolean add( Object ) Asegura que la colección contiene el argumento. Devuelve false si no se puede añadir el argumento a la colección boolean addAll( Collection ) Añade todos los elementos que se pasan en el argumento. Devuelve true si es capaz de incorporar a la colección cualquiera de los elementos del argumento void clear() Elimina todos los elementos que componen la colección boolean contains( Object ) Verdadero si la colección contiene el argumento que se pasa como parámetro boolean isEmpty() Verdadero si la colección está vacía, no contiene elemento alguno Iterator iterator() Devuelve un Iterator que se puede utilizar para desplazamientos a través de los elementos que componen la colección boolean remove( Object ) Si el argumento está en la colección, se elimina una instancia de ese elemento y se devuelve true si se ha conseguido boolean removeAll( Collection ) Elimina todos los elementos que están contenidos en el argumento. Devuelve true si consigue eliminar cualquiera de ellos boolean retainAll( Collection ) Formando Emprendedores De Calidad Para Un Mundo Empresarial 125 Instituto de Educación Superior “San Ignacio de Monterrico” Mantiene solamente los elementos que están contenidos en el argumento, es lo que sería una intersección en la teoría de conjuntos. Devuelve verdadero en caso de que se produzca algún cambio int size() Devuelve el número de elementos que componen la colección Object[] toArray() Devuelve un array conteniendo todos los elementos que forman parte de la colección. Este es un método opcional, lo cual significa que no está implementado para una Collection determinada. Si no puede devolver el array, lanzará una excepción de tipo UnsupportedOperationException El siguiente ejemplo, java421.java, muestra un ejemplo de todos estos métodos. De nuevo, recordar que esto funcionaría con cualquier cosa que derive de Collection, y que se utiliza un ArrayList para mantener un común denominador solamente. El primer método proporciona una forma se rellenar la colección con datos de prueba, en esta caso enteros convertidos a cadenas. El segundo método será utilizado con bastante frecuencia a partir de ahora. Las dos versiones de nuevaColeccion() crean ArrayList conteniendo diferente conjunto de datos que devuelven como objetos Collection, está claro que no se utiliza ningún otro interfaz diferente de Collection. El método print() también se usará a menudo a partir de ahora, y lo que hace es moverse a través de la Colección utilizando un Iterator, que cualquier Collection puede generar, y funciona con Listas, Conjuntos y Mapas. El método main() se usa simplemente para llamar a los métodos de la Colección. Listas Hay varias implementaciones de List, siendo ArrayList la que debería ser la elección por defecto, en caso de no tener que utilizar las características que proporcionan las demás implementaciones. List (interfaz) Formando Emprendedores De Calidad Para Un Mundo Empresarial 126 Instituto de Educación Superior “San Ignacio de Monterrico” La ordenación es la característica más importante de una Lista, asegurando que los elementos siempre se mantendrán en una secuencia concreta. La Lista incorpora una serie de métodos a la Colección que permiten la inserción y borrar de elementos en medio de la Lista. Además, en la Lista Enlazada se puede generar un ListIterator para moverse a través de las lista en ambas direcciones. ArrayList Es una Lista volcada en un Array. Se debe utilizar en lugar de Vector como almacenamiento de objetos de propósito general. Permite un acceso aleatorio muy rápido a los elementos, pero realiza con bastante lentitud las operaciones de insertado y borrado de elementos en medio de la Lista. Se puede utilizar un ListIterator para moverse hacia atrás y hacia delante en la Lista, pero no para insertar y eliminar elementos. LinkedList Proporciona un óptimo acceso secuencial, permitiendo inserciones y borrado de elementos de en medio de la Lista muy rápidas. Sin embargo es bastante lento el acceso aleatorio, en comparación con la ArrayList. Dispone además de los métodos addLast(), getFirst(), getLast(), removeFirst() y removeLast(), que no están definidos en ningún interfaz o clase base y que permiten utilizar la Lista Enlazada como una Pila, una Cola o una Cola Doble. En el ejemplo java422.java, cubren gran parte de las acciones que se realizan en las Listas, como moverse con un Iterator, cambiar elementos, ver los efectos de la manipulación de la Lista y realizar operaciones sólo permitidas a las Listas Enlazadas. En testBasico() y moverIter() las llamadas se hacen simplemente para mostrar la sintaxis correcta, y aunque se recoge el valor devuelto, no se usa para nada. En otros casos, el valor devuelto no es capturado, porque no se utiliza normalmente. No obstante, el lector debe recurrir a la documentación de las clases para comprobar el uso de cualquier método antes de utilizarlo. Sets Formando Emprendedores De Calidad Para Un Mundo Empresarial 127 Instituto de Educación Superior “San Ignacio de Monterrico” Set tiene exactamente el mismo interfaz que Collection, y no hay ninguna funcionalidad extra, como en el caso de las Listas. Un Set es exactamente una Colección, pero tiene utilizada en un entorno determinado, que es ideal para el uso de la herencia o el polimorfismo. Un Set sólo permite que exista una instancia de cada objeto. A continuación se muestran las diferentes implementaciones de Set, debiendo utilizarse HashSet en general, a no ser que se necesiten las características proporcionadas por alguna de las otras implementaciones. Set (interfaz) Cada elemento que se añada a un Set debe ser único, ya que el otro caso no se añadirá porque el Set no permite almacenar elementos duplicados. Los elementos incorporados al Conjunto deben tener definido el método equals(), en aras de establecer comparaciones para eliminar duplicados. Set tiene el mismo interfaz que Collection, y no garantiza el orden en que se encuentren almacenados los objetos que contenga. HashSet Es la elección más habitual, excepto en Sets que sean muy pequeños. Debe tener definido el método hashCode(). ArraySet Un Set encajonado en un Array. Esto es útil para Sets muy pequeños, especialmente aquellos que son creados y destruidos con frecuencia. Para estos pequeños Sets, la creación e iteración consume muchos menos recursos que en el caso del HashSet. Sin embargo, el rendimiento es muy malo en el caso de Sets con gran cantidad de elementos. TreeSet Es un Set ordenado, almacenado en un árbol balanceado. En este caso es muy fácil extraer una secuencia ordenada a partir de un Set de este tipo. Formando Emprendedores De Calidad Para Un Mundo Empresarial 128 Instituto de Educación Superior “San Ignacio de Monterrico” El ejemplo java423.java, no muestra todo lo que se puede hacer con un Set, sino que como Set es una Collection, y las posibilidades de las Colecciones ya se han visto, pues el ejemplo se limita a mostrar aquellas cosas que son particulares de los Sets. import java.util.*; public class java423 { public static void testVisual( Set a ) { java421.fill( a ); java421.fill( a ); java421.fill( a ); java421.print( a ); // No permite Duplicados! // Se añade otro Set al anterior a.addAll( a ); a.add( "uno" ); a.add( "uno" ); a.add( "uno" ); java421.print( a ); // Buscamos ese elemento System.out.println( "a.contains(\"uno\"): "+a.contains( "uno" ) ); } public static void main( String args[] ) { testVisual( new HashSet() ); testVisual( new ArraySet() ); } } Aunque se añaden valores duplicados al Set, a la hora de imprimirlos, se puede observar que solamente se acepta una instancia de cada valor. Cuando se ejecuta el ejemplo, se observa también que el orden que Formando Emprendedores De Calidad Para Un Mundo Empresarial 129 Instituto de Educación Superior “San Ignacio de Monterrico” mantiene el HashSet es diferente del que presenta el ArraySet, ya que cada uno tiene una forma de almacenar los elementos para la recuperación posterior. El ArraySet mantiene los elementos ordenados, mientras que el HashSet utiliza sus propias funciones para que las búsquedas sean muy rápidas. Cuando el lector cree sus propios tipos de estructuras de datos, deberá prestar atención porque un Set necesita alguna forma de poder mantener el orden de los elementos que lo integran, como se muestra en el ejemplo siguiente, java424.java. Las definiciones de los métodos equals() y hashCode() son semejantes a las de ejemplos anteriores. Se debe definir equals() en ambos casos, mientras que hashCode() solamente es necesario si la clase corresponde a un HashSet, que debería ser la primera elección a la hora de implementar un Set. Mapas Los Mapas almacenan información en base a parejas de valores, formados por una clave y el valor que corresponde a esa clave. Map (interfaz) Mantiene las asociaciones de pares clave-valor, de forma que se puede encontrar cualquier valor a partir de la clave correspondiente. HashMap Es una implementación basada en una tabla hash. Proporciona un rendimiento muy constante a la hora de insertar y localizar cualquier pareja de valores; aunque este rendimiento se puede ajustar a través de los constructores que permite fijar la capacidad y el factor de carga de la tabla hash. ArrayMap Es un Mapa circunscrito en un Array. Proporciona un control muy preciso sobre el orden de iteración. Está diseñado para su utilización con Mapas muy pequeños, especialmente con aquellos que se crean y destruyen muy Formando Emprendedores De Calidad Para Un Mundo Empresarial 130 Instituto de Educación Superior “San Ignacio de Monterrico” frecuentemente. En este caso de Mapas muy pequeños, la creación e iteración consume muy pocos recursos del sistema, y muchos menos que el HashMap. El rendimiento cae estrepitosamente cuando se intentan manejar Mapas grandes. TreeMap Es una implementación basada en un árbol balanceado. Cuando se observan las claves o los valores, se comprueba que están colocados en un orden concreto, determinado por Comparable o Comparator, que ya se verán. Lo importante de un TreeMap es que se pueden recuperar los elementos en un determinado orden. TreeMap es el único mapa que define el método subMap(), que permite recuperar una parte del árbol solamente. El ejemplo java425.java contiene dos grupos de datos de prueba y un método rellena() que permite llenar cualquier mapa con cualquier array de dos dimensiones de Objects. Los métodos printClaves(), printValores() y print() no son solamente unas cuantas utilidades, sino que demuestran como se pueden generar Colecciones que son vistas de un Mapa. El método keySet() genera un Set que contiene las claves que componen el Mapa; en este caso, es tratado como una Colección. Tratamiento similar se da a values(), que genera una List conteniendo todos los valores que se encuentran almacenados en el Mapa. Observar que las claves deben ser únicas, mientras que los valores pueden contener elementos duplicados. Debido a que las Colecciones son dependientes del Mapa, al representar solamente una vista concreta de parte de los datos del Mapa, cualquier cambio en una Colección se reflejará inmediatamente en el Mapa asociado. El método print() recoge el Iterator producido por entries() y lo utiliza para imprimir las parejas de elementos clave-valor. El resto del ejemplo proporciona ejemplos muy simples de cada una de las operaciones permitidas en un Mapa y prueba cada tipo de Mapa. Formando Emprendedores De Calidad Para Un Mundo Empresarial 131 Instituto de Educación Superior “San Ignacio de Monterrico” A la hora de crear Mapas propios, el lector debe tener en cuenta las mismas recomendaciones que anteriormente se proporcionaban en el caso de los Sets. CONECTANDOSE A BASE DE DATOS. JDBC Java Database Connectivity (JDBC) es una interfase de acceso a bases de datos estándar SQL que proporciona un acceso uniforme a una gran variedad de bases de datos relacionales. JDBC también proporciona una base común para la construcción de herramientas y utilidades de alto nivel. JDBC es un API de Java que permite al programador ejecutar instrucciones en lenguaje estándar de acceso a Bases de Datos, SQL (Structured Query Language, lenguaje estructurado de consultas), que es un lenguaje de muy alto nivel que permite crear, examinar, manipular y gestionar Bases de Datos relacionales. Para que una aplicación pueda hacer operaciones en una Base de Datos, ha de tener una conexión con ella, que se establece a través de un driver, que convierte el lenguaje de alto nivel a sentencias de Base de Datos. Es decir, las tres acciones principales que realizará JDBC son las de establecer la conexión a una base de datos, ya sea remota o no; enviar sentencias SQL a esa base de datos y, en tercer lugar, procesar los resultados obtenidos de la base de datos. El paquete JDK incluye JDBC a partir de la versión 1.1; El paquete java.sql que está incluido en la versión JDK 1.2 (conocido como el API JDBC 2.0) incluye muchas nuevas características no incluidas en el paquete java.sql que forma parte de la versión JDK 1.1 (referenciado como el API JDBC 1.0). Actualmente esta API se encuentra en la versión 3.0. Con el API JDBC 2.0, podremos hacer las siguientes cosas: Formando Emprendedores De Calidad Para Un Mundo Empresarial 132 Instituto de Educación Superior “San Ignacio de Monterrico” • Ir hacia adelante o hacia atrás en una hoja de resultados o movernos a un fila específica. • Hacer actualizaciones de las tablas de la base datos utilizando métodos Java en lugar de utilizar comandos SQL. • Enviar múltiples secuencias SQL a la base de datos como una unidad, o batch. • Utilizar los nuevos tipos de datos SQL3 como valores de columnas. DEFINICIÓN DE SQL El Lenguaje Estructurado de Consulta (SQL) es un conjunto especializado de órdenes de programación que permiten al programador (o usuario final) realizar las siguientes tareas: • Recuperar datos de una o más tablas de una o mas bases de datos. • Manipular datos de tablas insertando, eliminando o actualizando registros. • Obtener información de resumen acerca de los datos de las tablas, como totales, recuento de registros y valores mínimos, máximos y medios. • Crear, modificar o eliminar las tablas de una base de datos. Drivers JDBC Para usar JDBC con un sistema gestor de base de datos en particular, es necesario disponer del driver JDBC apropiado que haga de intermediario entre ésta y JDBC. JDBC es el API (Interfaz de Programación de la Aplicación) para la ejecución de sentencias SQL. Consiste en un conjunto de clases e interfases escritas en el lenguaje de programación Java. JDBC suministra un API estándar para los desarrolladores y hace posible escribir aplicaciones de base de datos usando un API puro Java. Usando JDBC es fácil enviar sentencias SQL virtualmente a cualquier sistema de base de datos. En otras palabras, con el API JDBC, no es necesario escribir un programa que acceda a una base de datos Sybase, otro para acceder a Oracle y otro para acceder a Informix. Un único programa escrito usando el API JDBC y el programa será capaz de enviar sentencias SQL a la base de Formando Emprendedores De Calidad Para Un Mundo Empresarial 133 Instituto de Educación Superior “San Ignacio de Monterrico” datos apropiada. Y, con una aplicación escrita en el lenguaje de programación Java, tampoco es necesario escribir diferentes aplicaciones para ejecutar en diferentes plataformas. La combinación de Java y JDBC permite al programador escribir una sola vez y ejecutarlo en cualquier entorno. Java, siendo robusto, seguro, fácil de usar, fácil de entender, y descargable automáticamente desde la red, es un lenguaje base excelente para aplicaciones de base de datos. JDBC expande las posibilidades de Java. Por ejemplo, con Java y JDBC API, es posible publicar una página web que contenga un applet que usa información obtenida de una base de datos remota. O una empresa puede usar JDBC para conectar a todos sus empleados (incluso si usan un conglomerado de máquinas Windows, Macintosh y UNIX) a una base de datos interna vía intranet. Con cada vez más y más programadores desarrollando en lenguaje Java, la necesidad de acceso fácil a base de datos desde Java continúa creciendo. ¿Qué hace JDBC? Simplemente JDBC hace posible estas tres cosas: • Establece una conexión con la base de datos. • Envía sentencias SQL • Procesa los resultados. El acceso a base de datos es siempre un punto a considerar por nuestras aplicaciones. Para esto en Java usamos Java Database Connectivity conocido por la abreviación JDBC. Cuando instalamos el JDK (Java Development Kit) automáticamente se instala esta API (JDBC) y con ella en forma muy sencilla podemos hacer consultas, mantenimientos y todo tipo de transacciones a una base de datos. Para la gente del mundo Windows, JDBC es para Java lo que ODBC es para Windows. Windows en general no sabe nada acerca de las bases de datos, pero define el estándar ODBC consistente en un conjunto de primitivas que cualquier driver o fuente ODBC debe ser capaz de entender y manipular. Los Formando Emprendedores De Calidad Para Un Mundo Empresarial 134 Instituto de Educación Superior “San Ignacio de Monterrico” programadores que a su vez deseen escribir programas para manejar bases de datos genéricas en Windows utilizan las llamadas ODBC. Con JDBC ocurre exactamente lo mismo: JDBC es una especificación de un conjunto de clases y métodos de operación que permiten a cualquier programa Java acceder a sistemas de bases de datos de forma homogénea. Lógicamente, al igual que ODBC, la aplicación de Java debe tener acceso a un driver JDBC adecuado. Este driver es el que implementa la funcionalidad de todas las clases de acceso a datos y proporciona la comunicación entre el API JDBC y la base de datos real. La necesidad de JDBC, a pesar de la existencia de ODBC, viene dada porque ODBC es un interfaz escrito en lenguaje C, que al no ser un lenguaje portable, haría que las aplicaciones Java también perdiesen la portabilidad. Y además, ODBC tiene el inconveniente de que se ha de instalar manualmente en cada máquina; al contrario que los drivers JDBC, que al estar escritos en Java son automáticamente instalables, portables y seguros. Toda la conectividad de bases de datos de Java se basa en sentencias SQL, por lo que se hace imprescindible un conocimiento adecuado de SQL para realizar cualquier clase de operación de bases de datos. Aunque, afortunadamente, casi Formando Emprendedores De Calidad Para Un Mundo Empresarial 135 Instituto de Educación Superior “San Ignacio de Monterrico” todos los entornos de desarrollo Java ofrecen componentes visuales que proporcionan una funcionalidad suficientemente potente sin necesidad de que sea necesario utilizar SQL, aunque para usar directamente el JDK se haga imprescindible. La especificación JDBC requiere que cualquier driver JDBC sea compatible con al menos el nivel «de entrada» de ANSI SQL 92 (ANSI SQL 92 Entry Level). ACCESO A BASE DE DATOS CON JDBC El API JDBC soporta dos modelos diferentes de acceso a Bases de Datos, los modelos de dos y tres capas. Modelo de dos capas Este modelo se basa en que la conexión entre la aplicación Java o el applet que se ejecuta en el navegador, se conectan directamente a la base de datos. Esto significa que el driver JDBC específico para conectarse con la base de datos, debe residir en el sistema local. La base de datos puede estar en cualquier otra máquina y se accede a ella mediante la red. Esta es la configuración de típica Cliente/Servidor: el programa cliente envía instrucciones SQL a la base de datos, ésta las procesa y envía los resultados de vuelta a la aplicación. Modelo de tres capas Formando Emprendedores De Calidad Para Un Mundo Empresarial 136 Instituto de Educación Superior “San Ignacio de Monterrico” En este modelo de acceso a las bases de datos, las instrucciones son enviadas a una capa intermedia entre Cliente y Servidor, que es la que se encarga de enviar las sentencias SQL a la base de datos y recoger el resultado desde la base de datos. En este caso el usuario no tiene contacto directo, ni a través de la red, con la máquina donde reside la base de datos. Este modelo presenta la ventaja de que el nivel intermedio mantiene en todo momento el control del tipo de operaciones que se realizan contra la base de datos, y además, está la ventaja adicional de que los drivers JDBC no tienen que residir en la máquina cliente, lo cual libera al usuario de la instalación de cualquier tipo de driver. Tipos de drivers Un driver JDBC puede pertenecer a una de cuatro categorías diferentes en cuanto a la forma de operar, entonces tenemos que considerar que existen 4 formas de usar JDBC para conexión con base de datos: • El puente JDBC-ODBC • Driver de Java parcialmente nativo • Driver JDBC de Java puro • Driver de protocolo de Java puro Formando Emprendedores De Calidad Para Un Mundo Empresarial 137 Instituto de Educación Superior “San Ignacio de Monterrico” El puente JDBC-ODBC Esta forma es la más sencilla y es la que usaremos inicialmente para comunicarnos con el gestor SQL Server. Para esto necesitamos del ODBC (Open Database Connectivity) de Microsoft, a través del cual crearemos un DSN (Data Source Name) que nos permitirá crear una cadena de conexión de información sobre la base de datos. El ODBC esta hecho en lenguaje C, mientras que JDBC en Java. El ODBC se comunica con la base de datos y el JDBC con el ODBC. La primera categoría de drivers es la utilizada por Sun inicialmente para popularizar JDBC y consiste en aprovechar todo lo existente, estableciendo un puente entre JDBC y ODBC. Este driver convierte todas las llamadas JDBC a llamadas ODBC y realiza la conversión correspondiente de los resultados. La ventaja de este driver, que se proporciona con el JDK, es que Java dispone de acceso inmediato a todas las fuentes posibles de bases de datos y no hay que hacer ninguna configuración adicional aparte de la ya existente. No obstante, tiene dos desventajas muy importantes; por un lado, la mayoría de los drivers ODBC a su vez convierten sus llamadas a llamadas a una librería nativa del fabricante DBMS, con lo cual la lentitud del driver JDBCODBC puede ser exasperante, al llevar dos capas adicionales que no añaden funcionalidad alguna; y por otra parte, el puente JDBC-ODBC requiere una instalación ODBC ya existente y configurada. Formando Emprendedores De Calidad Para Un Mundo Empresarial 138 Instituto de Educación Superior “San Ignacio de Monterrico” Lo anterior implica que para distribuir con seguridad una aplicación Java que use JDBC habría que limitarse en primer lugar a entornos Windows (donde está definido ODBC) y en segundo lugar, proporcionar los drivers ODBC adecuados y configurarlos correctamente. Esto hace que este tipo de drivers esté totalmente descartado en el caso de aplicaciones comerciales, e incluso en cualquier otro desarrollo, debe ser considerado como una solución transitoria, porque el desarrollo de drivers totalmente en Java hará innecesario el uso de estos puentes. Java/Binario (Driver de Java parcialmente nativo) Esta forma está integrada de controladores que se comunican con el servidor de base de datos en el protocolo nativo del servidor. Por ejemplo para el gestor DB2 necesitaríamos un driver nativo de DB2 de IBM, Para Informix necesitaríamos un driver nativo de Informix de Unix. Nuestro JDBC, hecho en Java se comunicaría con estos drivers. Este driver se salta la capa ODBC y habla directamente con la librería nativa del fabricante del sistema DBMS (como pudiera ser DB-Library para Microsoft SQL Server o CT-Lib para Sybase SQL Server). Este driver es un driver 100% Java pero aún así necesita la existencia de un código binario (la librería DBMS) en la máquina del cliente, con las limitaciones y problemas que esto implica. Driver JDBC-Net de Java puro (100% Java/Protocolo nativo) En esta forma los drivers están hechos en Java puro, pero sin embargo utilizan protocolos estándares, como por ejemplo HTTP, con servidor de base Formando Emprendedores De Calidad Para Un Mundo Empresarial 139 Instituto de Educación Superior “San Ignacio de Monterrico” de datos. El servidor traduce el protocolo de red. Para el caso de Windows, puede usar ODBC. Es un driver realizado completamente en Java que se comunica con el servidor DBMS utilizando el protocolo de red nativo del servidor. De esta forma, el driver no necesita intermediarios para hablar con el servidor y convierte todas las peticiones JDBC en peticiones de red contra el servidor. La ventaja de este tipo de driver es que es una solución 100% Java y, por lo tanto, independiente de la máquina en la que se va a ejecutar el programa. Igualmente, dependiendo de la forma en que esté programado el driver, puede no necesitar ninguna clase de configuración por parte del usuario. La única desventaja de este tipo de drivers es que el cliente está ligado a un servidor DBMS concreto, ya que el protocolo de red que utiliza MS SQL Server por ejemplo no tiene nada que ver con el utilizado por DB2, PostGres u Oracle. La mayoría de los fabricantes de bases de datos han incorporado a sus propios drivers JDBC del segundo o tercer tipo, con la ventaja de que no suponen un coste adicional. Driver de protocolo de Java puro (100% Java/Protocolo independiente) En esta última forma, conformada por drivers de java puro, la comunicación es a través de un protocolo específico para la marca de base de datos que se usa. Este tipo de conexión es más eficiente que usar ODBC, pero que requiere un costo monetario en la compra de estos tipos de drivers. Formando Emprendedores De Calidad Para Un Mundo Empresarial 140 Instituto de Educación Superior “San Ignacio de Monterrico” Esta es la opción más flexible, se trata de un driver 100% Java / Protocolo independiente, que requiere la presencia de un intermediario en el servidor. En este caso, el driver JDBC hace las peticiones de datos al intermediario en un protocolo de red independiente del servidor DBMS. El intermediario a su vez, que está ubicado en el lado del servidor, convierte las peticiones JDBC en peticiones nativas del sistema DBMS. La ventaja de este método es inmediata: el programa que se ejecuta en el cliente, y aparte de las ventajas de los drivers 100% Java, también presenta la independencia respecto al sistema de bases de datos que se encuentra en el servidor. De esta forma, si una empresa distribuye una aplicación Java para que sus usuarios puedan acceder a su servidor MS SQL y posteriormente decide cambiar el servidor por Oracle, PostGres o DB2, no necesita volver a distribuir la aplicación, sino que únicamente debe reconfigurar la aplicación residente en el servidor que se encarga de transformar las peticiones de red en peticiones nativas. La única desventaja de este tipo de drivers es que la aplicación intermediaria es una aplicación independiente que suele tener un coste adicional por servidor físico, que hay que añadir al coste del servidor de bases de datos. Aproximación a JDBC JDBC define ocho interfaces para operaciones con bases de datos, de las que se derivan las clases correspondientes. La figura siguiente, en formato OMT, con nomenclatura UML, muestra la interrelación entre estas clases según el modelo de objetos de la especificación de JDBC. Formando Emprendedores De Calidad Para Un Mundo Empresarial 141 Instituto de Educación Superior “San Ignacio de Monterrico” La clase que se encarga de cargar inicialmente todos los drivers JDBC disponibles es DriverManager. Una aplicación puede utilizar DriverManager para obtener un objeto de tipo conexión, Connection, con una base de datos. La conexión se especifica siguiendo una sintaxis basada en la especificación más amplia de los URL, de la forma jdbc:subprotocolo//servidor:puerto/base de datos Por ejemplo, si se utiliza mSQL el nombre del subprotocolo será msql. En algunas ocasiones es necesario identificar aún más el protocolo. Por ejemplo, si se usa el puente JDBC-ODBC no es suficiente con jdbc:odbc, ya que pueden existir múltiples drivers ODBC, y en este caso, hay que especificar aún más, mediante jdbc:odbc:fuente de datos. Una vez que se tiene un objeto de tipo Connection, se pueden crear sentencias, statements, ejecutables. Cada una de estas sentencias puede devolver cero o más resultados, que se devuelven como objetos de tipo ResultSet. Formando Emprendedores De Calidad Para Un Mundo Empresarial 142 Instituto de Educación Superior “San Ignacio de Monterrico” Y la tabla siguiente muestra la misma lista de clases e interfaces junto con una breve descripción. Clase/Interface Descripción Driver Permite conectarse a una base de datos: cada gestor de base de datos requiere un driver distinto DriverManager Permite gestionar todos los drivers instalados en el sistema DriverPropertyInfo Proporciona diversa información acerca de un driver Connection Representa una conexión con una base de datos. Una aplicación puede tener más de una conexión a más de una base de datos DatabaseMetadata Proporciona información acerca de una Base de Datos, como las tablas que contiene, etc. Statement Permite ejecutar sentencias SQL sin parámetros PreparedStatement Permite ejecutar sentencias SQL con parámetros de entrada/TD> CallableStatement Permite ejecutar sentencias SQL con parámetros de entrada y salida, típicamente procedimientos almacenados ResultSet Contiene las filas o registros obtenidos al ejecutar un SELECT ResultSetMetadata Permite obtener información sobre un ResultSet, como el número de columnas, sus nombres, etc. La primera aplicación que se va a crear simplemente crea una tabla en el servidor, utilizando para ello el puente JDBC-ODBC, siendo la fuente de datos un servidor SQL Server. Si el lector desea utilizar otra fuente ODBC, no tiene más que cambiar los parámetros de getConnection() en el código fuente. El establecimiento de la conexión es, como se puede es fácil suponer, la parte que mayores problemas puede dar en una aplicación de este tipo. Si algo no funciona, cosa más que probable en los primeros intentos, es muy recomendable activar la traza de llamadas ODBC desde el panel de control. De esta forma se puede ver lo que está haciendo exactamente el driver JDBC y por qué motivo no se está estableciendo la conexión. El siguiente diagrama relaciona las cuatro clases principales que va a usar cualquier programa Java con JDBC, y representa el esqueleto de cualquiera de los programas que se desarrollan para atacar a bases de datos. Formando Emprendedores De Calidad Para Un Mundo Empresarial 143 Instituto de Educación Superior “San Ignacio de Monterrico” La aplicación siguiente es un ejemplo en donde se aplica el esquema anterior, se trata de instalación java2101.java, crea una tabla y rellena algunos datos iniciales. import java.sql.*; class java2101 { static public void main( String[] args ) { Connection conexion; Statement sentencia; ResultSet resultado; System.out.println( "Iniciando programa." ); // Se carga el driver JDBC-ODBC try { Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" ); } catch( Exception e ) { System.out.println( "No se pudo cargar el puente JDBC-ODBC." ); return; } try { Formando Emprendedores De Calidad Para Un Mundo Empresarial 144 Instituto de Educación Superior “San Ignacio de Monterrico” // Se establece la conexión con la base de datos conexion = DriverManager.getConnection( "jdbc:odbc:Tutorial","","" ); sentencia = conexion.createStatement(); try { // Se elimina la tabla en caso de que ya existiese sentencia.execute( "DROP TABLE AGENDA" ); } catch( SQLException e ) {}; // Esto es código SQL sentencia.execute( "CREATE TABLE AMIGOS ("+ " NOMBRE VARCHAR(15) NOT NULL, " + " APELLIDOS VARCHAR(30) NOT NULL, " + " CUMPLE DATETIME) " ); sentencia.execute( "INSERT INTO AMIGOS " + "VALUES('JOSE','GONZALEZ','03/15/1973')" ); sentencia.execute( "INSERT INTO AMIGOS " + "VALUES('PEDRO','GOMEZ','08/15/1961')" ); sentencia.execute( "INSERT INTO AMIGOS " + "VALUES('GONZALO','PEREZ', NULL)" ); } catch( Exception e ) { System.out.println( e ); return; } System.out.println( "Creacion finalizada." ); } } Las partes más interesantes del código son las que se van a revisar a continuación, profundizando en cada uno de los pasos. Lo primero que se hace es importar toda la funcionalidad de JDBC, a través de la primera sentencia ejecutable del programa. import java.sql.*; Las siguientes líneas son las que cargan el puente JDBC-ODBC, mediante el método forName() de la clase Class. try { Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" ); } catch( Exception e ) { System.out.println( "No se pudo cargar el puente JDBC-ODBC." ); return; } Formando Emprendedores De Calidad Para Un Mundo Empresarial 145 Instituto de Educación Superior “San Ignacio de Monterrico” En teoría esto no es necesario, ya que DriverManager se encarga de leer todos los drivers JDBC compatibles, pero no siempre ocurre así, por lo que es mejor asegurarse. El método forName() localiza, lee y enlaza dinámicamente una clase determinada. Para drivers JDBC, la sintaxis que JavaSoft recomienda de forName() es nombreEmpresa.nombreBaseDatos.nombreDriver, y el driver deberá estar ubicado en el directorio nombreEmpresa\nombreBaseDatos\nombreDriver.class a partir del directorio indicado por la variable de entorno CLASSPATH. En este caso se indica que el puente JDBC-ODBC que se desea leer es precisamente el de Sun. Si por cualquier motivo no es posible conseguir cargar JdbcOdbcDriver.class, se intercepta la excepción y se sale del programa. En este momento es la hora de echar mano de la información que puedan proporcionar las trazas ODBC. La carga del driver también se puede especificar desde la línea de comandos al lanzar la aplicación: java -Djdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver Exprograma A continuación, se solicita a DriverManager que proporcione una conexión para una fuente de datos ODBC. El parámetro jdbc:odbc:Tutorial especifica que la intención es acceder a la fuente de datos con nombre Tutorial, Data Source Name o DSN, en la terminología ODBC. conexion = DriverManager.getConnection("jdbc:odbc:Tutorial","","" ); El segundo y tercer parámetro son el nombre del usuario y la clave con la cual se intentará la conexión. En este caso el acceso es libre, para acceder como administrador del sistema en el caso de un servidor MS SQL se usa la cuenta sa o system administrator, cuya cuenta de acceso no tiene clave definida; en caso de acceder a un servidor MS Access, la cuenta del administrador es admin y también sin clave definida. Esta es la única línea que con seguridad habrá de cambiar el programador para probar sus aplicaciones. getConnection admite también una forma con un único parámetro (el URL de la base de datos), que debe proporcionar toda la información de conexión necesaria al driver JDBC Formando Emprendedores De Calidad Para Un Mundo Empresarial 146 Instituto de Educación Superior “San Ignacio de Monterrico” correspondiente. Para el caso JDBC-ODBC, se puede utilizar la sentencia equivalente: DriverManager.getConnection ( "jdbc:odbc:SQL;UID=sa;PWD=" ); Para el resto de los drivers JDBC, habrá que consultar la documentación de cada driver en concreto. Inmediatamente después de obtener la conexión, en la siguiente línea sentencia = conexion.createStatement(); se solicita que proporcione un objeto de tipo Statement para poder ejecutar sentencias a través de esa conexión. Para ello se dispone de los métodos execute(String sentencia) para ejecutar una petición SQL que no devuelve datos o executeQuery(String sentencia) para ejecutar una consulta SQL. Este último método devuelve un objeto de tipo ResultSet. Una vez que se tiene el objeto Statement ya se pueden lanzar consultas y ejecutar sentencias contra el servidor. A partir de aquí el resto del programa realmente es SQL «adornado»: en la línea: sentencia.execute( "DROP TABLE AMIGOS" ); se ejecuta DROP TABLE AMIGOS para borrar cualquier tabla existente anteriormente. Puesto que este ejemplo es una aplicación «de instalación» y es posible que la tabla AMIGOS no exista, dando como resultado una excepción, se aísla la sentencia.execute() mediante un try y un catch. La línea siguiente ejecuta una sentencia SQL que crea la tabla AMIGOS con tres campos: NOMBRE, APELLIDOS y CUMPLE. De ellos, únicamente el tercero, correspondiente al cumpleaños, es el que puede ser desconocido, es decir, puede contener valores nulos. sentencia.execute( "CREATE TABLE AMIGOS ("+ " NOMBRE VARCHAR(15) NOT NULL, " + " APELLIDOS VARCHAR(30) NOT NULL, " + " CUMPLE DATETIME) " ); Formando Emprendedores De Calidad Para Un Mundo Empresarial 147 Instituto de Educación Superior “San Ignacio de Monterrico” Y ya en las líneas siguientes se ejecutan sentencias INSERT para rellenar con datos la tabla. En todo momento se ha colocado un try ... catch exterior para interceptar cualquier excepción que puedan dar las sentencias. En general, para java.sql está definida una clase especial de excepciones que es SQLException. Se obtendrá una excepción de este tipo cuando ocurra cualquier error de proceso de JDBC, tanto si es a nivel JDBC como si es a nivel inferior (ODBC o de protocolo). Por ejemplo, si en lugar de GONZALO en la línea correspondiente a la última inserción en la Base de Datos, se intenta añadir un nombre nulo (NULL), se generará una excepción SQLException con el mensaje [Microsoft][ODBC SQL Server Driver][SQL Server]Attempt to insert the value NULL into column 'NOMBRE', table 'master.dbo.AGENDA'; column does not allow nulls. INSERT fails. que en el caso de Microsoft Access sería: [Microsoft][ODBC Microsoft Access 97 Driver] The field 'AGENDA.NOMBRE' can't contain a Null value because the Required property for this field is set to True. Enter a value in this field. En román paladino, el hecho de que la columna NOMBRE esté definida como NOT NULL, hace que no pueda quedarse vacía. Ahora se verán los pasos que hay que dar para obtener información a partir de una base de datos ya creada. Como se ha dicho anteriormente, se utilizará executeQuery en lugar de execute para obtener resultados. Se sustituyen las líneas que contenían esa sentencia por: resultado = sentencia.executeQuery( "SELECT * FROM AMIGOS" ); while( resultado.next() ) { String nombre = resultado.getString( "NOMBRE" ); String apellidos = resultado.getString( "APELLIDOS" ); String cumple = resultado.getString( "CUMPLE" ); System.out.println( "El aniversario de D. " + nombre + " " + apellidos + ", se celebra el " + cumple ); Formando Emprendedores De Calidad Para Un Mundo Empresarial 148 Instituto de Educación Superior “San Ignacio de Monterrico” } En este caso, en la primera línea se utiliza executeQuery para obtener el resultado de SELECT * FROM AMIGOS. Mediante resultado.next() la posición se situará en el «siguiente» elemento del resultado, o bien sobre el primero si todavía no se ha utilizado. La función next() devuelve true o false si el elemento existe, de forma que se puede iterar mediante while ( resultado.next() ) para tener acceso a todos los elementos. A continuación, en las líneas siguientes se utilizan los métodos getXXX() de resultado para tener acceso a las diferentes columnas. El acceso se puede hacer por el nombre de la columna, como en las dos primeras líneas, o bien mediante su ubicación relativa, como en la última línea. Además de getString() están disponibles getBoolean(), getByte(), getDouble(), getFloat(), getInt(), getLong(), getNumeric(), getObject(), getShort(), getDate(), getTime() y getUnicodeStream(), cada uno de los cuales devuelve la columna en el formato correspondiente, si es posible. Después de haber trabajado con una sentencia o una conexión es recomendable cerrarla mediante sentencia.close() o conexión.close(). De forma predeterminada los drivers JDBC deben hacer un COMMIT de cada sentencia. Este comportamiento se puede modificar mediante el método Connection.setAutoCommit( boolean nuevovalor). En el caso de que se establezca AutoCommit a false, será necesario llamar de forma explícita a Connection.commit() para guardar los cambios realizados o Connection.rollback() para deshacerlos. Como el lector habrá podido comprobar hasta ahora, no hay nada intrínsecamente difícil en conectar Java con una base de datos remota. Los posibles problemas de conexión que puede haber (selección del driver o fuente de datos adecuada, obtención de acceso, etc.), son problemas que se tendrían de una u otra forma en cualquier lenguaje de programación. El objeto ResultSet devuelto por el método executeQuery(), permite recorrer las filas obtenidas, no proporciona información referente a la estructura de cada una de ellas; para ello se utiliza ResultSetMetaData, que permite obtener el tipo de Formando Emprendedores De Calidad Para Un Mundo Empresarial 149 Instituto de Educación Superior “San Ignacio de Monterrico” cada campo o columna, su nombre, si es del tipo autoincremento, si es sensible a mayúsculas, si se puede escribir en dicha columna, si admite valores nulos, etc. Para obtener un objeto de tipo ResultSetMetaData basta con llamar al método getMetaData() del objeto ResultSet. En la lista siguiente aparecen algunos de los métodos más importantes de ResultSetMetaData, que permiten averiguar toda la información necesaria para formatear la información correspondiente a una columna, etc. getCatalogName() Nombre de la columna en el catálogo de la base de datos getColumnName() Nombre de la columna getColumnLabel() Nombre a utilizar a la hora de imprimir el nombre de la columna getColumnDisplaySize() Ancho máximo en caracteres necesario para mostrar el contenido de la columna getColumnCount() Número de columnas en el ResultSet getTableName() Nombre de la tabla a que pertenece la columna getPrecision() Número de dígitos de la columna getScale() Número de decimales para la columna getColumnType() Tipo de la columna (uno de los tipos SQL en java.sql.Types) getColumnTypeName() Nombre del tipo de la columna isSigned() Para números, indica si la columna corresponde a un número con signo isAutoIncrement() Indica si la columna es de tipo autoincremento Formando Emprendedores De Calidad Para Un Mundo Empresarial 150 Instituto de Educación Superior “San Ignacio de Monterrico” isCurrency() Indica si la columna contiene un valor monetario isCaseSensitive() Indica si la columna contiene un texto sensible a mayúsculas isNullable() Indica si la columna puede contener un NULL SQL. Puede devolver los valores columnNoNulls, columnNullable o columnNullableUnknown, miembros finales estáticos de ResultSetMetaData (constantes) isReadOnly() Indica si la columna es de solo lectura isWritable() Indica si la columna puede modificarse, aunque no lo garantiza isDefinitivelyWritable() Indica si es absolutamente seguro que la columna se puede modificar isSearchable() Indica si es posible utilizar la columna para determinar los criterios de búsqueda de un SELECT getSchemaName() Devuelve el texto correspondiente al esquema de la base de datos para esa columna En general pues, los objetos que se van a poder encontrar en una aplicación que utilice JDBC, serán los que se indican a continuación. Connection Representa la conexión con la base de datos. Es el objeto que permite realizar las consultas SQL y obtener los resultados de dichas consultas. Es el objeto base para la creación de los objetos de acceso a la base de datos. DriverManager Encargado de mantener los drivers que están disponibles en una aplicación concreta. Es el objeto que mantiene las funciones de Formando Emprendedores De Calidad Para Un Mundo Empresarial 151 Instituto de Educación Superior “San Ignacio de Monterrico” administración de las operaciones que se realizan con la base de datos. Statement Se utiliza para enviar las sentencias SQL simples, aquellas que no necesitan parámetros, a la base de datos. PreparedStatement Tiene una relación de herencia con el objeto Statement, añadiéndole la funcionalidad de poder utilizar parámetros de entrada. Además, tiene la particularidad de que la pregunta ya ha sido compilada antes de ser realizada, por lo que se denomina preparada. La principal ventaja, aparte de la utilización de parámetros, es la rapidez de ejecución de la pregunta. CallableStatement Tiene una relación de herencia cn el objeto PreparedStatement. Permite utilizar funciones implementadas directamente sobre el sistema de gestión de la base de datos. Teniendo en cuenta que éste posee información adicional sobre el uso de las estructuras internas, índices, etc.; las funciones se realizarán de forma más eficiente. Este tipo de operaciones es muy utilizada en el caso de ser funciones muy complicadas o bien que vayan a ser ejecutadas varias veces a lo largo del tiempo de vida de la aplicación. ResultSet Contiene la tabla resultado de la pregunta SQL que se haya realizado. En párrafos anteriores se han comentado los métodos que proporciona este objeto para recorrer dicha tabla. Transacciones En párrafos anteriores se ha tratado de la creación y uso de sentencias SQL, que siempre se obtenían llamando a un método de un objeto de tipo Connection, como createStatement() o prepareStatement(). El uso de transacciones, también se controla mediante métodos del objeto Connection. Como ya se ha dicho, Connection representa una conexión a una Base de datos dada, luego Formando Emprendedores De Calidad Para Un Mundo Empresarial 152 Instituto de Educación Superior “San Ignacio de Monterrico” representa el lugar adecuado para el manejo de transacciones, dado que estas afectan a todas las sentencias ejecutadas sobre una conexión a la base de datos. Por defecto, una conexión funciona en modo autocommit, es decir, cada vez que se ejecuta una sentencia SQL se abre y se cierra automáticamente una transacción, que sólo afecta a dicha sentencia. Es posible modificar esta opción mediante setAutoCommit(), mientras que getAutoCommit() indica si se está en modo autocommit o no. Si no se está trabajando en modo autocommit será necesario que se cierren explícitamente las transacciones mediante commit() si tienen éxito, o rollback(), si fallan; nótese que, tras cerrar una transacción, la próxima vez que se ejecute una sentencia SQL se abrirá automáticamente una nueva, por lo que no existe ningún método del tipo que permita iniciar una transacción. Es posible también especificar el nivel de aislamiento de una transacción, mediante setTransactionIsolation(), así como averiguar cuál es el nivel de aislamiento de la actual mediante getTransactionIsolation(). Los niveles de aislamiento se representan mediante las constantes que se muestran en la lista siguiente, en la cual se explica muy básicamente el efecto de cada nivel de aislamiento. TRANSACTION_NONE No se pueden utilizar transacciones. TRANSACTION_READ_UNCOMMITTED Desde esta transacción se pueden llegar a ver registros que han sido modificados por otra transacción, pero no guardados, por lo que podemos llegar a trabajar con valores que nunca llegan a guardarse realmente. TRANSACTION_READ_COMMITTED Se ven solo las modificaciones ya guardadas hechas por otras transacciones. TRANSACTION_REPEATABLE_READ Si se leyó un registro, y otra transacción lo modifica, guardándolo, y lo volvemos a leer, seguiremos viendo la información que había cuando lo Formando Emprendedores De Calidad Para Un Mundo Empresarial 153 Instituto de Educación Superior “San Ignacio de Monterrico” leímos por primera vez. Esto proporciona un nivel de consistencia mayor que los niveles de aislamiento anteriores. TRANSACTION_SERIALIZABLE Se verán todos los registros tal y como estaban antes de comenzar la transacción, no importa las modificaciones que otras transacciones hagan, ni que lo hayamos leído antes o no. Si se añadió algún nuevo registro, tampoco se verá. Además de manejar transacciones, el objeto Connection también proporciona algunos otros métodos que permiten especificar características de una conexión a una base de datos; por ejemplo, los métodos isReadOnly() y setReadOnly() permiten averiguar si una conexión a una base de datos es de sólo lectura, o hacerla de sólo lectura. El método isClosed() permite averiguar si una conexión está cerrada o no, y nativeSQL() permite obtener la cadena SQL que el driver mandaría a la base de datos si se tratase de ejecutar la cadena SQL especificada, permitiendo averiguar qué es exactamente lo que se le envía a la base de datos. Información de la Base de Datos Falta aún una pieza importante a la hora de trabajar con la conexión a la base de datos mediante Connection, y es la posibilidad de poder interrogar sobre las características de una base de datos; por ejemplo, puede se interesante saber si la base de datos soporta cierto nivel de aislamiento en una transacción, como la TRANSACTION_SERIALIZABLE, que muchos gestores no soportan. Para esto está otro de los interfaces que proporciona JDBC, DatabaseMetaData, al que es posible interrogar sobre las características de la base de datos con la que se está trabajando. Es posible obtener un objeto de tipo DatabaseMetaData mediante el método getMetaData() de Connection. DatabaseMetaData proporciona diversa información sobre una base de datos, y cuenta con varias docenas de métodos, a través de los cuales es posible obtener gran cantidad de información acerca de una tabla; por ejemplo, getColumns() devuelve las columnas de una tabla, getPrimaryKeys() devuelve la lista de columnas que forman la clave Formando Emprendedores De Calidad Para Un Mundo Empresarial 154 Instituto de Educación Superior “San Ignacio de Monterrico” primaria, getIndexInfo() devuelve información acerca de sus índices, mientras que getExportedKeys() devuelve la lista de todas las claves ajenas que utilizan la clave primaria de esta tabla, y getImportedKeys() las claves ajenas existentes en la tabla. El método getTables() devuelve la lista de todas las tablas en la base de datos, mientras que getProcedures() devuelve la lista de procedimientos almacenados. Muchos de los métodos de DatabaseMetaData devuelven un objeto de tipo ResultSet que contiene la información deseada. El listado que se presenta a continuación, muestra el código necesario para obtener todas las tablas de una base de datos. String nombreTablas = "%"; // Listamos todas las tablas String tipos[] = new String[1]; // Listamos sólo tablas tipos[0] = "TABLE"; DatabaseMetaData dbmd = conexion.getMetaData(); ResultSet tablas = dbmd.getTables( null,null,nombreTablas,tipos ); boolean seguir = tablas.next(); while( seguir ) { // Mostramos sólo el nombre de las tablas, guardado // en la columna "TABLE_NAME" System.out.println( tablas.getString( tablas.findColumn( "TABLE_NAME" ) ) ); seguir = tablas.next(); }; Hay todo un grupo de métodos que permiten averiguar si ciertas características están soportadas por la base de datos; entre ellos, destacan supportsGroupBy() indica si se soporta el uso de GROUP BY en un SELECT, mientras que supportsOuterJoins() indica si se pueden llevar a cabo outer-joins. El método supportsTransactions(), comentado antes, indica si cierto tipo de transacciones está soportado o no. Otros métodos de utilidad son getUserName(), que devuelve el nombre del usuario actual; getURL(), que devuelve el URL de la base de datos actual. DatabaseMetaData proporciona muchos otros métodos que permiten averiguar cosas tales como el máximo número de columnas utilizable en Formando Emprendedores De Calidad Para Un Mundo Empresarial 155 Instituto de Educación Superior “San Ignacio de Monterrico” un SELECT, etc. En general, casi cualquier pregunta sobre las capacidades de la base de datos se puede contestar llamando a los distintos métodos del objeto DatabaseMetaData, que merece la pena que el lector consulte cuando no sepa si cierta característica está soportada. Puente Jdbc-Odbc Para desarrollar nuestras aplicaciones nosotros usaremos la primera forma de conexión a base de datos con el puente JDBC-ODBC, que es la más sencilla y libre de costos monetarios. Siempre debemos considerar la siguiente figura en el desarrollo completo de nuestra aplicación: • Lo primero que tenemos considerar es el tener nuestra base de datos en SQL Server. • Luego construir el DSN (Data Source Name) mediante ODBC. • Considerar que los objetos para acceder a base de datos se encuentran en la API JDBC, por lo tanto una línea obligatoria en nuestro programa en Java es: impor java.sql.*; // Acceso a los objetos Connection, Statement y ResulSet • Finalmente podemos centrarnos en desarrollar el código Java que complete nuestra aplicación. Acceso A Una Base De Datos Con Java Formando Emprendedores De Calidad Para Un Mundo Empresarial 156 Instituto de Educación Superior “San Ignacio de Monterrico” La siguiente figura muestra un. Esta aplicación tiene efecto si hemos creado el DSN dsnPubs. Al dar clic en el botón Probar DSN veremos el siguiente resultado: El código para este programa esta dado por 2 clases: Formulario y MiPanel. El código de la clase Formulario es el siguiente: //Archivo Formulario import javax.swing.*; import java.awt.*; import java.awt.event.*; class Formulario extends JFrame { static int wF= 400, hF= 300; //"static" xq se van a usar en "main()" public Formulario(String s){ super(s); //invoca al padre y le pasa la cadena "s" como título getContentPane().add(new MiPanel()); //crea el panel en el formulario setLocation((getToolkit().getScreenSize().width - wF)/2, (getToolkit().getScreenSize().height - hF)/2); //para ubicar el formulario en una posicion especifica Formando Emprendedores De Calidad Para Un Mundo Empresarial 157 Instituto de Educación Superior “San Ignacio de Monterrico” // de la pantalla //getToolkit() me da informción útil, getScreenSize() // dimensiones de la pantalla (width, height) } static public void main(String[] arg) { JFrame f= new Formulario("Carlos Durand"); //para cerrar la ventana cuando se da click a la "X" //de la ventana f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent { System.exit(0); } }); f.setSize(wF, hF); f.setVisible(true); } } El código de MiPanel es el siguiente: //Archivo MiPanel import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.sql.*; // Acceso a objetos de JDBC class MiPanel extends JPanel implements ActionListener { JButton b= new JButton("Probar DSN"); JTextArea t= new JTextArea(); public MiPanel() { setLayout(new BorderLayout()); b.addActionListener(this); add(b, BorderLayout.NORTH); Formando Emprendedores De Calidad Para Un Mundo Empresarial 158 e) Instituto de Educación Superior “San Ignacio de Monterrico” add(t, BorderLayout.CENTER); } // --------------------Para eventos public void actionPerformed(ActionEvent e) { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection cn= DriverManager.getConnection( "jdbc:odbc:dsnPubs", "sa", ""); DatabaseMetaData meta= cn.getMetaData(); t.setText("Exito en conexión...!\n"); t.append("\nDatabase:\t" + meta.getDatabaseProductName()); t.append("\nversion:\t" + meta.getDatabaseProductVersion()); cn.close(); } catch(Exception ex) { t.setText("La conexión fracasó por:\n\n"); t.append(ex.toString()); } } } Explicación: • Los objetos de JDBC deben estar siempre dentro de una instrucción try - catch • Para indicar que tipo de driver vamos a usar en nuestra aplicación ponemos la sentencia: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); • Por la anterior sentencia el driver a usar es el puente JDBC-ODBC. • El administrador de drivers (DriverManager) del JDBC para el caso del puente JDBCODBC nos pide 3 argumentos: el DSN, login y password, como se indica en la siguiente sentencia: Connection cn= DriverManager.getConnection("jdbc:odbc:dsnPubs", "sa", ""); Formando Emprendedores De Calidad Para Un Mundo Empresarial 159 Instituto de Educación Superior “San Ignacio de Monterrico” • Este nos devuelve un objeto de tipo Connection con el cual se apertura el acceso a la base de datos. • Existen objetos para la metadata. La metadata es la data del entorno de los datos, por ejemplo si una columna tiene la data “Juan Pérez“, entonces su metadata es el nombre de la columna por ejemplo Nombre. Para recoger metadata del gestor de base de datos usamos un objeto de la clase DatabaseMetaData como indica la siguiente sentencia: DatabaseMetaData meta= cn.getMetaData(); • La metadata de la base de datos es pedida a través de un objeto de la clase Connection (cn). • El objeto meta de la clase DatabaseMetaData nos permite obtener el nombre y la versión del gestor de base de datos, como muestran las dos sentencias siguientes: t.append("\nDatabase:\t" + meta.getDatabaseProductName()); t.append("\nversion:\t" + meta.getDatabaseProductVersion()); • Podemos manipular excepciones a través de un objeto de la clase Exception o de la clase SQLException. En nuestra aplicación lo hicimos de la siguiente forma: catch(Exception ex) { t.setText("La conexión fracasó por:\n\n"); t.append(ex.toString()); } • La aplicación todo lo que hace es probar el dsnPubs mostrando el nombre y la versión del gestor de base de datos. • Esta aplicación puede servir de plantilla para probar otros DSNs. Objetos básicos de JDBC Formando Emprendedores De Calidad Para Un Mundo Empresarial 160 Instituto de Educación Superior “San Ignacio de Monterrico” Existen muchos objetos de JDBC, sin embargo, estos tienen vital importancia para empezar a desarrollar nuestras aplicaciones para acceder a base de datos: Connection Permite la conexión a la base de datos. Origina un canal entre nuestra aplicación y la base de datos y será siempre imprescindible en una aplicación para acceder a una base de datos. Statement Este objeto nos permitirá ejecutar una sentencia SQL para nuestra base de datos. Por ejemplo: select, insert, update y delete. ResulSet Si el objeto Statement ejecuta una sentencia select del SQL, entonces, este devuelve un conjunto de resultados. Este conjunto de resultados es asignado y manipulado por un objeto ResulSet. ResultSetMetaData Un objeto de esta clase tiene información meta sobre el conjunto de resultados, como por ejemplo: cuántas columnas tiene la consulta, los nombres de las columnas, los tipos de datos que guarda cada columna, cuántas filas, etc. Para dar ejemplo de los objetos anteriores vamos a desarrollar una aplicación que muestra un formulario con un botón (Consulta a la tabla Authors de la Pubs) y un objeto área de texto que muestra una consulta cuando se da clic al botón. El programa funciona con dsnPubs. La consulta será a la tabla Authors de la base datos Pubs (base de datos de ejemplo que viene con el SQL Server): Formando Emprendedores De Calidad Para Un Mundo Empresarial 161 Instituto de Educación Superior “San Ignacio de Monterrico” El código de la aplicación esta dado por 2 clases: Formulario y MiPanel. El código de la clase Formulario ya fue creado en el anterior ejemplo y es similar, sin embargo, el código de MiPanel es el siguiente: //Archivo MiPanel import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.sql.*; class MiPanel extends JPanel implements ActionListener { JButton b= new JButton("Consulta a la tabla Authors de la Pubs"); JTextArea t= new JTextArea(); public MiPanel() { setLayout(new BorderLayout()); b.addActionListener(this); add(b, BorderLayout.NORTH); add(new JScrollPane(t), BorderLayout.CENTER); } Formando Emprendedores De Calidad Para Un Mundo Empresarial 162 Instituto de Educación Superior “San Ignacio de Monterrico” // ----------------------------------------------------Para eventos public void actionPerformed(ActionEvent e) { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection cn= DriverManager.getConnection( "jdbc:odbc:dsnPubs", "sa", ""); Statement st= cn.createStatement(); ResultSet rs= st.executeQuery( "Select au_lname as Apellido," + "au_fname as Nombre," + "phone as Teléfono," + "address as Dirección" + " From Authors"); muestraData(rs); cn.close(); } catch(SQLException ex) { t.setText("Error de SQL:\n\n"); t.append(ex.toString()); } catch(Exception ex) { t.setText("Error genérico:\n\n"); t.append(ex.toString()); } } // ----------------------------------------------------Auxiliares public void muestraData(ResultSet r) throws Exception { ResultSetMetaData rmeta= r.getMetaData(); int numColumnas= rmeta.getColumnCount(); // Cuántas columnas t.setText(""); for(int i=1; i<=numColumnas; ++i) t.append(rmeta.getColumnName(i) + "\t"); // nombre de columna t.append("\n"); while(r.next()) { // hasta fin de archivo for(int i=1; i<=numColumnas; ++i) t.append(r.getString(i) + "\t"); t.append("\n"); } Formando Emprendedores De Calidad Para Un Mundo Empresarial 163 Instituto de Educación Superior “San Ignacio de Monterrico” } } Explicacion • En este ejemplo mostramos los objetos: Connection (cn), Statement (st) y ResulSet (rs) en una operación conjunta para la consulta a la base de datos. • Para acceder a los datos de la consulta necesitamos del objeto ResultSet: ResultSet rs= st.executeQuery( "Select au_lname as Apellido," + "au_fname as Nombre," + "phone as Teléfono," + "address as Dirección" + " From Authors"); • El objeto ResultSet permite acceder a las filas de la consulta mediante una instrucción Select del SQL. • Para ejecutar la instrucción Select necesitamos de un objeto Statement: Statement st= cn.createStatement(); • Por supuesto para ejecutar un Statement necesitamos previamente de un objeto Connection: Connection cn=DriverManager.getConnection("jdbc:odbc:dsnPubs","sa", ""); • El método muestraData es un método personalizado que responde a una instrucción Select. Su construcción se debió a la intención de simplificar y modular la aplicación. • El método muestraData como es invocado dentro de una instrucción try tiene que llevar la instrucción: throws Exception. Esto hará que si se produce una excepción en el método entonces se ejecuta la instrucción catch de la instrucción try en la que se invocó al método. • En nuestro caso el método muestraData recibe un objeto ResulSet (r) que viene a ser el contenido del objeto rs que tiene la consulta: muestraData(rs); Formando Emprendedores De Calidad Para Un Mundo Empresarial 164 Instituto de Educación Superior “San Ignacio de Monterrico” • Dentro del método muestraData usamos un objeto de la clase ResultSetMetaData con la intención de solicitar información meta de la consulta, como: cuántas columnas hay y cómo se llama cada columna. ResultSetMetaData rmeta= r.getMetaData(); int numColumnas= rmeta.getColumnCount(); // Cuántas columnas t.setText(""); for(int i=1; i<=numColumnas; ++i) t.append(rmeta.getColumnName(i) + "\t"); // nombre de columna • Para leer hasta la ultima fila usamos el siguiente código: while(r.next()) { // hasta fin de archivo for(int i=1; i<=numColumnas; ++i) t.append(r.getString(i) + "\t"); t.append("\n"); } • El método next() del ResulSet retorna false cuando no encuentra una fila. También cuando se ejecuta la consulta el objeto ResulSet está antes de la primera fila. • Para leer el dato de una columna y retornarlo como un dato de tipo String usamos el método getString(índice) del objeto ResulSet. Este método requiere como argumento el índice de la columna (1 para la primera) o el nombre de la columna. • Conforme se hace lectura a los datos de las columnas del ResultSet, estos datos obtenidos como String son añadidos al objeto JTextArea (t) para que el usuario vea la consulta. DISEÑO GUI CON AWT Y SWING. Formando Emprendedores De Calidad Para Un Mundo Empresarial 165 Instituto de Educación Superior “San Ignacio de Monterrico” Librería AWT y Swing Swing es un conjunto de clases desarrolladas por primera vez para Java 1.2 (el llamado Java2), para mejorar el anterior paquete que implementaba clases para fabricar interfaces de usuario, el llamado AWT (Abstract Window Tools) que aún se usa bastante en las aplicaciones Java. Tanto Swing como AWT forman parte de una colección de clases llamada JFC (Java Foundation Classes) que incluyen paquetes dedicados a la programación de interfaces gráficos (así como a la producción multimedia). Uno de los problemas frecuentes de la programación clásica era como programar interfaces de usuario, ya que esto implicaba tener que utilizar las API propias del Sistema Operativo y esto provocaba que el código no fuera transportable a otros sistemas. AWT fue la primera solución a este problema propuesta por Java. AWT está formada por un conjunto de clases que no dependen del sistema operativo, pero que proponen una serie de clases para la programación de GUIs (graphic users interfaces, interfaces gráficos de usuario; cualquier entorno de comunicación entre el ordenador y el usuario). AWT usa clases gráficas comunes a todos los sistemas operativos gráficos y luego la máquina virtual traduce esa clase a la forma que tenga en el sistema concreto en el que se ejecutó el programa, sin importar que dicho sistema sea un sistema X, McIntosh o Windows. La popularidad de AWT desbordó las expectativas de la propia empresa Sun. La clave de AWT era el uso de componentes iguales (peers). Los elementos de los interfaces AWT dejaban al sistema la responsabilidad de generar realmente los componentes. Eso aseguraba una vista coherente respecto al sistema en el que se ejecutaba el programa. El problema es que ante la Formando Emprendedores De Calidad Para Un Mundo Empresarial 166 Instituto de Educación Superior “San Ignacio de Monterrico” grandiosidad de la imagen en Windows y Mac OS, otros sistemas quedaban peor ante la misma aplicación. Por ello (y por otros problemas) aparece Swing en la versión 1.2 como parte del JFC (Java Foundation Classes) que es el kit de clases más importante de Java para las producciones gráficas. Los problemas de AWT eran: • AWT tenía problemas de compatibilidad en varios sistemas. • A AWT le faltaban algunos componentes avanzados (árboles, tablas,...). • Consumía excesivos recursos del sistema. Swing aporta muchas más clases, consume menos recursos y construye mejor la apariencia de los programas. En cualquier caso, AWT no desaparece; simplemente se añade a las nuevas capacidades Swing Componentes Los componentes son los elementos básicos de la programación con Swing. Todo lo que se ve en un GUI de Java es un componente. Los componentes se colocan en otros elementos llamados contenedores que sirven para agrupar componentes. Un administrador de diseño se encarga de de disponer la presentación de los componentes en un dispositivo de presentación concreto. La clase javax.swing.JComponent es la clase padre de todos los componentes. A su vez, JComponent desciende de java.awt.container y ésta de java.awt.component. De esto se deduce que Swing es una extensión de AWT, de hecho su estructura es análoga. Formando Emprendedores De Calidad Para Un Mundo Empresarial 167 Instituto de Educación Superior “San Ignacio de Monterrico” La clase JComponent posee métodos para controlar la apariencia del objeto. Por ejemplo: la visibilidad, tamaño, posición, tipo de letra, color,... Al dibujar un componente, se le asigna un dispositivo de presentación. Además posee métodos que controlan el comportamiento del componente. Cuando el usuario ejecuta una acción sobre un componente, entonces se crea un objeto de evento que describe el suceso. El objeto de evento se envía a objetos de control de eventos (Listeners). Los eventos son uno de los pilares de la construcción de Interfaces de usuario y una de las bases de la comunicación entre objetos. Formando Emprendedores De Calidad Para Un Mundo Empresarial 168 Instituto de Educación Superior “San Ignacio de Monterrico” Pares En AWT se usaban interfaces de pares. Esto significaba que cada componente creado con AWT, creaba un par igual correspondiente al mundo real. Es decir al crear el botón, existía el botón virtual creado en Java y el que realmente era dibujado en la pantalla (el real). El programador no necesita saber de la existencia de ese par, la comunicación del objeto creado con su par corría por cuenta de AWT. Este modelo de componentes se elimina en Swing. En Swing se habla de componentes de peso ligero. La clase JComponent que es la raíz de clases Swing, no utiliza un par, cada componente es independiente del sistema de ventanas principal. Se dibujan a sí mismos y responden a los eventos de usuario sin ayuda de un par. La ventaja de este modelo es que requiere menos recursos y que su modificación visual es más ágil y efectiva. Modelo/vista/controlador Se trata del modelo fundamental del trabajo con interfaces de usuario por parte de Swing. Consiste en tres formas de abstracción. Un mismo objeto se ve de esas tres Formas: • Modelo. Se refiere al modelo de datos que utiliza el objeto. Es la información que se manipula mediante el objeto Swing. • Vista. Es cómo se muestra el objeto en la pantalla. • Controlador. Es lo que define el comportamiento del objeto. Por ejemplo un array de cadenas que contenga los meses del año, podría ser el modelo de un cuadro combinado de Windows. Un cuadro combinado es un rectángulo con un botón con una flecha que permite elegir una opción de una lista. La vista de ese cuadro es el hecho de mostrar esas cadenas en ese rectángulo con flecha. Y el controlador es la capa software que permite capturar el clic del ratón cuando apunta a la flecha del control a fin de mostrar y seleccionar el contenido. Formando Emprendedores De Calidad Para Un Mundo Empresarial 169 Instituto de Educación Superior “San Ignacio de Monterrico” Métodos de JComponent La clase JComponent es abstracta, lo cual significa que no puede crear objetos, pero sí es la superclase de todos los componentes visuales (botones, listas, paneles, applets,...) y por ello la lista de métodos es interminable, ya que proporciona la funcionalidad de todos los componentes. Además puesto que deriva de Component y Container tiene los métodos de estos, por ello aún es más grande esta lista. Algunos son: Métodos de información Método String getName() void setname(String nombre) Container getParent() uso Obtiene el nombre del componente cambia el nombre del componente Devuelve el contenedor que sostiene a este componente Métodos de apariencia y posición Método void setVisible(boolean vis) uso Muestra u oculta el componente según el valor del argumento sea Color getForeground() true o false Devuelve el color de frente en void setForeGround(Color color) Color getBackground() forma de objeto Color Cambia el color frontal Devuelve el color de fondo en void setBackground(Color color) Point getLocation() forma de objeto java.awt.Color Cambia el color de fondo Devuelve la posición del componente en forma de objeto void setLocation(int x, int y) Point Coloca componente en la void setLocation(Point p) posición x, y Coloca el componente en la posición Dimension getSize() el marcada por las coordenadas del punto P Devuelve el tamaño del Formando Emprendedores De Calidad Para Un Mundo Empresarial 170 Instituto de Educación Superior “San Ignacio de Monterrico” componente en un objeto de tipo void setSize(Dimension d) java.awt.Dimension. Cambia las dimensiones del objeto en base a un objeto Dimension o indicando la anchura y la altura con dos enteros. void setSize(int ancho, int alto) void setBounds(int x, int y, int ancho, int alto) Determina la posición de la ventana (en la coordenada x, y) así como su tamaño con los parámetros ancho y void setPreferredSize(Dimension d) alto Cambia el tamaño preferido del componente. Este tamaño es el que void setToolTipText(String texto) el componente quiere tener. Hace que el texto realmente indicado aparezca cuando el usuario posa el cursor del ratón sobre el String getToolTipText() componente Obtiene el texto de ayuda del Cursor getCursor() componente Obtiene el cursor del componente void setCursor(Cursor cursor) en forma de objeto java.awt.Cursor Cambia el cursor del componente void setFont(Font fuente) por el especificado en el parámetro. Permite especificar el tipo de letra de la fuente del texto Contenedores Son un tipo de componentes pensados para almacenar y manejar otros componentes. Los objetos JComponent pueden ser contenedores al ser una clase que desciende de Container que es la clase de los objetos contenedores de AWT. Formando Emprendedores De Calidad Para Un Mundo Empresarial 171 Instituto de Educación Superior “San Ignacio de Monterrico” Para hacer que un componente forme parte de un contenedor, se utiliza el método add. Mientras que el método remove es el encargado de eliminar un componente. Ambos métodos proceden de la clase java.awt.Container Swing posee algunos contenedores especiales. Algunos son: • JWindow. Representa un panel de ventana sin bordes ni elementos visibles. • JFrame. Objeto que representa una ventana típica con bordes, botones de cerrar,etc. • JPanel. Es la clase utilizada como contenedor genérico para agrupar componentes. • JDialog. Clase que genera un cuadro de diálogo. • JApplet. Contenedor que agrupa componentes que serán mostrados en un navegador. JWindow Este objeto deriva de la clase java.awt.Window que a su vez deriva de java.awt.Container. Se trata de un objeto que representa un marco de ventana simple, sin borde, ni ningún elemento. Sin embargo son contenedores a los que se les puede añadir información. Estos componentes suelen estar dentro de una ventana de tipo Frame o, mejor, JFrame. Júrame Los objetos JFrame derivan de la clase Frame que, a su vez deriva, también de la clase Window, por lo que muchos métodos de esta clase son comunes a la anterior. Los objetos JFrame son ventanas completas. JDialog JDialog deriva de la clase AWT Dialog que es subclase de Window. Representa un cuadro de diálogo que es una ventana especializada para realizar operaciones complejas. Añadir componentes a las ventanas Formando Emprendedores De Calidad Para Un Mundo Empresarial 172 Instituto de Educación Superior “San Ignacio de Monterrico” Las clases JDialog y JFrame no permiten usar el método add, como les ocurre a los contenedores normales, por eso se utiliza el método getContentPane() que devuelve un objeto Container que representa el área visible de la ventana. A este contenedor se le llama panel contenedor y sí permite método add. public class prbVentana{ public static void main(String args[]){ JFrame ventana=new JFrame(“Prueba”); ventana.setLocation(100,100); Container c=ventana.getContentPane(); c.add(new JLabel(“Hola”)); ventana.pack(); ventana.setVisible(true); } Este código muestra una ventana ajustada al contenido de una ventana que pone Hola. Eventos En términos de Java, un evento es un objeto que es lanzado por un objeto y enviado a otro objeto llamado escuchador (listener). Un evento se lanza (o se dispara, fire) cuando ocurre una determinada situación (un clic de ratón, una pulsación de tecla,...). La programación de eventos es una de las bases de Java y permite mecanismos de diseño de programas orientados a las acciones del usuario. Es decir, son las acciones del usuario las que desencadenan mensajes entre los objetos (el flujo del código del programa se desvía en función del evento producido, alterando la ejecución normal). Hay multitud de tipos de eventos, más adelante se señala una lista de los eventos fundamentales. En su captura hay que tener en cuenta que hay tres objetos implicados: Formando Emprendedores De Calidad Para Un Mundo Empresarial 173 Instituto de Educación Superior “San Ignacio de Monterrico” • El objeto fuente. Que es el objeto que lanza los eventos. Dependiendo del tipo de objeto que sea, puede lanzar unos métodos u otros. Por ejemplo un objeto de tipo JLabel (etiqueta) puede lanzar eventos de ratón (MouseEvent) pero no de teclado (KeyEvent). El hecho de que dispare esos eventos no significa que el programa tenga que, necesariamente, realizar una acción. Sólo se ejecuta una acción si hay un objeto escuchando. • El objeto escuchador u oyente (listener). Se trata del objeto que recibe el evento producido. Es el objeto que captura el evento y ejecuta el código correspondiente. Para ello debe implementar una interfaz relacionada con el tipo de evento que captura. Esa interfaz obligará a implementar uno o más métodos cuyo código es el que se ejecuta cuando se dispare el evento. • El objeto de evento. Se trata del objeto que es enviado desde el objeto fuente al escuchador. Según el tipo de evento que se haya producido se ejecutará uno u otro método en el escuchador. Escuchadores De Eventos Cada tipo de evento tiene asociado un interfaz para manejar el evento. A esos interfaces se les llama escuchadores (Listeners) ya que proporcionan métodos que están a la espera de que el evento se produzca. Cuando el evento es disparado por el objeto fuente al que se estaba escuchando, el método manejador del evento se dispara automáticamente. Por ejemplo, el método actionPerformed es el encargado de gestionar eventos del tipo ActionEvent (eventos de acción, se producen, por ejemplo, al hacer clic en un botón). Este método está implementado en la interfaz ActionListener (implementa escuchadores de eventos de acción). Cualquier clase que desee escuchar eventos (los suyos o los de otros objetos) debe implementar la interfaz (o interfaces) pensada para capturar los eventos del Formando Emprendedores De Calidad Para Un Mundo Empresarial 174 Instituto de Educación Superior “San Ignacio de Monterrico” tipo deseado. Esta interfaz habilita a la clase para poder implementar métodos de gestión de eventos. Por ejemplo; un objeto que quiera escuchar eventos ActionEvent, debe implementar la interfaz ActionListener. Esa interfaz obliga a definir el método ya comentado actionPerformed. El código de ese método será invocado automáticamente cuando el objeto fuente produzca un evento de acción. Es decir, hay tres actores fundamentales en el escuchador de eventos: • El objeto de evento que se dispara cuando ocurre un suceso. Por ejemplo para capturar el ratón sería MouseEvent. • El método o métodos de captura del evento (que se lanza cuando el evento se produce). Pueden ser varios, por ejemplo para la captura de eventos de tipo MouseEvent (evento de ratón) existen los métodos mouseReleased (es invocado cuando se libera un botón del ratón), mousePressed (es invocado cuando se pulsa un botón del ratón), mouseEntered (es invocado cuando el cursor entra en el objeto) y mouseExited (ocurre cuando el ratón sale del objeto). • La interfaz que tiene que estar implementada en la clase que desea capturar ese evento. En este ejemplo sería MouseListener, que es la que obliga a la clase del escuchador a implementar los cuatro métodos de gestión comentados anteriormente Sin duda, el más complejo es este último, pero hay que entender que una internaz lo único que consigue es dar a una clase la facultad de escuchar (Listen) eventos. Formando Emprendedores De Calidad Para Un Mundo Empresarial 175 Instituto de Educación Superior “San Ignacio de Monterrico” Fuentes De Eventos Disparar eventos El objeto fuente permite que un objeto tenga capacidad de enviar eventos. Esto se consigue mediante un método que comienza por la palabra add seguida por el nombre de la interfaz que captura este tipo de eventos. Este método recibe como parámetro el objeto escuchador de los eventos. Esto es más fácil de lo que parece. Para que un objeto fuente, sea escuchado, hay que indicar quién será el objeto que escuche (que obligadamente deberá implementar la interfaz relacionada con el evento a escuchar). Cualquier componente puede lanzar eventos, sólo hay que indicárselo, y eso es lo que hace el método add. Ejemplo: public class MiVentana extends JFrame implements ActionListener{ JButton boton1=new JButton(“Prueba”); //Constructor public MiVentana() { boton1.addActionListener(this);//El botón lanza //eventos que son capturados por la ventana } public void actionPerformed(ActionEvent e){ //Manejo del evento } } Formando Emprendedores De Calidad Para Un Mundo Empresarial 176 Instituto de Educación Superior “San Ignacio de Monterrico” En el ejemplo anterior se habilita al boton1 para que lance eventos mediante el método addActionListener. Este método requiere un objeto escuchador que, en este caso, será la ventana en la que está el botón. Esta ventana tiene que implementar la interfaz ActionListener para poder escuchar eventos (de hecho el método addActionListener sólo permite objetos de esta interfaz). Cuando se haga clic con el ratón se llamará al método actionPerformed de la ventana, que es el método de gestión. Hay que señalar que una misma fuente puede tener varios objetos escuchando los eventos (si lanza varios métodos add). Si hay demasiados objetos escuchando eventos, se produce una excepción del tipo TooManyListenersException eliminar oyentes, Hay un método remove que sirve para que un oyente del objeto deje de escuchar los eventos. boton1.removeActionListener(this); //La ventana deja de //escuchar los eventos del botón Objeto de evento. Clase EventObject Ya se ha comentado que cuando se produce un evento se crea un objeto llamado objeto de evento. Este objeto es pasado al objeto que está escuchando los eventos. Todos los objetos de evento pertenecen a clases que derivan de EventObject. Esta es la superclase de todos los objetos de evento. Representa un evento genérico y en la práctica sólo sirve para definir los métodos comunes a todos los eventos que son: método Object getSource() uso Obtiene el objeto que lanzó el evento (método String toString() muy importante) Método toString redefinido para mostrar la información del evento Formando Emprendedores De Calidad Para Un Mundo Empresarial 177 Instituto de Educación Superior “San Ignacio de Monterrico” Lanzar Eventos Propios Se pueden crear eventos propios y lanzarlos a cualquier objeto que esté preparado para capturar eventos. Para ello basta crear el evento deseado indicando, al menos, en el constructor el objeto que capturará el evento y el identificador de evento. El identificador es un entero que sirve para indicar el tipo de evento producido. En un evento MouseEvent habrá que (MouseEvent.MOUSE_CLICKED), indicar si es de un evento de clic arrastre (MouseEvent.MOUSEDRAGGED,...). Además según el tipo de evento se pueden requerir más valores (posición del cursor, etc.). En general está técnica sirve para hacer pruebas, pero también se emplea para otros detalles. Formando Emprendedores De Calidad Para Un Mundo Empresarial 178 Instituto de Educación Superior “San Ignacio de Monterrico” Ejemplo: ventana v1=new ventana(); v1.setLocation(100,100); v1.setSize(300,300); v1.setVisible(true); WindowEvent we=new WindowEvent(v1,WindowEvent.WINDOW_CLOSING); v1.dispatchEvent(we); Suponiendo que ventana sea una clase preparada para escuchar eventos de tipo WindowsEvent, se crea el objeto de evento we. El envío del evento se realiza con el método dispachEvent. Adaptadores Para facilitar la gestión de eventos en ciertos casos, Java dispone de las llamadas clases adaptadores. Gracias a ellas, en muchos casos se evita tener que crear clases sólo para escuchar eventos. Estas clases son clases de contenido vacío pero que son muy interesantes para capturas sencillas de eventos. Todas poseen la palabra adapter en el nombre de clase. Por ejemplo esta es la definición de la clase MouseAdapter: public abstract class MouseAdapter implements MouseListener { public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} } Es una clase que implementa el interfaz MouseListener, pero que no define lo que hace cada método de captura. Eso se suele indicar de manera dinámica: Formando Emprendedores De Calidad Para Un Mundo Empresarial 179 Instituto de Educación Superior “San Ignacio de Monterrico” JFrame ventana =new JFrame(“prueba”); ventana.setLocation(100,100); ventana.setSize(300,300); ventana.setVisible(true); ventana.addMouseListener(new MouseAdapter(){ public void mouseClicked(MouseEvent e){ System.out.println(“Hola”); }}; En el ejemplo anterior al hacer clic en la ventana se escribe el mensaje Hola en la pantalla. No ha hecho falta crear una clase para escuchar los eventos. Se la crea de forma dinámica y se la define en ese mismo momento. La única función del adaptador es capturar los eventos deseados. Otro ejemplo (hola mundo en Swing): import javax.swing.*; import java.awt.*; import java.awt.event.*; public class HolaMundoSwing { public static void main(String[] args) { JFrame frame = new JFrame("HolaMundoSwing"); JLabel label = new JLabel("Hola Mundo"); frame.getContentPane().add(label); frame.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0); } } ); frame.pack(); frame.setVisible(true); } } El resultado de ese famoso código es esta ventana: Formando Emprendedores De Calidad Para Un Mundo Empresarial 180 Instituto de Educación Superior “San Ignacio de Monterrico” El evento windowClosing está capturado por una clase adaptadora, cuya efecto es finalizar el programa cuando se cierra la ventana. Clases adaptadoras: • ComponentAdapter • ContainerAdapter • FocusAdapter • InternalFrameAdapter • KeyAdapter • MouseAdapter • MouseMotionAdapter • PrintJobAdapter • WindowAdapter Mensajes hacia el usuario. clase JOptionPane Una de las labores típicas en la creación de aplicaciones gráficas del tipo que sea, es la de comunicarse con el usuario a través de mensajes en forma de cuadro de diálogo. Algunos cuadros son extremadamente utilizados por su sencillez (textos de aviso, error, confirmación, entrada sencilla de datos, etc.). La clase JOptionPane deriva de JComponent y es la encargada de crear este tipo de cuadros. Aunque posee constructores, normalmente se utilizan mucho más una serie de métodos estáticos que permiten crear de forma más sencilla objetos JOptionPane. Cuadros de información Son cuadros de diálogo que sirven para informar al usuario de un determinado hecho. Se construyen utilizando los siguientes métodos estáticos: Formando Emprendedores De Calidad Para Un Mundo Empresarial 181 Instituto de Educación Superior “San Ignacio de Monterrico” método static showMessageDialog( padre, Object mensaje) static uso void Muestra un cuadro de diálogo en el Component contenedor padre indicado con un determinado mensaje void Muestra un cuadro de diálogo en el showMessageDialog( Component contenedor padre indicado con un padre, Object mensaje, String título, determinado mensaje, título y tipo. int tipo) static showMessageDialog( void Igual que el anterior pero se permite Component indicar un icono para acompañar el padre, Object mensaje, String título, mensaje int tipo, Icon i) Estos son los posibles creadores de este tipo de cuadro. El tipo puede ser una de estas constantes: • JOptionPane.INFORMATION_MESSAGE. • JOptionPane.ERROR_MESSAGE. • JOptionPane.WARNING_MESSAGE. Aviso • JOptionPane.QUESTION_MESSAGE. Pregunta • JOptionPane.PLAIN_MESSAGE. Sin icono Ejemplo: JOptionPane.showMessageDialog(this, “Soy un mensaje normal”, “Cuadro 1”, JOptionPane.INFORMATION_MESSAGE); Formando Emprendedores De Calidad Para Un Mundo Empresarial 182 Instituto de Educación Superior “San Ignacio de Monterrico” SOCKETS Introducción Los sockets son un proceso de comunicación entre aplicaciones que están en diferentes máquinas en una red. Para mayor precisión, un socket es un punto de comunicación por el cual un proceso puede emitir o recibir información. Los sockets se hicieron populares con Berckley Software Distribution, de la universidad norteamericana de Berkley. Y estos han de ser capaces de utilizar el protocolo de streams TCP (Transfer Contro Protocol) y el de datagramas UDP (User Datagram Protocol). Los sockets utilizan una serie de primitivas para establecer el punto de comunicación, para conectarse a una máquina remota en un determinado puerto que esté disponible, para escuchar en él, para leer o escribir y publicar información en él, y finalmente para desconectarse. Una ilustración de los pasos a seguir en este proceso de comunicación, es el siguiente: Formando Emprendedores De Calidad Para Un Mundo Empresarial 183 Instituto de Educación Superior “San Ignacio de Monterrico” Recepción y envío de datos (SocketGet y SocketSend) A continuación mostraremos 2 aplicaciones, la primera (SocketGet) recibirá valores que son enviados por la segunda aplicación (SocketSend). Ambas, aplicaciones las puede ejecutar en su misma PC, o en diferentes PC, pero debe tener en cuenta que debe ejecutar primero SocketGet, que es la que estará atenta a los envíos del cliente SocketSend. Formando Emprendedores De Calidad Para Un Mundo Empresarial 184 Instituto de Educación Superior “San Ignacio de Monterrico” Esta sería la estructura de archivos de SocketGet, donde el programa principal es justamente la clase SocketGet, la que esta diseñada para habilitar puerto 8013, con la intención de recibir por allí, los datos que un cliente le envía. Los demás programas son los que generan la interfase visual donde la clase SocketGet muestra los datos enviados por el cliente. Asimismo, la clase MsgBox mostrará mensajes de error, si es que estos se produjeran. Código de la clase SocketGet: import java.net.*; import java.io.*; public class SocketGet extends Thread { private ServerSocket fServerSocket=null; private DataInputStream entrada=null; Formando Emprendedores De Calidad Para Un Mundo Empresarial 185 Instituto de Educación Superior “San Ignacio de Monterrico” public SocketGet() { super(); try { fServerSocket= new ServerSocket(8013); } catch(IOException e) { MsgBox.show(e.getMessage()); } } public void run() { Socket elCliente; try { // acepta esperar cliente elCliente= fServerSocket.accept(); entrada = new DataInputStream( elCliente.getInputStream()); while(true) { // bytes disponibles para leer int bytesDisponibles= entrada.available(); if(bytesDisponibles>0) { byte[] buffer = new byte[bytesDisponibles]; entrada.read(buffer); String line= new String(buffer); PanelMain.texto.append(line.trim() + "\n"); PanelMain.texto.setCaretPosition( PanelMain.texto.getText().length()); } else { sleep(1000); } } } catch(IOException e) { Formando Emprendedores De Calidad Para Un Mundo Empresarial 186 Instituto de Educación Superior “San Ignacio de Monterrico” MsgBox.show(e.getMessage()); } catch(InterruptedException e) { MsgBox.show(e.getMessage()); } } } Código de la clase PanelMain: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class PanelMain extends JPanel { public static JTextArea texto= new JTextArea(); SocketGet sGet= new SocketGet(); public PanelMain() { setLayout(new BorderLayout()); add(new JScrollPane(texto), BorderLayout.CENTER); sGet.start(); // Se queda recibiendo } } Código de la clase PanelMain: import javax.swing.*; import java.awt.*; Código de la clase Formulario: import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Formulario extends JFrame { Formando Emprendedores De Calidad Para Un Mundo Empresarial 187 Instituto de Educación Superior “San Ignacio de Monterrico” static int wF= 200, hF= 300; public Formulario(String s) { super(s); getContentPane().add(new PanelMain()); setLocation((getToolkit().getScreenSize().width - wF)/2, (getToolkit().getScreenSize().height - hF)/2); } static public void main(String[] arg) { JFrame f= new Formulario("Rocoge"); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(wF, hF); f.setVisible(true); } } Código de la clase MsgBox: import javax.swing.JOptionPane; public class MsgBox extends JOptionPane { public static final int ERROR = JOptionPane.ERROR_MESSAGE; public static final int INFORMATION= JOptionPane.INFORMATION_MESSAGE; public static final int WARNING = JOptionPane.WARNING_MESSAGE; public static final int QUESTION = JOptionPane.QUESTION_MESSAGE; public static final int PLAIN = JOptionPane.PLAIN_MESSAGE; Formando Emprendedores De Calidad Para Un Mundo Empresarial 188 Instituto de Educación Superior “San Ignacio de Monterrico” //constantes enteras para botones de confirmación public static final int DEFAULT= JOptionPane.DEFAULT_OPTION; public static final int YES_NO= JOptionPane.YES_NO_OPTION; public static final int YES_NO_CANCEL = JOptionPane.YES_NO_CANCEL_OPTION; public static final int OK_CANCEL = JOptionPane.OK_CANCEL_OPTION; //constantes enteras de retorno //para mensajes de confirmación public static final int YES= JOptionPane.YES_OPTION; public static final int NO= JOptionPane.NO_OPTION; public static final int OK= JOptionPane.OK_OPTION; public static final int CANCEL= JOptionPane.CANCEL_OPTION; public MsgBox() { super(); } // Solo el diálogo con su mensaje public static void show(String mensaje) { JOptionPane.showMessageDialog(null, mensaje, "Mensaje", JOptionPane.INFORMATION_MESSAGE); } // Mensaje y título para el diálogo public static void show(String mensaje, String titulo) { JOptionPane.showMessageDialog(null, mensaje, titulo, JOptionPane.INFORMATION_MESSAGE); } // Mensaje, título y figura para el diálogo public static void show(String mensaje, String titulo, Formando Emprendedores De Calidad Para Un Mundo Empresarial 189 Instituto de Educación Superior “San Ignacio de Monterrico” int tipo) { JOptionPane.showMessageDialog(null, mensaje, titulo,tipo); } // Mensaje y tipo de confirmación para el diálogo public static int confirma(String mensaje) { return JOptionPane.showConfirmDialog(null, mensaje, "Confirmación", DEFAULT); } // Mensaje, título y tipo de confirmación para el diálogo public static int confirma(String mensaje, String titulo, int tipo) { return JOptionPane.showConfirmDialog(null, mensaje, titulo, tipo); } } Esta sería la estructura de archivos de SocketSend, donde el programa principal es justamente la clase SocketSend, la que esta diseñada para enviar datos a una PC según su IP y un puerto, de tal forma que si hubiese una aplicación servidora atenta a ese puerto, entonces podrá recoger los datos por allí. Los demás programas son los que generan la interfase visual donde la clase SocketSend muestra los datos que envía al Servidor. Asimismo, la clase MsgBox mostrará mensajes de error, si es que estos se produjeran. Código de la clase SocketSend import java.net.*; import java.io.*; public class SocketSend extends Thread { private Socket socket; Formando Emprendedores De Calidad Para Un Mundo Empresarial 190 Instituto de Educación Superior “San Ignacio de Monterrico” private DataOutputStream salida=null; int contador=0; public SocketSend() { try { // poner IP de la otra PC, o dejar así // sobre la misma socket= new Socket("127.0.0.1", 8013); salida = new DataOutputStream(socket.getOutputStream()); } catch(IOException e) { MsgBox.show(e.getMessage()); } } public void run() { while(true) try { salida.writeBytes(String.valueOf(contador)); salida.flush(); PanelMain.texto.append("Se envia: " + contador++ + "\n"); PanelMain.texto.getText(setCaretPosition( PanelMain.texto.getText().length()); sleep(3000); } catch(IOException e) MsgBox.show(e.getMessage( } catch(InterruptedException e) { MsgBox.show(e.getMessage()); } } } Formando Emprendedores De Calidad Para Un Mundo Empresarial 191 Instituto de Educación Superior “San Ignacio de Monterrico” Formando Emprendedores De Calidad Para Un Mundo Empresarial 192