Capítulo 2

EL LENGUAJE JAVA





 

2.1. INTRODUCCIÓN AL LENGUAJE JAVA.

2.1.1. Orígenes de Java.

    El lenguaje Java está relacionado con C++, que es un descendiente directo del lenguaje C. De C heredó la sintaxis y de C++ la mayoría de las características de orientación a objetos.

    Hay que recordar que aunque Java se ha convertido en algo inseparable del entorno Internet, es ante todo un lenguaje de     programación.

    A finales de los 80 y principios de los 90 el control lo tenía la programación orientada a objetos y C++, C++ parecía un lenguaje perfecto ya que había sabido mezclar la alta eficiencia de C junto con el paradigma de la orientación a objetos, en definitiva, un lenguaje que se podía utilizar en una amplia diversidad de programas.

    Con la aparición de Internet y la World Wide Web (WWW) en la década de los 90, surgieron nuevas fuerzas que pretendían la evolución hacía un nuevo lenguaje de programación, esto provocó una nueva revolución en el mundo de la programación, estas son las premisas para la aparición del lenguaje Java.

    Java fue concebido por James Gosling, Patrick Naughton, Chris Warth, Ed Frank y Mike Sheridan en Sun Microsystems Inc. en 1991. Este lenguaje inicialmente se denominó Oak, pero se le puso el nombre de Java en 1995.

    Otros muchos han colaborado en el diseño y evolución del lenguaje: Bill Joy, Arthur van Hoff, Jonathan Payne, Frank Yellin y Tim Lindholm, por citar algunos de ellos.

    Inicialmente, Java no surgió como un lenguaje de desarrollo para Internet, sino que, la motivación principal fue la necesidad de crear un lenguaje que fuese independiente de la plataforma, es decir, con arquitectura neutral, con el fin de desarrollar software para diversos dispositivos electrónicos (microondas, controles remotos...). Existen muchos tipos de CPU que se utilizan como controladores. El problema de C y C++ (y de otros lenguajes) es que están diseñados para ser compilados para un procesador específico. Es cierto que se puede compilar un programa C++ para casi cualquier tipo de CPU, pero para poder realizarlo es necesario disponer del compilador de C++ correspondiente a ese tipo de CPU. Estos compiladores son en general caros y se tarda mucho tiempo en desarrollarlos. Por esta razón, se comenzó a trabajar en busca de un lenguaje portable y que fuese independiente de la plataforma, es decir, que se pudiera utilizar para producir código que se ejecutara en una serie de CPUs, en diferentes entornos, sin necesidad de tener que ser recompilado.

    Mientras se desarrollaba Java, surgió el fenómeno WWW, que impulsó aún más el desarrollo de Java: Internet está poblada por un inmenso universo de máquinas con diferentes sistemas operativos y tipos de CPU, lo que sugiere que la portabilidad sea un objetivo a conseguir, ya que a los usuarios les gustaría poder ejecutar los mismos programas, con independencia de su máquina o sistema operativo. De esta forma, Java pasó de estar pensado para la programación de dispositivos electrónicos de consumo, a ser un lenguaje idóneo para desarrollar programas portables que se pudieran ejecutar en Internet. Esto es así hasta el punto de que la idea original de utilizarlo para programar dispositivos electrónicos, fue desplazada por la de la programación en Internet.

2.1.2. Applets vs Aplicaciones Java.

    Java permite la creación de dos tipos de aplicaciones:

    Aplicaciones independientes Java.
    Applets Java.

    Una aplicación independiente Java es un programa que se ejecuta en la computadora del usuario, utilizando el sistema operativo de esa computadora. En este sentido, Java no es muy diferente del resto de lenguajes de programación, como C, C++ o Visual Basic, con la salvedad de que es ejecutada por una Máquina Virtual.

    Un applet Java es un programa diseñado para ser transmitido a través de Internet o de una Intranet, desde un servidor web a un cliente, y ser ejecutado en un navegador web compatible con Java. Los applets Java no presentan problemas de seguridad, pues su ejecución se realiza dentro del entorno del navegador. Estos applets no son simples ficheros de imagen, sonido o animación, sino que son verdaderos programas, que pueden reaccionar ante las acciones del usuario y cambiar dinámicamente, no sólo ejecutar repetidamente la misma animación.

    Los applets tienen, pues, la gran ventaja de la portabilidad, ya que pueden ser ejecutados por todos los sistemas con un navegador compatible con Java, con independencia de su sistema operativo o arquitectura de CPU.

    Pero los applets no serían tan ventajosos si no fueran capaces de abordar dos problemas básicos: seguridad y portabilidad. De ambos ya hemos hablado ya ligeramente. Extendamos algo más estos conceptos.

2.1.3. Seguridad.

    Cada vez que se transfiere al ordenador de un usuario un programa, se corre el riesgo de recibir un virus, caballos de Troya... Además, existen otros programas maliciosos contra los que hay que protegerse: estos programas pueden recoger información privada del usuario como números de tarjetas de crédito, cuentas bancarias y palabras de acceso que puede ser obtenida analizando el contenido del sistema de ficheros local del ordenador.

    Java establece un cortafuegos (firewall) entre una aplicación de red y el ordenador local: cuando se carga desde el navegador de un usuario una página web que contiene un applet Java, no hay ningún peligro de ser infectados por un virus o a recibir intentos de accesos malintencionados. Java consigue esto creando un entorno de ejecución aislado para ese applet, que no tiene acceso a los recursos locales de la máquina cliente. Ésta es una de las grandes ventajas de Java, en cuanto a seguridad se refiere.

2.1.4. Portabilidad.

    Hoy en día en el mundo hay muchas computadoras conectadas a Internet, de diferentes arquitecturas y sistemas operativos; es deseable tener un mecanismo que permita generar código ejecutable que sea portable a todos esos tipos de plataformas que están conectadas a Internet.

    Veremos cómo el mismo mecanismo que soluciona el problema de la seguridad, lo hace con la portabilidad, de manera eficiente y elegante.

    La clave de la portabilidad estriba en que el compilador de Java compila su código a un fichero objeto de formato independiente de la arquitectura de la máquina en que se ejecutará.

    El código fuente Java se "compila" a un código de bytes de alto nivel independiente de máquina, este código (byte-codes) está diseñado para poder ejecutarse en una máquina hipotética que es implementada por un sistema run-time, que sí es dependiente de máquina.
 

    Esta estructura queda representada claramente en el siguiente gráfico:
 
 

    Una representación de la arquitectura Java sobre una plataforma genérica, podría ser representada por la siguiente figura:



    En ella se observa que la parte dependiente del sistema es la Máquina Virtual Java (JVM) y las librerías fundamentales, que también nos permitirían acceder directamente al hardware de la máquina. Además, habrá APIs de Java que también entren en contacto directo con el hardware y serán dependientes de la máquina, como ejemplo de este tipo de APIs podemos citar:

    Java 2D: gráficos 2D y manipulación de imágenes.
    Java Media Framework : Elementos críticos en el tiempo: audio, video...
    Java Animation: Animación de objetos en 2D.
    Java Telephony: Integración con telefonía.
    Java Share: Interacción entre aplicaciones multiusuario.
    Java 3D: Gráficos 3D y su manipulación.

    Por tanto Java es un lenguaje interpretado, y esto es lo que permite que los programas Java puedan ser ejecutados en una gran variedad de entornos; como hemos visto sólo será necesario implementar para cada plataforma el intérprete de Java. Una vez que existe el programa intérprete para un sistema dado, cualquier programa Java puede ejecutarse en esa plataforma. Si Java fuese un lenguaje compilado, tendrían que existir diferentes versiones del mismo programa para cada uno de los tipos de CPU que están conectados a Internet. La interpretación es la forma más sencilla de crear programas realmente portables.

    El hecho de que Java es interpretado también permite hacerlo seguro. Como la ejecución de los programas Java está controlada por el intérprete de Java, éste controla el entorno de ejecución del programa y evita que provoque efectos no deseados en el sistema. La seguridad también se mejora con ciertas restricciones que existen en el lenguaje Java.

    La única desventaja que podría achacarse a Java sería que, al ser un lenguaje interpretado, se ejecuta más despacio que si fuese compilado (código ejecutable). También es cierto que Java, al utilizar byte-codes, hace que la diferencia de velocidad de ejecución con respecto de un lenguaje realmente compilado no sea tan grande.
 

2.2. CARACTERÍSTICAS DE JAVA.

2.2.1. Java es Simple.

    Java ofrece toda la funcionalidad de un lenguaje potente, pero sin las características menos usadas y más confusas de éstos. C++ es un lenguaje que adolece de falta de seguridad, pero C y C++ son lenguajes más difundidos, por ello Java se diseñó para ser parecido a C++ y así facilitar un rápido y fácil aprendizaje.

    Java elimina muchas de las características de otros lenguajes como C++, para mantener reducidas las especificaciones del lenguaje y añadir características muy útiles como el garbage collector (reciclador de memoria dinámica). No es necesario preocuparse de liberar memoria, el reciclador se encarga de ello y como es un thread de baja prioridad, cuando entra en acción, permite liberar bloques de memoria muy grandes, lo que reduce la fragmentación de la memoria.

    Java reduce en un 50% los errores más comunes de programación con lenguajes como C y C++ al eliminar muchas de las características de éstos, entre las que destacan:

    aritmética de punteros
    no existen referencias
    registros (struct)
    definición de tipos (typedef)
    macros (#define)
    necesidad de liberar memoria (free)

    Aunque, en realidad, lo que hace es eliminar las palabras reservadas (struct, typedef), ya que las clases son algo parecido.

    Además, el intérprete completo de Java que hay en este momento es muy pequeño, ocupa muy poca RAM.

2.2.2. Java es Orientado a Objetos.

    Java implementa la tecnología básica de C++ con algunas mejoras y elimina algunos aspectos para mantener el objetivo de la simplicidad del lenguaje. Java trabaja con sus datos como objetos y con interfaces a esos objetos. Soporta las tres características propias del paradigma de la orientación a objetos: encapsulación, herencia y polimorfismo. Las plantillas de objetos son llamadas, como en C++, clases y sus copias, instancias. Estas instancias, como en C++, necesitan ser construidas y destruidas en espacios de memoria.

    Java incorpora funcionalidades inexistentes en C++ como por ejemplo, la resolución dinámica de métodos. Esta característica deriva del lenguaje Objective C, propietario del sistema operativo Next. En C++ se suele trabajar con librerías dinámicas (DLLs) que obligan a recompilar la aplicación cuando se retocan las funciones que se encuentran en su interior. Este inconveniente es resuelto por Java mediante una interfaz específica llamada RTTI (RunTime Type Identification) que define la interacción entre objetos excluyendo variables de instancias o implementación de métodos. Las clases en Java tienen una representación en el runtime que permite a los programadores interrogar por el tipo de clase y enlazar dinámicamente la clase con el resultado de la búsqueda.

2.2.3. Java es Robusto.

    Java realiza verificaciones en busca de problemas tanto en tiempo de compilación como en tiempo de ejecución. La comprobación de tipos en Java ayuda a detectar errores lo antes posible en el ciclo de desarrollo. Java obliga a la declaración explícita de métodos y tipos de variables (Java es fuertemente tipado), reduciendo así las posibilidades de error. Maneja la memoria para eliminar las preocupaciones por parte del programador de la liberación o corrupción de memoria. También implementa los arrays auténticos, en vez de listas enlazadas de punteros, con comprobación de límites, para evitar la posibilidad de sobreescribir o corromper memoria resultado de punteros que señalan a zonas equivocadas. Estas características reducen drásticamente el tiempo de desarrollo de aplicaciones en Java.

    Además, para asegurar el funcionamiento de la aplicación, realiza una verificación de los byte-codes, que son el resultado de la compilación de un programa Java. Es un código de máquina virtual que es interpretado por el intérprete Java. No es el código máquina directamente entendible por el hardware, pero ya ha pasado todas las fases del compilador: análisis de instrucciones, orden de operadores, etc., y ya tiene generada la pila de ejecución de órdenes.

    Java proporciona, por tanto:

    Comprobación de punteros
    Comprobación de límites de arrays
    Excepciones
    Verificación de byte-codes

2.2.4. Java es Multihilo (Multithreaded).

    Java al ser multithreaded, permite muchas actividades simultáneas en un programa. Los threads (a veces llamados, procesos ligeros), son básicamente pequeños procesos o piezas independientes de un gran proceso. Al estar los threads construidos en el lenguaje, son más fáciles de usar y más robustos que sus homólogos en C o C++.

    El beneficio de ser multithreaded consiste en un mejor rendimiento interactivo y mejor comportamiento en tiempo real. Aunque el comportamiento en tiempo real está limitado a las capacidades del sistema operativo subyacente (Unix, Windows, etc.), aún supera a los entornos de flujo único de programa (single-threaded), tanto en facilidad de desarrollo, como en rendimiento.

    Como ejemplo de todo esto, supongamos la visualización de una imagen desde Internet. Con Java, esta imagen se pueden ir trayendo en un thread independiente, permitiendo que el usuario pueda acceder a la información en la página sin tener que esperar por el navegador.

2.2.5. Java posee Arquitectura Neutral.

    Uno de los principales problemas con el que se enfrentan los programadores es que no hay garantías de que un programa que escriben hoy se ejecute mañana, e incluso en la misma máquina. Las actualizaciones del sistema operativo, de los procesadores y los cambios en los recursos básicos del sistema pueden provocar que un programa deje de funcionar correctamente. Java ha sido diseñado para "escribir una vez, ejecutar en cualquier sitio, en cualquier momento y para siempre". En gran parte, ese objetivo se ha logrado.

2.2.6. Java es Interpretado.

    El intérprete Java (sistema run-time) puede ejecutar directamente el código objeto. Enlazar (linkar) un programa, normalmente, consume menos recursos que compilarlo, por lo que los desarrolladores con Java pasarán más tiempo desarrollando y menos esperando por el ordenador. No obstante, el compilador actual del JDK es bastante lento. Por ahora, que todavía no hay compiladores específicos de Java para las diversas plataformas, Java es más lento que otros lenguajes de programación, como C++, ya que debe ser interpretado y no ejecutado como sucede en cualquier programa tradicional.

    Se dice que Java es de 10 a 30 veces más lento que C, y que tampoco existen en Java proyectos de gran envergadura como en otros lenguajes. La verdad es que ya hay comparaciones ventajosas entre Java y el resto de los lenguajes de programación, y una gran cantidad de folletos electrónicos a favor y en contra de los distintos lenguajes contendientes con Java. Lo que se suele dejar de lado en todo esto, es que primero habría que decidir hasta que punto Java, un lenguaje en pleno desarrollo y todavía sin definición definitiva, está maduro como lenguaje de programación para ser comparado con otros; como por ejemplo con Smalltalk, que lleva más de 20 años en uso.

    La verdad es que Java para conseguir ser un lenguaje independiente del sistema operativo y del procesador que incorpore la máquina utilizada, es tanto interpretado como compilado. Y esto no es ningún contrasentido, el código fuente escrito con cualquier editor se compila generando el byte-code. Este código intermedio es de muy bajo nivel, pero sin alcanzar las instrucciones máquina propias de cada plataforma y no tiene nada que ver con el p-code de Visual Basic. El byte-code corresponde al 80% de las instrucciones de la aplicación. Ese mismo código es el que se puede ejecutar sobre cualquier plataforma. Para ello hace falta el run-time, que sí es completamente dependiente de la máquina y del sistema operativo, que interpreta dinámicamente el byte-code y añade el 20% de instrucciones que faltaban para su ejecución. Con este sistema es fácil crear aplicaciones multiplataforma, pero para ejecutarlas es necesario que exista el run-time correspondiente al sistema operativo utilizado.

2.2.7. Java es Distribuido.

    Java se ha construido con extensas capacidades de interconexión TCP/IP. Existen librerías de rutinas para acceder e interactuar con protocolos como http y ftp. Esto permite a los programadores acceder a la información a través de la red con tanta facilidad como a los ficheros locales.

    Java en sí no es distribuido, sino que proporciona las librerías y herramientas para que los programas puedan ser distribuidos, es decir, que se ejecuten en varias máquinas, interactuando.

    Como ejemplo, Java también tiene la posibilidad de que los objetos puedan ejecutar procedimientos remotos; esta facilidad se llama RMI (Remote Method Invocation). Esto le aporta más características del modelo cliente/servidor.

2.2.8. Java es Dinámico.

    Java se beneficia todo lo posible de la tecnología orientada a objetos. Java no intenta conectar todos los módulos que comprenden una aplicación hasta el tiempo de ejecución. Las librería nuevas o actualizadas no paralizarán las aplicaciones actuales (siempre que mantengan el API anterior).

    La siguiente figura muestra las diferencias entre un navegador convencional y un con Java:

    Java también simplifica el uso de protocolos nuevos o actualizados. Si su sistema ejecuta una aplicación Java sobre la red y encuentra una pieza de la aplicación que no sabe manejar es capaz de traer automáticamente cualquiera de esas piezas que el sistema necesita para funcionar desde el servidor remoto.
 
 

    Java, para evitar tener que ir descargando módulos de byte-codes, objetos o nuevas clases, implementa las opciones de persistencia, para que no se eliminen cuando de limpie la caché de la máquina.
 

2.3. JAVA Y LA ORIENTACIÓN A OBJETOS.

    Java, como anteriormente hemos comentado es un lenguaje orientado a objetos. Profundicemos en los aspectos teóricos de la programación orientada a objetos.

2.3.1. Dos Paradigmas.

    Todos los programas están formados por código y datos, al mismo tiempo un determinado programa puede estar organizado en base a su código o a sus datos. Así, nos encontramos con dos posibles paradigmas:

    Modelo orientado a proceso: el programa consiste en una serie de pasos lineales; el código actúa sobre los datos. Este es el caso de algunos lenguajes basados en procedimientos, como el lenguaje C.

    Modelo orientado a objetos: el programa se organiza en torno a sus datos (objetos) y la comunicación entre los objetos se produce mediante un conjunto de interfaces bien definidas (métodos). Un programa orientado a objetos se puede interpretar como datos que controlan el acceso al código. Con este enfoque se pueden conseguir algunas ventajas de organización.

2.3.2. Abstracción.

    La abstracción es un concepto clave de la programación orientada a objetos. Consiste en una manera de gestionar la complejidad, de forma que consideremos los objetos como entes bien definidos con un comportamiento propio único, sin considerar los detalles internos de estos objetos. Por ejemplo, abstraemos el objeto "coche" cuando lo consideramos con un fin determinado (por ejemplo, para hacer un viaje), sin tener en cuenta que un coche se compone de cientos de piezas y posee unos mecanismos internos complejos.

    Una forma de gestionar la abstracción es utilizando clasificaciones jerárquicas; esto permite dividir en niveles la semántica de los sistemas complejos, tratando así con partes más manejables. De esta forma, los datos de los programas algorítmicos tradicionales se transforman mediante la abstracción en objetos. Cada objeto tiene un comportamiento propio, y los objetos se comunican entre sí por medio de mensajes: es decir, un objeto solicita un servicio de otro enviándole un mensaje. Ésta es la esencia de la programación orientada a objetos.

2.3.3. Principios de la Programación Orientada a Objetos.

    Los principios básicos de la programación orientada a objetos son tres:

    Encapsulado
    Herencia
    Polimorfismo.

2.3.3.1. Encapsulado.

    El encapsulado es el mecanismo que permite juntar el código y los datos que manipula éste, para mantenerlos alejados de posibles interferencias o usos indebidos. Es como un envoltorio protector que evita que otro código que está fuera pueda acceder arbitrariamente al código o a los datos. De esta forma, el acceso al código y a los datos se realiza de forma controlada a través de una interfaz bien definida.

    El poder del código encapsulado es que todo el mundo conoce cómo acceder a él y pueden utilizarlo independientemente de los detalles de implementación; y esto sin temor a efectos laterales inesperados.

    Con el encapsulado se consigue al ocultar información. Se ocultan todos los secretos de un objeto que no contribuyen a sus características esenciales, típicamente la estructura de un objeto se encuentra oculta y sólo son los métodos los que pueden acceder a esa estructura interna (es lo único visible al exterior).

    En Java, la base del encapsulado es la clase. Una clase define la estructura y el comportamiento (datos y código) que serán compartidos por un conjunto de objetos. Cada objeto de una clase dada contiene la estructura y el comportamiento definidos por la clase; a los objetos se les llama también instancias de una clase. Una clase es una construcción lógica, mientras que un objeto tiene realidad física.

    Cuando se crea una clase, hay que especificar el código y los datos que la constituyen. Estos elementos son los miembros de la clase. Los datos definidos por la clase son las variables miembro o variables de instancia. El código que opera sobre esos datos se llama método miembro, o simplemente método. La palabra equivalente a método en C/C++ sería función. En un programa Java escrito correctamente, los métodos definen cómo se pueden utilizar las variables miembro. De esta manera, el comportamiento y la interfaz de una clase se definen mediante métodos que operan sobre sus datos de instancia.

    Como el propósito de una clase es encapsular la complejidad, debería haber mecanismos para ocultar la complejidad de la implementación dentro de la clase. Por esto, cada variable o método de una clase puede marcarse como público o privado:

    Si una variable o método de una clase es público, los usuarios externos a la clase pueden acceder a esa variable o método; sin embargo, si una variable o método de una clase es privado, entonces sólo puede ser utilizado por código miembro de la clase.

    Cualquier otro código que no es miembro de la clase no puede acceder a un método o variable privado. Como los métodos privados de una clase sólo se pueden utilizar por otras partes del programa a través de los métodos públicos de la clase, de esta forma se asegura que no se van a producir accesos no permitidos. Esto quiere decir que la interfaz pública debería ser diseñada cuidadosamente para no exponer demasiado los trabajos internos de una clase.

2.3.3.2. Herencia.

    La herencia es el mecanismo mediante el cual un objeto adquiere (o hereda) las propiedades de otro. De esta forma se consigue la clasificación jerárquica. Si no se hiciera una clasificación jerárquica de los objetos, cada objeto debería definir todas sus características explícitamente, y esto no sería viable.

    Sin embargo, utilizando la herencia, un objeto puede heredar sus atributos generales de otro objeto (su padre), y definir explícitamente sólo aquellas cualidades que lo hacen único dentro de su clase. Por tanto, la herencia es el mecanismo que permite a un objeto ser una instancia específica de un caso más general.

    Una clase que hereda de otra, se denomina subclase, y aquella clase de la que se hereda, se denomina superclase.

    Por ejemplo, supongamos que se define una clase para describir a los animales; se podría definir otra clase para describir a los mamíferos. La clase "mamíferos" heredaría todos los atributos de la clase "animales", pero además definiría una serie de atributos específicos de los mamíferos, como el tipo de dientes, o las glándulas mamarias. En este caso, la clase "mamíferos" es subclase de "animales", y la clase "animales" es superclase de "mamíferos".

    Una subclase hereda todos los atributos de cada uno de sus antecesores en la jerarquía de clases. Esta característica permite a los programas orientados a objetos crecer en complejidad de manera lineal en vez de geométrica.

2.3.3.3. Polimorfismo.

    El polimorfismo, es una característica que permite que una interfaz sea utilizada por una clase general de acciones. La acción específica se determina a partir de la naturaleza exacta de la situación.

    El concepto de polimorfismo se puede expresar con la frase "una interfaz, varios métodos". Esto reduce la complejidad permitiendo que la misma interfaz se utilice para especificar una clase general de acción. El compilador es quien debe seleccionar la acción específica (esto es, el método) que se debe ejecutar en cada situación; el programador no tiene que preocuparse por hacer esta selección manualmente, sino que sólo necesita recordar y utilizar la interfaz general.

    Como ejemplo, supongamos que necesitamos escribir una función que opere con caracteres, números enteros, y números reales; sin el polimorfismo, el programador tendría que escribir tres veces la misma función y darle a cada una un nombre diferente; gracias al polimorfismo, estas funciones podrían recibir el mismo nombre.

2.3.3.4. Conclusiones.

    Si se aplican adecuadamente, el polimorfismo, el encapsulado y la herencia pueden producir programas mucho más robustos y fáciles de ampliar que los modelos tradicionales de diseño orientado al proceso.

    Una estrategia de herencia bien diseñada es la base para reutilizar el código.

    El encapsulado permite realizar la migración de las implementaciones tras el paso del tiempo, sin destrozar todo el código que depende de la interfaz pública de sus clases.

    El polimorfismo permite crear código limpio, sensible, legible y resistente.

    En resumen, aplicando los principios orientados a objetos, las distintas partes de un programa complejo se pueden unir para formar un conjunto unido, robusto y mantenible.

2.3.4. Clases, objetos, variables y métodos en Java.

    La clase es la base de la programación orientada a objetos en Java. Es la construcción lógica sobre la que se construye el lenguaje Java, ya que define la forma y la naturaleza de un objeto.

    Cualquier concepto que se quiera implementar en un programa Java debe ser encapsulado en una clase.

    Una clase define un nuevo tipo de dato. Una vez definido, este nuevo tipo se puede utilizar para crear objetos de ese tipo. Así, un objeto es una instancia de una clase. Las palabras "objeto" e "instancia" se suelen usar indistintamente.

    Cuando se define una clase en Java, hay que especificar los datos que contiene la clase y el código que opera sobre ellos. Aunque puede haber clases sencillas que sólo contengan código o datos, la mayoría de las clases contiene ambas cosas.

    En Java, una clase se declara utilizando la palabra clave class.

    En un programa Java, todos los caracteres después de la secuencia "//" hasta el final de línea se consideran comentarios, al igual que los que se encuentran entre la secuencia "/*" y "*/".

    La forma general de la definición de una clase en Java es:
 

class nombre_de_clase {
    tipo variable_de_instancia_1;
    ...
    tipo variable_de_instancia_n;
    tipo nombre_de_método_1(lista_de_parámetros) {
        // cuerpo del método 1
    }
    ...
    tipo nombre_de_método_n(lista_de_parámetros) {
        // cuerpo del método n
    }
}


    Los datos o variables definidos en una clase se llaman variables de instancia. El código está implementado en los métodos. El conjunto de las variables y métodos definidos dentro de una clase son los miembros de la clase.

    Normalmente, las variables de instancia de una clase son modificadas por los métodos definidos en esa clase, por lo que los métodos son los que determinan cómo se pueden utilizar los datos de una clase.

    Cuando se crea un objeto de una determinada clase, ese objeto tiene su propia copia de las variables de instancia; los datos de un objeto son propios y están separados de los datos de otro objeto, aunque sean objetos de la misma clase.

2.3.5. Declaración y Creación de Objetos en Java.

    Una vez que se define una clase, para declarar objetos de esa clase, hay que realizar dos pasos:

    Declarar una variable del tipo de la clase; esta variable no es el objeto, sino una referencia al mismo.
    Obtener una copia física y real del objeto y asignarla a esa variable; esto se realiza con el operador de Java new. El operador new asigna dinámicamente, es decir, en tiempo de ejecución, memoria para un objeto y devuelve una referencia al mismo. Hasta que no se crea el objeto con el operador new, éste no existe físicamente.
 

    El uso del operador new es:

    variable = new nombre_de_clase();
    El nombre de la clase seguido por los paréntesis especifica el constructor de la clase. El constructor de una clase define qué acciones se deben ejecutar cuando se crea un objeto de esa clase; un uso de los constructores es, por ejemplo, inicializar ciertas variables de instancia cuando se crea el objeto. Los constructores también pueden recibir parámetros de entrada; al igual que los métodos, también pueden ser sobrecargados.

    Con los tipos simples, como los caracteres o los enteros, no es necesario utilizar el operador new, porque los tipos simples se implementan como variables normales; esto es así por cuestiones de eficiencia en la implementación de Java.

    Merece la pena resaltar de nuevo la diferencia entre clase y objeto: la clase es una construcción lógica, mientras que un objeto tiene realidad física, es decir, ocupa espacio en memoria.

2.3.6. Destrucción de objetos en Java.

    El operador new se utiliza para reservar memoria dinámicamente para la creación de los objetos.

    Sin embargo, la destrucción de los objetos, y la consiguiente liberación de su memoria utilizada, se realiza en Java de manera automática. A este mecanismo se le denomina recogida de basura (garbage collection), su funcionamiento es el siguiente: cuando no hay ninguna referencia a un objeto determinado, se asume que ese objeto no se va a utilizar más, y la memoria ocupada por el objeto se libera, este proceso es ejecutado por un thread de baja prioridad.

    No hay necesidad, por tanto, en Java, de destruir explícitamente los objetos, como sucede en C++.

2.3.7. Métodos.

    La forma general de un método en Java es:
 

    tipo nombre_de_método(lista_de_parámetros) {
        // cuerpo del método
    }
    El tipo especifica el tipo de dato devuelto por el método. Puede ser cualquier tipo válido, incluidos los tipos de clase; si el método no devuelve ningún valor, entonces el tipo debe ser void. El nombre del método lo indica nombre_de_método y lista_de_parámetros es la lista de parámetros que recibe el método; los parámetros son variables que reciben el valor de los argumentos que se pasan al método. Un método puede no tener parámetros, en este caso la lista de parámetros está vacía.

    En cuanto al paso de parámetros, en Java se realiza de la siguiente manera: cuando se pasa un tipo simple a un método, se hace por valor, esto es, se pasa una copia del parámetro; sin embargo, si se pasa como parámetro un objeto, entonces se pasa por referencia.

    Si el método devuelve algún valor distinto de void, entonces lo devuelve utilizando la sentencia return, de la siguiente forma:

    return valor;

    Algunas veces un método necesita hacer referencia al objeto que lo invocó. Para poder hacer esto, Java define la palabra reservada this, que puede ser utilizada dentro de cualquier método para referirse al objeto actual. this también se utiliza para resolver colisiones en el espacio de nombres.

    Otro concepto importante es la sobrecarga de un método, consiste en definir dos o más métodos dentro de la misma clase que tengan el mismo nombre, pero con sus listas de parámetros distintas. Esto permite implementar el polimorfismo (una interfaz, varios métodos).

    Cuando se invoca un método sobrecargado, Java utiliza el tipo y/o el número de argumentos como guía para determinar la versión del método sobrecargado que realmente debe llamar. En algunos casos las conversiones de tipo automáticas de Java pueden jugar un papel importante en la resolución de la sobrecarga.

2.3.8. Control de acceso.

    La encapsulación proporciona otra importante característica: el control de acceso. Éste consiste en poder controlar qué partes de un programa pueden acceder a los miembros de una clase. Si se permite el acceso a los datos de una clase solamente a través de un conjunto de métodos bien definidos, se puede prevenir la incorrecta utilización de esos datos.

    Una clase bien implementada debe ser como una "caja negra" que puede ser utilizada pero no deja ver los detalles internos de su implementación.

    El tipo de acceso a un miembro de una clase se define con el especificador de acceso que modifica su declaración. Los especificadores de acceso en Java son public público), private (privado) y protected (protegido).

    Cuando un miembro de una clase tiene el especificador public, entonces ese miembro puede ser accedido por cualquier parte del programa, incluso si se accede desde otra clase.

    Cuando un miembro tiene el especificador private, entonces sólo puede ser accedido por otros miembros de su clase.

    Cuando no se utiliza ningún especificador de acceso, entonces, por defecto, los miembros de una clase son públicos dentro de su propio paquete, pero no pueden ser accedidos desde fuera de su paquete.

    El especificador protected permite que el miembro de la clase afectado sea accesible por las subclases directas de su clase.

2.3.9. Herencia en Java.

    La herencia permite las clasificaciones jerárquicas. En Java, para heredar una clase, se utiliza la palabra reservada extends.

    Por ejemplo, si la clase B hereda la clase A (B es subclase de A, A es superclase de B), la declaración de la clase B sería así:
 

    class B extends A {
        // variables de instancia
        // métodos
    }


    Así, la clase B hereda todas las variables de instancia y métodos de la clase A, y además define sus propias variables de instancia y métodos. Pero hay una excepción: una subclase no puede acceder a aquellos miembros de la superclase que han sido declarados como private.

    Expliquemos ahora el concepto de sobrescritura de métodos:

    En una jerarquía de clases, cuando un método de una subclase tiene el mismo nombre y tipo que un método de su superclase, entonces se dice que el método de la subclase sobrescribe al método de la superclase.

    Y cuando se llama a un método sobrescrito dentro de una subclase, siempre se refiere a la versión del método definida en la subclase. La versión del método definida por la superclase está oculta.

    Y la llamada a una función sobrescrita se resuelve en tiempo de ejecución, en lugar de durante la compilación; esto se denomina selección de método dinámica. La selección de método dinámica es la forma que tiene Java de implementar el polimorfismo durante la ejecución.

    Así, combinando la herencia con la sobrescritura de métodos, una superclase puede definir la forma general de los métodos que serán utilizados por todas sus subclases. Por otra parte, este polimorfismo dinámico es uno de los mecanismos más poderosos que ofrece el diseño orientado a objetos para soportar la reutilización del código y la robustez.
 

2.4. GESTIÓN DE EXCEPCIONES EN JAVA.

    Una excepción es una condición anormal que surge en una secuencia de código durante la ejecución de un programa. Es decir, es un "error de ejecución".

    En los lenguajes de programación que no tienen gestión de excepciones, hay que controlar los posibles errores de ejecución del programa manualmente, usando códigos de error. Sin embargo, Java posee gestión de excepciones, y lleva el problema de la gestión del error en tiempo de ejecución al mundo orientado a objetos.

    En Java, cuando surge una condición excepcional, se crea un objeto que representa la excepción y se envía al método que provocó esta excepción. Este método puede elegir gestionar la excepción él mismo o pasarla. Pero en algún punto, la excepción es capturada y procesada.

    Las excepciones pueden ser generadas por el intérprete de Java o pueden ser generadas por el propio código. Las excepciones generadas por Java están relacionadas con errores que violan las reglas del lenguaje Java o las restricciones del entorno de ejecución de Java. Las excepciones generadas manualmente se suelen utilizar para informar de algún error al método llamante.

    La gestión de excepciones en Java se realiza mediante cinco palabras clave: try, catch, throw, throws y finally.

    El funcionamiento básico es el siguiente: las sentencias del programa que se quieren controlar se incluyen en un bloque try; si se produce una excepción dentro de un bloque try, entonces esta excepción es lanzada. El programa puede capturar esta excepción, utilizando la sentencia catch, y tratarla como desee. Si se quiere generar una excepción manualmente, se utiliza la palabra clave throw. Cualquier excepción que se lanza fuera de un método debe ser especificada como tal utilizando la sentencia throws. Cualquier código que se quiera ejecutar obligatoriamente antes de que termine un método, debe introducirse en un bloque finally.

    Ésta es la forma general de un bloque de gestión de excepciones en Java:
 

    try {
        // bloque de código
    }
    catch (TipoExcepción1 exOb) {
        // gestor de excepciones del tipo TipoExcepción1
    }
    catch (TipoExcepción exOb) {
        // gestor de excepciones del tipo TipoExcepción2
    }
    //...
    finally {
        // bloque de código que se ejecutará antes de que termine el bloque try,
        // aunque se produzca una excepción
    }


    Cuando el intérprete de Java lanza una excepción, ésta debe ser capturada porque de lo contrario el programa terminará su ejecución bruscamente. Hay varios tipos de excepciones que son lanzadas automáticamente, como por ejemplo, las excepciones de división por cero, de índice de matriz fuera del rango de la misma, conversión de tipo inválida, etc.
 

2.5. PROGRAMACIÓN MULTIHILO EN JAVA.

    El lenguaje Java permite la programación multihilo. Un programa normal sólo tiene un camino de ejecución, es decir, en un determinado momento de la ejecución se está en un único punto del programa.

    Sin embargo, un programa multihilo tiene varios caminos o hilos de ejecución (threads) que pueden ejecutarse concurrentemente (si bien esto depende de la arquitectura de la máquina y de la capacidad del sistema operativo para permitir multithread). De esta forma se puede maximizar el uso de la CPU, ya que, por ejemplo, mientras un hilo está recibiendo un fichero de la red, otro puede estar grabando un fichero en disco; y otro puede estar procesando la entrada de datos del usuario; de esta forma, se aumenta el rendimiento del programa. Esto es una ventaja importante respecto a los tradicionales programas secuenciales.

    Los sistemas de un sólo hilo utilizan un enfoque llamado bucle de suceso con sondeo. En este modelo, un único hilo de control permanece en un bucle infinito, sondeando una única cola de sucesos para decidir qué hacer a continuación. Esto desaprovecha la utilización de la CPU. También puede suceder que una parte del programa acapare el sistema y otros eventos no puedan ser procesados.

    La utilización de la programación multihilo en Java elimina el mecanismo de bucle principal con sondeo: los diferentes hilos se ejecutan, y cada uno de ellos puede pararse, sin afectar al resto del programa (los demás hilos).

2.5.1. Estados de un hilo.

    Los hilos, al igual que los procesos, pueden estar en diferentes estados:

    preparado para ejecutarse: se puede ejecutar en el momento en que se disponga de un ciclo de CPU
    suspendido: está detenido temporalmente
    bloqueado: está esperando un recurso
    ejecutándose: se está ejecutando en ese momento
    finalizado: ha finalizado ya su ejecución

2.5.2. Prioridades de los hilos.

    Cada hilo de ejecución de un programa Java tiene una prioridad. Esta prioridad permite al intérprete de Java determinar cómo tratar cada hilo con respecto a los demás. El valor de la prioridad de un hilo es un número entero, que indica la prioridad relativa de un hilo respecto a otro.

    La prioridad de un hilo se utiliza para decidir cuándo se produce el cambio de contexto, esto es, el cambio de ejecución de un hilo por otro. De esta manera, un hilo deja de ejecutarse para que se ejecute otro, por una de estas dos causas:

    - El hilo cede voluntariamente el control, porque se bloquea en espera de una Entrada/Salida pendiente o de otro evento; en este caso, el siguiente hilo a ejecutar será aquel de mayor prioridad de entre los hilos preparados para la ejecución.

    - El hilo es desalojado por otro de mayor prioridad. En cuanto aparece como preparado para ejecutarse un nuevo hilo con mayor prioridad que el que se está ejecutando en ese momento, pasa a ejecutarse el nuevo hilo, suspendiéndose el de menor prioridad.

    El sistema multihilo de Java está desarrollado en base a la clase Thread y la interfaz Runnable. Y hay una serie de métodos en la clase Thread que permiten gestionar los hilos: obtener sus prioridades, pararlos, suspenderlos, detenerlos hasta que se produzca un evento, etc.

    La clave para conseguir un buen rendimiento con los programas multihilo es pensar de forma concurrente en vez de en forma lineal. Por ejemplo, cuando dentro de un programa hay dos subsistemas que pueden ejecutarse concurrentemente, es bueno implementarlos en hilos individuales, y así se mejorará la eficiencia del programa global. Pero tampoco es cuestión de tener demasiados hilos, pues esto puede degradar también el funcionamiento del sistema, que podría estar más ocupado en realizar los cambios de contexto de los hilos que en la ejecución de los mismos.
 

2.6. APPLETS JAVA.

    Los applets son aplicaciones Java a las que se accede en un servidor Internet, se transmiten a través de la red, se instalan automáticamente y se ejecutan como parte de un documento de la red (página web). Su gran ventaja es la portabilidad.

    Por tanto, los applets sólo pueden ser ejecutados por un navegador compatible con Java (como Netscape Navigator, MS Internet Explorer, etc), o por un programa visualizador de applets (como el programa appletviewer).

    Para incluir un applet en una página HTML, en esta página hay que utilizar la etiqueta o tag <APPLET>.

    Para el desarrollo de applets, Java proporciona la clase Applet, que proporciona los métodos para su manejo.

2.6.1. Arquitectura de un applet.

    Los applets son programas basados en ventanas, y así su arquitectura es diferente a la de los programas que interactúan con una consola.

    Los applets están guiados por eventos. Así, un applet está esperando hasta que se produce un evento. Cuando se produce un evento, el applet es informado de ello, y efectúa la acción correspondiente a ese evento (abrir un fichero, presentar una imagen, realizar un cálculo, etc.)

    Por tanto, es el usuario el que inicia la interacción con un applet y no al contrario. Estas interacciones se envían al programa como eventos que éste debe tratar.

2.6.2. Estructura de un applet.

    Hay cinco métodos que proporcionan el mecanismo que permite controlar la ejecución de un applet. Todos estos métodos tienen implementaciones por defecto, así que sólo han de sobrescribirse si se van a utilizar. Los métodos son:

    init(): es el primer método que se ejecuta, y sólo se ejecuta cuando se carga el applet. Este método se debe utilizar para realizar las acciones iniciales del applet: inicializar variables, distribuir los compontentes gráficos del applet, etc.

    start(): se ejecuta después de init(). También se ejecuta cada vez que el applet tiene que volver a ejecutarse (por ejemplo, cuando el usuario sale de la página web y después vuelve a la misma; o cuando se minimiza la ventana del navegador y se ejecuta otra tarea, y se vuelve a ejecutar el applet)

    paint(): este método se ejecuta cada vez que hay que pintar la pantalla del applet (por ejemplo, porque la ventana de la página web ha sido tapada por otra ventana, o porque se ha minimizado o restaurado su tamaño). Por eso en este método se deben introducir las acciones para volver a presentar los datos gráficos en el área de visualización del applet.

    stop(): este método se ejecuta cuando el navegador deja el documento HTML que contiene el applet (porque viaja a otra página). Se debe utilizar este método para suspender los hilos que no es necesario que se ejecuten mientras el applet no es visible. Estos hilos se pueden reiniciar cuando se ejecuta el método start() al volver el usuario a la página que contiene el applet.

    destroy(): se ejecuta cuando el applet va a ser borrado totalmente de memoria; entonces hay que liberar todos los recursos que el applet esté utilizando. El método stop() siempre se ejecuta antes que destroy().

    Resumiendo, para implementar un applet, hay que sobrescribir alguno o todos los métodos anteriores, en función de las necesidades del programador: aquellas acciones que tienen que ejecutarse solamente una vez, cuando se inicia el applet, se implementarán en init(); aquellas que se tengan que ejecutar cada vez que el applet se vuelve a ejecutar, se implementarán en start(); las que tengan que ejecutarse cuando el applet se detiene, en el método stop(); aquellas que se tienen que ejecutar cada vez que se tiene que volver a visualizar el applet, en el método paint(); por último, cualquier acción que deba realizarse cuando finaliza el applet (liberación de recursos, etc.) debe ejecutarse en el método destroy().

2.6.3. Algunos métodos gráficos.

    Hay una serie de métodos que permiten presentar información de carácter gráfico en el área de visualización del applet.
    Por ejemplo, el método drawString(cadena, x, y) permite escribir una cadena de caracteres en una posición (x, y) del applet.

    Otros métodos, como setBackground() y setForeground(), permiten definir el color de fondo y el de primer plano, respectivamente, de los datos gráficos que se presenten en pantalla.

    Hay métodos para dibujar líneas, como drawLine(), rectángulos, como drawRect()y fillRect(), elipses y círculos, como drawOval() y fillOval(), arcos, como drawArc() y fillArc(), polígonos, como drawPolygon() y fillPolygon(), etc. Otros métodos se encargan de visualizar imágenes, o diferentes tipos y estilos de letra, etc.

    Hay por tanto una gran variedad de métodos para poder presentar información gráfica en pantalla.

2.6.4. AWT: controles gráficos y gestores de organización.

    El AWT (Abstract Window Toolkit), es el conjunto de clases de Java que permiten desarrollar interfaces gráficas basadas en ventanas. Este conjunto de clases es amplio y sofisticado. El AWT contiene clases y métodos que permiten crear y gestionar ventanas.

    El AWT define ventanas en función de una jerarquía de clases que añade funcionalidad y carácter específico con cada nivel. Las dos ventanas más comunes son las que derivan de Panel, que las utilizan los applets, y las que derivan de Frame (marco), que permiten crear una ventana estándar del entorno gráfico del sistema operativo de la máquina.

    AWT define una gran variedad de controles gráficos que pueden ser insertados en un applet. Los controles son componentes que permiten al usuario interactuar con la aplicación de varias formas; por ejemplo, el control más utilizado es el botón (push button). Un gestor de organización automáticamente posiciona los componentes en el área de visualización del applet. Por tanto, la apariencia de la ventana depende de la combinación de controles que contiene y el gestor de organización utilizado para colocarlos.

    El gestor de organización automatiza la tarea de colocar los componentes gráficos dentro de la ventana. Aun así, a veces interesa situar manualmente estos componentes, por la complejidad de la otra opción. Este es el caso de este proyecto de fin de carrera.

2.6.5. Diferentes tipos de controles gráficos.

    Se relacionan ahora los diferentes tipos de controles gráficos que se pueden insertar en un applet. Cada uno se corresponde con una clase Java:

    Etiqueta (clase Label): es un elemento pasivo, pues simplemente muestra una cadena de caracteres que se representa en pantalla; el usuario no tiene la posibilidad de interactuar con ella.

    Botón (clase Button): es el control que más se usa; es como un botón que contiene una etiqueta y que genera un evento cuando se pulsa sobre él. Se suelen utilizar para indicar el comienzo o el final de una acción, o la aceptación de una proposición.

    Cuadro de comprobación (clase Checkbox): control que se utiliza para activar o desactivar una opción. Está formado por una etiqueta y un pequeño cuadro hueco que puede contener una pestaña marcada o no; esta pestaña indica si el Checkbox está activado o no.

    Grupo de cuadros de comprobación(clase CheckboxGroup): es un conjunto de cuadros de comprobación -Checkbox's- que son mutuamente excluyentes, es decir, solamente uno de ellos puede estar seleccionado en un momento dado. También son llamados radio buttons. Se usan cuando se quiere dar al usuario la posibilidad de elegir de entre varias opciones, de las cuales sólo una puede estar activa.

    Opción (clase Choice): es una lista emergente de elementos que permite al usuario elegir uno de ellos. Es una especie de menú. Cuando está inactivo, un componente Choice sólo ocupa el espacio suficiente para mostrar el elemento seleccionado actualmente; pero cuando se pulsa sobre él, emerge una lista de opciones de las cuales el usuario puede seleccionar una.

    Lista (clase List): es una lista de elementos de los cuales se pueden seleccionar uno o varios. La diferencia respecto a Choice es que éste muestra sólo el elemento que está seleccionado en cada momento, mientras que List muestra varios elementos, y además permite seleccionar más de un elemento (selección múltiple).

    Barra de desplazamiento (clase Scrollbar): es un elemento que se utiliza para seleccionar valores continuos entre un mínimo y un máximo. Las barras de scrollbar pueden estar orientadas horizontal o verticalmente, y constan de dos flechas en cada extremo de la barra, de modo que al pulsar sobre una de las dos flechas, se aumenta en una unidad el valor de la barra en el sentido de la flecha.

    Campo de texto (clase TextField): es un área de entrada de texto de una sola línea. Se utilizan para que los usuarios puedan introducir texto en los programas, y permiten utilizar las flechas, las teclas de cortar y pegar y las selecciones de ratón.

    Área de texto (clase TextArea): es un área de entrada de texto, similar a TextField, pero con más de una línea visible en pantalla

2.6.6. Gestores de organización.

    La función de un gestor de organización es colocar en la ventana del programa los controles gráficos automáticamente, siguiendo algún tipo de regla.

    La ventaja de tener un gestor de organización es que evita tener que colocar a mano los componentes gráficos (esto es especialmente importante si estos son un gran número).

    Para establecer el gestor de organización que se usará en un programa Java, se utiliza el método setLayout(), que tiene el siguiente formato:

    void setLayout (LayoutManager gestor)
 

    Si no se quiere utilizar ningún gestor, y se desea colocar los componentes gráficos manualmente, entoces el parámetro debe ser null. En este caso, habrá que implementar en el programa la posición y el tamaño de cada componente gráfico (en pixeles).

    El programador puede implementar el gestor de organización que mejor se adapte a sus necesidades. Pero Java proporciona varios tipos predefinidos de gestores de organización. Estos son:

    Gestor FlowLayout (organización continuada): es el gestor de organización por defecto para los applets. Es la organización más simple, pues los componentes, según se van añadiendo, se colocan desde la esquina superior izquierda, de izquierda a derecha, y de arriba a abajo. Cuando no caben más componentes en una línea, se colocan en la siguiente.

    Gestor BorderLayout : se suele utilizar en ventanas de nivel superior. Se compone de cuatro posiciones de anchura fija en los extremos y un área grande en el centro. Cada una de estas áreas tiene un nombre: North (norte), South (sur), East (este), West (oeste) y Center (centro).

    Gestor GridLayout : este gestor organiza los componentes en una cuadrícula de dos dimensiones (como una matriz bidimensional). Al definir este gestor, hay que especificar el número de filas y de columnas.

    Gestor GridBagLayout : es similar al gestor GridLayout, pues también trabaja se basa en una cuadrícula bidimensional para colocar los componentes, pero es más complejo y evolucionado.

    Gestor CardLayout : organiza los componentes como tarjetas indexadas, de forma que sólo hay una visible en un momento -está encima de las demás- ; esto puede ser útil para interfaces de usuario que tienen componentes opcionales que se pueden habilitar e inhabilitar dinámicamente en función de la entrada del usuario.
 

2.7. FUENTES DE RECOGIDA DE INFORMACIÓN

Al ser este capítulo típicamente teórico, es obligada la indicación de las fuentes que se han utilizado para su realización:

      Pedro Manuel Cuenca Jiménez. 1997. "El lenguaje de programación Java". Ed. Anaya Multimedia.

      Patrick Naughton, Herbert Schildt. 1997. "JAVA. Manual de Referencia". Ed. Osborne/McGraw-Hill.

      Bruce Eckel. 2000. "Thinking in Java" Second Edition. Prentice Hall.

      J. Froufe. Tutorial de Java. (Internet).

      Capítulo "El Lenguaje de Programación Java" del Proyecto de Fin de Carrera de la Facultad de Informática
        de la U.P.M. "Visualización Interactiva del Proceso de Integración Riemann en Internet".