Mostrando las entradas con la etiqueta JVM. Mostrar todas las entradas
Mostrando las entradas con la etiqueta JVM. Mostrar todas las entradas

27 de mayo de 2021

Sincronización.

    Los hilos se comunican principalmente compartiendo el acceso a los campos y los objetos a los que hacen referencia dichos campos. Aunque este esquema es extremadamente eficiente, lleva implícito dos errores potenciales:

  1. Interferencia de hilos (Thread interference).
  2. Errores de consistencia de memoria (Memory consistency errors).

    Con la sincronización se pueden prevenir estos dos errores, pero también se puede incurrir en la contención de hilos, que ocurre cuando dos o más hilos tratan de acceder al mismo recurso de manera simultánea (condición de competencia) provocando que el entorno de la máquina virtual ejecute uno o más hilos más lentamente, o incluso que llegue a suspender su ejecución. 

    La inanición y el interbloqueo de hilos son dos formas de contención y su análisis y tratamiento quedan fuera de los alcances de este blog. Aquí sólo se proporcionarán los elementos básicos relacionados con la sincronización de hilos y la prevención de los dos errores mencionados con anterioridad.

Interferencia de hilos.

    El planteamiento iniciará con el análisis de una clase extremadamente simple (Ejemplo Contador) que implementa un contador, así como su respectivo incremento, decremento y acceso a través de los métodos correspondientes.

    Si sólo un hilo accede a los métodos para la modificación de c: todo trabaja como se espera; pero si un objeto de la clase Contador es referido por múltiples hilos, la interferencia entre ellos hará que las cosas se salgan de control.

    La interferencia acontece cuando dos operaciones, ejecutándose en diferentes hilos pero accediendo a los mismos datos, se intercalan. Las operaciones de alto nivel que se escriben en los lenguajes de programación, por simples y sencillas que parezcan, habitualmente consisten de múltiples pasos o suboperaciones, y cuando estos pasos de traslapan o superponen los problemas emergen (condiciones de competencia).

    Los detalles precisos respecto a la exactitud de cómo se descompone una operación tan simple como el incremento de una variable (c++) no son relevantes, basta con saber por ahora que dicha operación podría ser descompuesta en tres pasos (ocurre lo análogo y correspondiente para c--):

  1. Obtener o recuperar el valor actual de c (fetch).
  2. Incrementar dicho valor en 1.
  3. Almacenar el valor incrementado nuevamente en c.

    Con base en lo anterior, supongamos ahora la existencia de dos hilos, y que el Hilo A invoca a incrementa y que casi al mismo tiempo el Hilo B invoca a decrementa. Si el valor inicial de c es 0, una posible secuencia de acciones traslapadas podría ser la siguiente:

  1. Hilo A: Recupera c.
  2. Hilo B: Recupera c.
  3. Hilo A: Incrementa valor recuperado; resultado: 1.
  4. Hilo B: Decrementa valor recuperado; resultado: -1.
  5. Hilo A: Almacena resultado en c; c vale ahora 1.
  6. Hilo B: Almacena resultado en c; c vale ahora -1.

    Como puede observarse, el resultado del Hilo A se pierde: es sobre escrito por el resultado del Hilo B. Este planteamiento es sólo una combinación posible, bajo distintas circunstancias, podría ser que ahora el resultado del Hilo B sea el que se pierda o también podría darse el caso de que todo resulte bien como se espera; el resultado final es impredecible (condición de competencia).

    El Ejemplo PruebaContador muestra la situación de interferencia de hilos en una ejecución con dos hilos: uno (main) incrementando 100 000 veces el contador y el otro (h) decrementando la misma cantidad de veces ¿Cuál es el resultado esperado y cuál es el que se obtiene? ¿Se generan los mismos valores o resultados en distintas ejecuciones? ¿Por qué no son cero?

Errores de consistencia de memoria.

    Este tipo de errores ocurren cuando hilos diferentes tienen vistas inconsistentes de lo que deberían ser los mismos datos. Las causas de este tipo de errores son muchas y muy variadas y su análisis queda fuera de los alcances de este blog. Por ahora basta con saber que el programador debe tener consciencia de este tipo de errores para que pueda emplear una estrategia para anularlos.

    La clave para anular este tipo de errores de consistencia de memoria es entender una relación denominada "sucede antes" (happens-before). Dicha relación es simplemente una garantía de que las escrituras de memoria realizadas por una declaración específica, son visibles para otra declaración específica. Para visualizar mejor esto, considere lo siguiente:

int contador = 0;
. . . 
Hilo A: contador++;
. . . 
Hilo B: System.out.println(contador);

    Suponga que dicho contador es compartido por dos hilos A y B, y suponga también que A incrementa el contador y que poco después B imprime en la salida estándar el contador. El valor impreso por B puede ser 0 o 1, es decir: no hay garantía de que el cambio a contador por parte del hilo A sea visible para el hilo B, a menos de que el programador establezca una relación happens-before entre estas dos sentencias. La sincronización es una de las formas de crear este tipo de relación.

    Java proporciona dos formas de sincronización:

  1. Métodos sincronizados.
  2. Sentencias sincronizadas.

     Para que un método sea sincronizado, basta con añadir la palabra reservada synchronized a su definición, tal y como se muestra en el Ejemplo ContadorSincronizado. El hacer que los métodos sean sincronizados, tiene dos efectos para las instancias de la clase:

  1. No es posible el traslape para dos invocaciones de métodos sincronizados que se realicen sobre el mismo objeto. Cuando un hilo está ejecutando un método sincronizado para un objeto determinado, todos los demás hilos que invoquen métodos sincronizados para el mismo objeto se bloquean, es decir, se suspende su ejecución, hasta que el primer hilo haya terminado.
  2. Cuando un método sincronizado termina, automáticamente establece una relación happens-before para cualquier invocación subsecuente de un método sincronizado para el mismo objeto, lo que garantiza que los cambios al estado del objeto sean visibles para todos los hilos. 

    Los constructores no pueden ser sincronizados y no tendría sentido, porque sólo el hilo que crea el objeto debería tener acceso a él. De hecho esta situación se identifica como un error de sintaxis.

    Los métodos sincronizados habilitan una estrategia simple para prevenir la interferencia de hilos y los errores de consistencia de memoria. Como regla general podría decirse que: si un objeto es visible por más de un hilo, todas las lecturas o escrituras a las variables (atributos) del objeto deberían ser por métodos sincronizados, con excepción quizá de los atributos final (los cuales no pueden ser modificados una vez que el objeto ha sido construido).

    El Ejemplo PruebaContadorSincronizado muestra cómo podría utilizarse la clase ContadorSincronizado. Analice ambas clases y compárese con sus contra partes de la sección anterior.

Candados intrínsecos y sentencias sincronizadas.

    La sincronización se construye al rededor de una entidad conocida como candado intrínseco o candado de monitor (o simplemente monitor) misma que juega un rol fundamental en dos aspectos relacionados con la sincronización:

  1. Hace cumplir el acceso exclusivo al estado del objeto (exclusión mutua).
  2. Establece y garantiza la relación happens-before esencial para la visibilidad y coherencia (consistencia de memoria).

    Cada objeto tiene asociado un candado intrínseco (lo mismo que cada clase cuando se habla de métodos estáticos). Por convención, si un hilo necesita acceso exclusivo y consistente a los campos de un objeto, tiene que apropiarse de dicho candado antes de hacerlo y liberarlo al terminar.

    Mientras un hilo posea el candado intrínseco, ningún otro hilo puede adquirirlo por lo que se bloqueará. Por otro lado, cuando un hilo libera un candado intrínseco, se establece una relación happens-before entre dicha acción y cualquier adquisición subsecuente del mismo candado.

    Cuando un hilo invoca un método sincronizado, automáticamente adquiere el candado intrínseco para dicho objeto y lo libera cuando el hilo en cuestión termina de ejecutar el método, aun cuando la terminación se dé por una excepción no atrapada.

    En este sentido, sucede que no siempre conviene que todo el método esté sincronizado debido a que se merma la concurrencia (contención de hilos); en algunos casos conviene que se sincronice sólo un bloque de código, debido a que el método podría tener también sentencias que no necesariamente acceden a datos compartidos, o que acceden a datos compartidos distintos, en cuyo caso un enfoque basado en sentencias sincronizadas sería entonces más apropiado (consulte el Ejercicio 8 de los Ejercicios selectos).

    Aunque para el ejemplo que se ha venido desarrollando en esta entrada no tendría mucho sentido utilizar las sentencias sincronizadas, con la finalidad de mostrar su uso y equivalencia para este caso particular, se propone al lector la revisión, comparación y análisis del Ejemplo ContadorSincronizado2 así como su correspondiente clase de prueba del Ejemplo PruebaContadorSincronizado2.

Conclusión.


    Como conclusión general e
s importante resaltar la importancia de la sincronización al trabajar con hilos y recursos compartidos por éstos.

    De los ejemplos comentados es importante también enfatizar y no perder de vista que una clase tan simple como Contador, utilizada en un contexto de hilos compitiendo por acceso a los datos puede derivar, sin la debida sincronización, en un potencial desastre respecto a la consistencia de la memoria y la coherencia de los datos derivada de la interferencia de hilos.

    Por otro lado, conviene también el tener presente que un abuso de la sincronización, pueden incurrir en la nada deseable contención de hilos, haciendo que las potenciales ventajas de la concurrencia no sólo desaparezcan sino que sean absurdas.

    Finalmente aquí, como en otras tantas instancias, se manifiestan dos de esas importantes y perennes lecciones de vida:

  1. "Las nuevas soluciones traen consigo nuevos problemas".
  2. "Todo viene con su precio".


26 de mayo de 2021

Trabajando con hilos.

Hilos y pausas.

    Una vez que un hilo se crea y la máquina virtual de Java lo integra al entorno de ejecución, el hilo empieza a trabajar. En ocasiones, resulta conveniente que un hilo realice pequeñas pausas, o que se sincronice con otros en el sentido de esperar o verificar si algún otro hilo ha hecho ya su trabajo.

    El Ejemplo MensajesPausados muestra el uso del método sleep para pausar temporalmente la ejecución de un hilo. sleep es un método estático, es decir, no se requiere de un objeto concreto que reciba el mensaje, pero sí el nombre de la clase a la cual pertenece (línea 28). En esencia, el ejemplo imprime línea por línea un hermoso poema con pausas de 4 segundos (4000 milisegundos) entre cada línea. Note que esto es sólo un tiempo aproximado, y no debería considerarse con exactitud bajo ninguna circunstancia. En este sentido, resulta fatal el considerar aspectos de cualquier tipo de sincronización con base en el tiempo: no es buena idea y el azar puede (y seguramente lo hará) jugar en su contra.

    Por otro lado, el Ejemplo MensajesPausados2 muestra como única diferencia respecto del anterior la línea 12, particularmente en lo que se refiere a la excepción InterruptedException. El uso de un método como sleep podría generar un excepción del tipo verificada (Checked Exception), por lo que es preciso que se maneje o al menos se atrape. En el primer ejemplo main sólo reporta que de ocurrir la relanzará, es decir, no hace ningún manejo ni la atrapa pero al menos la reporta; el ejemplo en turno omite dicha declaración, por lo que ni siquiera compilará y se reportará un mensaje similar al siguiente:

MensajesPausados2.java:28: error: unreported exception InterruptedException; must be caught or declared to be thrown
            Thread.sleep(4000);
                        ^
1 error

se invita al lector a corroborar lo anterior.

    El Ejemplo MensajesPausados3 muestra una alternativa para la ejecución realizando un manejo muy elemental de la excepción correspondiente a través del bloque try-catch (líneas 22-31). Note que desde el punto de vista de la compilación y la ejecución, el Ejemplo MensajesPausados y el Ejemplo MensajesPausados3 son lógicamente equivalentes.

Hilos interactuando.

   El Ejemplo HilosInteractuando consiste en dos hilos:

  1. El de main.
  2. El derivado de CicloMensaje: Hilo (línea 57).

    El primero es el hilo principal main que cada aplicación de Java tiene. El hilo principal crea y pone en ejecución (líneas 56-59) un nuevo hilo a partir de un objeto ejecutable (CicloMensaje) y espera por él para terminar (líneas 63, 67, 68-69 y 74).

    Si el hilo derivado de la clase CicloMensaje se tarda mucho en terminar, el hilo principal lo interrumpe (línea 71).
   
   Como en los ejemplos de la sección anterior, el hilo de CicloMensaje imprime un bello poema línea por línea pero ahora con una pausa de 1 segundo entre ellas. Note el lector que no se está haciendo uso del método sleep, sino del método join de la clase Thread (línea 67); la diferencia es sutil pero importante: join es un método que debe recibir un objeto concreto a través de su respectivo mensaje (invocación) y, por la naturaleza misma de su funcionamiento, también puede generar la excepción InterruptedException. En este sentido, el diálogo entre los objetos podría interpretarse de la siguiente manera:

"El hilo de main envía un mensaje a Hilo (de CicloMensaje) para decirle que lo esperará 1 segundo más para que termine."

    Si el hilo de CicloMensaje sigue vivo (líneas 63 y 68) y el tiempo transcurrido excede la paciencia preestablecida (líneas 42 ó 48), entonces Hilo es interrumpido (línea 71) antes de que haya impreso todos sus mensajes e imprime un mensaje de reproche antes de salir (línea 34).

Consideraciones.

    El Ejemplo Ejemplo MensajesPausados ilustra dos conceptos importantes:

  1. Interrupciones que pueden presentarse al trabajar con hilos (InterruptedException).
  2. Las pausas de los hilos basadas en tiempo (método sleep( ) de la clase Thread).

    El método sleep hace que el hilo actual interrumpa su ejecución (se duerma) por un tiempo aproximado de cuatro segundos. Es importante que el lector tome en cuenta esto último y nunca estará de más el repetirlo, ya que es un error suponer una exactitud en el tiempo de interrupción debido a que existen gastos de gestión en la administración de los hilos (overhead). Aún peor que lo anterior, es el tratar de sincronizar hilos con base en este criterio de tiempo.

    La sincronización de hilos es una labor no trivial y no debe ser minimizada. Más adelante en el blog se muestran las bases de la sincronización en la entrada Sincronización, misma que proporciona al lector una aproximación un poco más detallada pero finalmente introductoria al respecto.

    El uso del método sleep conlleva la potencial generación de la excepción InterruptedException, la cual es una excepción verificada, es decir, debe ser atrapada o relanzada para que el programa compile y funcione, razón por la cual la línea 12 del Ejemplo Ejemplo MensajesPausados adopta este último mecanismo. Para obtener más información con respecto a los aspectos mencionados en este párrafo, consulte en el Contenido temático del blog más ampliamente los conceptos relacionados con las excepciones.

    Finalmente se conmina encarecidamente al lector a que consulte el API para ampliar y complementar la información respecto a los métodos utilizados en los ejemplos.


17 de diciembre de 2020

Creación de hilos.

   En Java hay básicamente dos formas de generar un nuevo hilo de ejecución:

  1. Crear una instancia de la clase Thread.
  2. Crear una instancia de una clase que sea ejecutable, es decir, una clase que implemente la interfaz Runnable.

   En esta entrada se presentarán dos ejemplos de cada una de estas formas.

Clase Thread.
   El Ejemplo HolaHilo muestra la creación de un hilo cuando una clase (HolaHilo) hereda de la clase Thread (línea 8).

   Todas las subclases de Thread deberían sobreescribir el método run( ), ya que la definición de dicho método establece el comportamiento que tendrá el hilo creado, es decir, el conjunto de acciones a realizar por el hilo, son definidas dentro de este método (líneas 10-12) que, para este caso, consiste únicamente en imprimir un mensaje en la salida estándar.

   Observe cómo en el método main (líneas 14-17) se crea, en la línea 15 un nuevo hilo de ejecución (hilo), mismo que se hecha a andar en la línea 16 a través del método start( ).

   El método start( ) hace que el hilo receptor del mensaje inicie su ejecución, dentro de esta inicialización del hilo ocurren distintos aspectos de gestión que la máquina virtual de Java tiene que realizar para que las cosas funcionen, como la invocación al método run( ) del hilo correspondiente.

   Es importante hacer notar al lector que nunca hay un llamado explícito al método run( ), sino que ocurre un llamado implícito como parte de las gestiones y acciones que realiza el método start( ).

   Por último, el Ejemplo HolaHilo2 muestra una variación del ejemplo anterior.

   En las líneas 11 y 19 se ha hecho uso del método currentThread( ) de la clase Thread, el cuál regresa la instancia del hilo que se está ejecutando (hilo actual), esto con la finalidad de que el método getName( ) obtenga el nombre asignado a dicha instancia. Esta propiedad del hilo en cuestión es utilizada para imprimirla en la salida estándar en las líneas 12 y 20 respectivamente.

   Finalmente, la línea 17 (comentada) utiliza el método setName( ) para asignarle un nombre al hilo ("Hilo Jr."). Se deja como ejercicio para el lector descomentar dicha línea, recompilar, ejecutar y comparar las salidas de los ejemplos correspondientes y comprender la diferencia.

Interfaz Runnable.
   El Ejemplo HolaEjecutable muestra la creación de un hilo utilizando la interfaz Runnable (línea 8).

   Una interfaz en Java es básicamente un contrato que la clase que la implementa debe cumplir, es decir, la clase que implementa una interfaz se compromete a definir todos los métodos que establezca dicha interfaz.

   Para el caso de la interfaz Runnable, el único método que hay que definir es el método run( ) (líneas 10-12), cuya única misión es imprimir un mensaje en la salida estándar.

   En la línea 15 se crea una instancia de la clase HolaEjecutable (ejecutable), misma que es utilizada como argumento para el constructor del hilo creado en la línea 16.

   Si todo sale bien, en la línea 17 existen dos hilos de ejecución:

  1. El de main.
  2. El que se creo en la línea 16 (hilo).

   Note aquí también que nunca hay un llamado explícito al método run( ), sino que más bien hay un llamado implícito en alguna parte del método start( ) (línea 17), el cuál es el encargado de realizar la gestión necesaria para que el hilo recién creado se introduzca en el entorno de ejecución de la máquina virtual de Java. Se recomienda ampliamente al lector el consultar el API para obtener más detalles acerca de la clase Thread y de la interfaz Runnable.

   Finalmente el Ejemplo HolaEjecutable2 muestra una variación del ejemplo descrito con anterioridad. Note particularmente las líneas 11 y 20 en donde se ha hecho uso del método currentThread( ) de la clase Thread, el cuál regresa la instancia del hilo que se está ejecutando (hilo actual); por otro lado, el método getName( ) obtiene el nombre asignado a dicha instancia. Esta propiedad del hilo en cuestión es utilizada para imprimirla en la salida estándar en las líneas 12 y 21 respectivamente.

El intercambio de la líneas 17 y 18 para que mutuamente se excluyan se deja como ejercicio para el lector.


15 de diciembre de 2020

Hilos.

   En un enfoque tradicional de programación, las sentencias y expresiones se ejecutan de manera secuencial, en la programación concurrente, este mismo grupo de sentencias y expresiones se ejecutan de manera concurrente, entendiéndose por concurrencia a la capacidad de las diferentes partes o unidades de un programa para ejecutarse fuera de orden o en orden parcial sin afectar el resultado final.

   Abusando de la simplificación, puede entenderse a la concurrencia como la capacidad de un software o equipo de cómputo para realizar más de una tarea al mismo tiempo.

   En la programación concurrente existen dos unidades básicas de ejecución: procesos e hilos. En el lenguaje de programación Java la programación concurrente se basa principalmente en hilos (threads) aunque estos están estrechamente relacionados con los procesos.

    Los detalles y aspectos relacionados con los procesos gestionados por un sistema operativo quedan fuera de los alcances de este blog; sin embargo, es preciso que el lector tenga al menos una idea general de los aspectos inherentes relacionados con los procesos, razón por la cual se esbozarán algunos conceptos.

   Un proceso es una instancia en ejecución de un programa. En este sentido, un proceso es un entorno de ejecución autocontenido y habitualmente tiene un conjunto de recursos completos, privados, básicos y fundamentales relacionados con su ejecución; así, cada proceso tiene además su propio espacio de memoria.

   Los hilos son también llamados procesos ligeros. Tanto los hilos como los procesos proveen un entorno de ejecución sin embargo, la creación de un nuevo hilo requiere en lo general de menos recursos que la creación de un nuevo proceso.

Programación con hilos.

   Los hilos habitualmente existen dentro de un proceso. Cada proceso tiene al menos un hilo denominado hilo principal. Los hilos comparten los recursos asignados al proceso, tales como la memoria y los archivos asociados por ejemplo, lo cual los hace muy eficientes pero potencialmente problemáticos.

   Desde el punto de vista de los programadores de aplicaciones Java, todo inicia con un solo hilo: main, y este hilo tiene la capacidad de crear nuevos hilos y a partir de ahí generar una programación concurrente a través de dichos hilos.

   Cada hilo en Java está asociado con una instancia de la clase Thread por lo que se recomienda ampliamente al lector revisar la especificación de dicha clase en el API y todo el apartado relacionado con la concurrencia de los tutoriales de Java.

By Hooman Mallahzadeh - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=100534098


15 de marzo de 2018

Ejercicios selectos (POO).

Parte I.
  1. En el Ejemplo Parvulo3 se hizo referencia a la invocación del mensaje obtenNombre (línea 17) dentro del método mensaje. Cambie el método obtenNombre por el atributo nombre y compruebe lo descrito en el blog.
  2. Considere el Ejemplo PruebaParvulo3 ¿Qué sucede si en lugar de acceder al atributo nombre por medio del método obtenNombre (líneas 9 y 11) se intenta acceder directamente al atributo a través del operador punto (.) como se hace para enviar mensajes a los objetos? Para probar lo anterior cambie la expresión: parvulo.obtenNombre() por la expresión: parvulo.nombre recompile y analice lo que sucede.
  3. Modifique el Ejemplo PruebaParvulo3 para que genere más de un objeto de la clase Parvulo3 del Ejemplo Parvulo3, de tal forma que tenga un conjunto de al menos tres (objetos) párvulos. Para los objetos creados definales una identidad a cada uno de los (objetos) párvulos instanciados por medio de la asignación de un nombre distinto a cada uno de ellos, envíeles mensajes, experimente y juegue con ellos; recuerde que sus objetos son, al fin y al cabo, niñ@s pequeñ@s.
  4. Para el Ejemplo Parvulo4 defina y añada un constructor sin argumentos, de tal forma que la labor del constructor sea definir su propio nombre (el nombre del lector) al atributo nombre. Modifique en consecuencia la clase del Ejemplo PruebaParvulo4 para que haga uso del constructor recién definido, y pueda así comprobar su funcionamiento.
  5. La entrada POO (mensajes y métodos) abordó el tema de la sobrecarga y ejemplificó el concepto utilizando sobrecarga de constructores. La sobrecarga de métodos es análoga a la de constructores. Modifique el Ejemplo Parvulo5 para que implemente la sobrecarga de métodos de la siguiente manera:
    1. Defina un nuevo método con la siguiente firma: public void mensaje(String m).
    2. La responsabilidad del nuevo método mensaje será la de imprimir en la salida estándar la leyenda "Mensaje recibido" seguido de la cadena m.
    3. Realice una clase de prueba (puede basarse en la del Ejemplo PruebaParvulo5) para comprobar el funcionamiento de todos los métodos, especialmente el método sobrecargado mensaje.
  6. Considere el Ejemplo Parvulo5 y modifíquelo para que, en lugar de uno, defina tres atributos de la clase (String): nombre, apellido1, apellido2 y en base a lo anterior:
    1. Agregue el método set correspondiente para cada uno de los atributos en base a lo descrito en el blog.
    2. Agregue el método get correspondiente para cada uno de los atributos en base a lo descrito en el blog.
    3. Agregue un constructor para que sea posible construir un objeto (párvulo) utilizando únicamente el nombre y el primer apellido.
    4. Modifique el método mensaje para que, en caso de que alguno de los atributos del objeto en cuestión sea null, dicho atributo sea ignorado y en consecuencia, no se imprima en la salida estándar.
    5. Construya una clase de prueba que demuestre tanto el funcionamiento de todas las posibles opciones de construcción de objetos, como el adecuado funcionamiento de los métodos de tipo setget, así como el método mensaje.
    6. Construya una nueva clase de prueba que pida los datos (nombre, apellido1 y apellido2) desde la entrada estándar, y que con éstos se modifique los datos de un objeto determinado.
  7. El Ejemplo PruebaHerencia tiene la línea 42 comentada, por lo que el compilador y la JVM la ignoran. Si la descomenta ¿qué piensa que sucederá?, ¿compilará?, ¿se ejecutará?, ¿imprimirá algo en la salida estándar?, ¿fallará la ejecución? Después de analizar el programa responda dichas preguntas y corrobore su respuesta con la experimentación.
  8. Modifique el Ejemplo Persona para que contenga más atributos; es decir, para que las características de una persona estén más completas (por ejemplo el RFC). No olvide agregar métodos de tipo set y get por cada uno de los atributos que añada. En este sentido, agregue también los siguientes cambios:
    1. Cambie el atributo nombre por una distribución más convencional: nombre, primer apellido, y segundo apellido.
    2. Agregue un atributo que represente la dirección o domicilio.
    3. Agregue un atributo que represente el sexo: femenino o masculino, según sea el caso.
    4. Después de lo anterior:
      1. Compruebe el adecuado funcionamiento de la clase Persona respecto de las modificaciones recién hechas. Asegúrese de comprobar que los métodos de tipo set y get trabajan de la manera esperada.
      2. Compruebe que con los cambios realizados a la clase Persona los aspectos relacionados con la herencia de la clase Científico del Ejemplo Cientifico, siguen funcionando sin ningún tipo de problema.
      3. Modifique la clase Científico de manera análoga, y compruebe también que el mecanismo de la herencia permanece inalterado. Para la clase Científico agregue dos atributos (y sus correspondientes métodos de acceso):
        1. Institución o lugar donde labora.
        2. Grado de estudios.
      4. Agregue nuevas acciones, comportamientos o responsabilidades a las clases Persona y Cientifico (como el método toString( ) por ejemplo), y asegúrese de probar su adecuado funcionamiento. No olvide experimentar con los conceptos de sobrecarga y sobre escritura.
    5. Considere el atributo RFC. Este atributo no debería ser modificado con cualquier información, por lo que un método del tipo set no sería recomendable. Para este tributo considere más bien:
      1. Armarlo en base a la información de una persona (investigar cómo):
        1. Primer apellido.
        2. Segundo apellido
        3. Nombre (s).
        4. Fecha de nacimiento
      2. Solicitar la homoclave desde la entrada estándar.
  9. Defina la clase Fecha. Para este ejercicio:
    1. Determine los atributos que debe tener una fecha.
    2. Escriba los métodos del tipo set y get para modificar y acceder respectivamente a los atributos de la clase.
    3. Piense también en el comportamiento mínimo que debería tener dicha clase e impleméntelo mediante los métodos correspondientes, como por ejemplo el método public String obtenerFecha( ) ó public String toString( ).
    4. Escriba también un método (public void diaSiguiente( )), de tal forma que cuando un objeto de la clase Fecha (por ejemplo fecha) reciba el mensaje correspondiente (fecha.diaSiguiente( )), el método deberá modificar los atributos pertinentes para generar la fecha del día siguiente al que actualmente se refiere el objeto fecha. Tome en consideración que hay meses con 31 días (enero, marzo, mayo, julio, agosto, octubre y diciembre) y otros con 30 (abril, junio, septiembre y noviembre). No olvide tomar en cuenta los años bisiestos para el mes de febrero.
    5. Construya también la clase que permita probar el funcionamiento correspondiente de los métodos descritos, así como el comportamiento adicional que haya usted definido para los objetos de la clase Fecha.
Parte II.
    1. Construya un ejemplo completamente diferente al visto en el blog, en donde ponga de relieve los conceptos de envío de mensajes, herencia, polimorfismo, encapsulamiento y ocultación de información. Puede apoyarse de alguna jerarquía de clases que sea de su interés o preferencia.
    2. Muchos de los "supervillanos" humanos de los comics son científicos excepcionales (Dr. Doom, Lex Luthor, Dr. Octupus, Norman Osborn, etc.) ¿Qué comportamiento común podría identificar en este tipo de personajes que le permita extender y/o redefinir el comportamiento de un Científico? Siguiendo la idea plasmada para las clases Persona y Cientifico, genere la nueva clase Supervillano y añada, redefina y pruebe el comportamiento que defina.
    3. Considere el Ejemplo PruebaPolimorfismo. En la línea 28, a una referencia de la clase Persona le es asignado un objeto de la clase Cientifico sin problemas; de hecho el programa funciona, lo cual sugiere que es posible instanciar objetos de subclases (Cientifico) a referencias de súper clases (Persona). ¿Será posible hacer lo mismo al revés?, es decir, ¿será posible que a una referencia de la clase Cientifico se le pueda asignar un objeto de la clase Persona? Haga un programa que implemente esto último y determine sus conclusiones.
    4. El formato internacional de fecha definido por IS0 8601, intenta estandarizar los distintos problemas y variaciones para los formatos de las fechas definiendo un sistema numérico como se muestra a continuación:  AAAA-MM-DD, donde: AAAA es el año [todos los dígitos, p.ej. 2032] MM es el mes [01 (Enero) hasta 12 (Diciembre)] DD es el día [01 hasta 31, de acuerdo al mes]. Así por ejemplo, la fecha "20 de Septiembre de 2059", en este formato internacional se escribe como: 2059-09-20.
      1. Defina una clase con todos los elementos necesarios para que maneje apropiadamente una fecha del formato ISO 8601.
      2. Considere el manejo de errores correspondiente (Fechas no válidas. Deberá tomar en consideración también los años bisiestos).
      3. Derivaciones de fecha: formato europeo (utilizado en México) y formato EE.UU.:
        1. Fecha en formato europeo: 20-09-2059
        2. Fecha en formato EE.UU: 09-20-2059
        3. Polimorfismo en la impresión de la fecha (ISO, europeo y EE.UU.). Ejercite el concepto de enlazado dinámico (dynamic binding).
      4. Para este ejercicio resultaría de utilidad, aprovechando la herencia, lo realizado en el Ejercicio 9 de la Parte I.
    5. En el Ejemplo figuras geométricas (dynamic binding), la línea 14 de la declaración de la interfaz FiguraGeometricaPropiedades aparece como comentario. Borre los símbolos del comentario de tal forma que se añada a la declaración de la interfaz el método obtenPerimetro( ), y en consecuencia, se deba definir (escribir el código) dicho método en las clases que implementan la interfaz. Realice todos los cambios necesarios y pruebe su funcionamiento.

5 de junio de 2017

Pilas (implementación).

   La representación de una pila, como una secuencia de nodos, se muestra en la siguiente figura. Los detalles de su implementación se desarrollarán a continuación.

Abstracción de una pila como una secuencia de nodos.

 Pila primitiva.

   Esta sección describe la implementación de una pila primitiva. Se ha denominado de esta forma debido a que implementa una estructura de datos que almacena un tipo de dato primitivo: int.

   La definición de la clase fundamental para la implementación de la estructura de datos se muestra en el Ejemplo NodoPrimitivo. Los detalles de este tipo de implementación de clases autorreferidas se han discutido con anterioridad en la entrada Abstracción de estructuras de datos y no se repetirán aquí, por lo que se sugiere al lector que la revise nuevamente y se tome el tiempo necesario para comparar el Ejemplo NodoPrimitivo con el Ejemplo Nodo antes de continuar.

   La implementación de la estructura de datos pila se muestra en el Ejemplo PilaPrimitiva, y su explicación se centrará únicamente en los métodos push, pop, estaVacia e imprime.

   El método estaVacia (líneas 37-39) realiza una verificación bastante simple: si el tope de la pila es igual a null, regresa verdadero (está vacía), si no, regresa falso (existe al menos un elemento).

   El método push por su parte (líneas 18-23), recibe como argumento el elemento a insertar en la pila (elemento), y se apoya del método estaVacia y de los constructores para realizar la inserción:

  • Si la pila está vacía (línea 19), se crea un nuevo nodo con el elemento correspondiente (línea 20) para el atributo dato, y null en su atributo siguiente.
  • Si la pila no está vacía (línea 21), se crea un nuevo nodo con el elemento correspondiente para el atributo dato y con el tope actual en su atributo siguiente (línea 22). Así mismo, note que el tope es actualizado para hacer referencia al nodo recién creado.
   Ahora bien, observe que el método pop (líneas 26-34) posee una característica particular: el método lanza (throws) una excepción ExcepcionEDVacia (línea 26) lo cual quiere decir que, en determinadas condiciones, el método puede lanzar una excepción; para el caso del método pop, la condición consiste en intentar eliminar un elemento de la pila cuando ésta está vacía.

   La excepción ExcepcionEDVacia definida en el Ejemplo ExcepcionEDVacia es en realidad bastante simple, ya que delega toda la responsabilidad a RuntimeException que es la clase de la que deriva, y lo único que hace es establecer un identificador para la excepción a través de sus constructores, los cuales en realidad utilizan el constructor de la super clase (clase padre) a través de la cláusula super. Es importante que el lector recuerde esta excepción, ya que será la excepción que utilizarán todas las estructuras de datos que se implementan en el blog.

   Ahora bien, cuando el programador se enfrenta a la necesidad de lanzar una excepción cabe la pregunta ¿y de qué tipo? Se puede utilizar una excepción escrita por alguien más o se puede hacer una propia. Se debería hacer clases de excepción propias cuando se responda de manera afirmativa a alguna de las siguientes preguntas, de otra forma, debería usarla una excepción escrita por alguien más (vea Creating Exception Classes):
  • ¿Necesita un tipo de excepción que no está representada en el API de Java?
  • ¿Sus usuarios se verán beneficiados si pueden diferenciar sus excepciones de aquellas lanzadas por clases escritas por alguien más?
  • ¿Su código lanza más de una excepción relacionada?
  • Si utiliza una excepción escrita por alguien más, ¿sus usuarios tendrán acceso a esas excepciones?
  • ¿Su paquete de clases debería ser independiente y auto contenido?
   Continuando con el Ejemplo PilaPrimitiva, el método pop verifica (línea 27) si la pila está vacía, si lo está, crea y lanza la excepción correspondiente (línea 28); en caso contrario, recupera el dato almacenado (línea 30), y desecha el nodo correspondiente al hacer que el tope haga ahora referencia al elemento siguiente del tope actual (línea 31), para finalmente regresar el dato recuperado (línea 33). En Java no hay una eliminación o liberación explícita de memoria, dicha labor es delegada al recolector de basura el cual se encarga, grosso modo, de identificar aquellos objetos que no estén siendo referidos para entonces liberar la memoria que utilizan.

   Por último, en el método imprime (líneas 42-57), si la estructura de datos está vacía (línea 43), se reporta (línea 44); en caso contrario, se realiza un recorrido por todos los nodos de la estructura para imprimir su contenido (líneas 47-54).

   La clase de prueba para la pila primitiva del Ejemplo PilaPrimitiva, se presenta en el Ejemplo PruebaPila. Observe cómo en la línea 6 se crea la pila y se utiliza un constructor sin argumentos.

   Las líneas 9-12 realizan la inserción en la pila de los números del cero al nueve; por cada inserción se imprime todo el contenido de la pila, como se muestra en la siguiente figura:

Ejemplo de inserción de elementos en la pila primitiva. Se muestra la salida del Ejemplo PruebaPila.

    Por otro lado, las líneas 15-24 realizan la eliminación de los elementos de la pila. Dicho fragmento de código intenta eliminar once elementos (líneas 17-18) de la pila (recuerde que sólo fueron insertados diez elementos, por lo que al intentar eliminar el décimo primero, se generará la excepción ExcepcionEDVacia), y dado que el método pop puede lanzar una excepción, el código involucrado en la eliminación debe estar dentro de una cláusula try-catch-finally, la cual permite atrapar (cachar) las excepciones que un método pudiera lanzar.

   Si se genera un excepción ExcepcionEDVacia, ésta es atrapada y el flujo de control se envía al ámbito de la cláusula catch en donde se realiza el correspondiente tratamiento de la excepción. Para el caso del Ejemplo PruebaPila el manejo de la excepción consiste únicamente en imprimir la secuencia de eventos (en forma de pila) que dieron lugar a la excepción; sin embargo, es importante aclarar que el manejo de una excepción puede ser tan elaborado como en un momento dado se requiera.

   La salida correspondiente a la eliminación de elementos de la pila primitiva, se muestra en la siguiente figura:

Ejemplo de eliminación de elementos en la pila primitiva. Se muestra la salida del Ejemplo PruebaPila.

Pila genérica.
   Esta sección generaliza la implementación de una pila de tal forma que la estructura de datos tenga la capacidad de almacenar objetos genéricos; es decir, objetos de cualquier clase.

   Como primer paso, es preciso que el lector se tome el tiempo que considere necesario para comparar el Ejemplo NodoPrimitivo discutido en la sección anterior, con el Ejemplo NodoG. Es importante resaltar que, aunque distintos, ambos ejemplos son esencialmente equivalentes.

La primera diferencia que salta a la vista, además del nombre de la clase, es la notación <T>. Dicha notación se utiliza en Java para especificar que la clase gestiona objetos genéricos, es decir, que el tipo o la clase de los objetos que almacena no está especificada en la definición de la misma, sino que se especifica en el momento de la instanciación del objeto (observe la línea 6 del Ejemplo PruebaPilaGenerica).

   La línea 7 del Ejemplo NodoG define que la clase del atributo dato es T, es decir, un genérico, y por lo tanto el atributo siguiente es una referencia a un objeto que almacena objetos genéricos.

   Observe cómo ahora los constructores y los métodos de tipo set reciben como argumentos objetos genéricos T (líneas 10, 14 y 19), y referencias a objetos que a su vez almacenan objetos genéricos (líneas 14 y 27); mientras que los métodos de tipo get regresan genéricos (línea 23), o referencias a objetos que almacenan objetos genéricos (línea 31).

   Ahora bien, las consideraciones hechas para el Ejemplo NodoG respecto a los genéricos, son las mismas que debe tomar en cuenta para el Ejemplo Pila, por lo que una vez más se pide encarecidamente al lector que compare este último con el Ejemplo PilaPrimitiva discutido en la sección anterior tomando en consideración lo explicado hasta este momento para los genéricos.

   Observe que en ninguna parte del Ejemplo Pila se hace explícita una clase específica para el genérico T, por lo que también aquí se hace una gestión de genéricos en directa relación con los genéricos utilizados en el Ejemplo NodoG.

   En resumen, la clase NodoG del Ejemplo NodoG define nodos que almacenan objetos genéricos (nodos genéricos), los cuales son utilizados de manera conveniente por la clase Pila del Ejemplo Pila para conformar una estructura de datos dinámica que almacena objetos o nodos genéricos en forma de pila.

   El Ejemplo PruebaPilaGenerica muestra la clase de prueba para la pila genérica del Ejemplo Pila. La línea más importante del Ejemplo PruebaPilaGenerica es la línea 6, ya que es en ella en donde finalmente se define la clase de objetos que contendrá (almacenará) la pila: Integer.

   Consulte la sección Genéricos de la entrada Ejemplos selectos de transición para ampliar un poco más la información y los detalles acerca de los genéricos en Java. En el Ejemplo PilaC se muestra la implementación alternativa de una pila utilizando un ArrayList del API de Java; mientras que en el EjemploPruebaPilaC se presenta su correspondiente clase de prueba. Adicionalmente, se proporcionan al lector dos ejemplos adicionales que utilizan colecciones (estructuras de datos) del API: Stack y ArrayDeque (Deque); tómese el tiempo de estudiarlos y revisar la información correspondiente en el API.

   Finalmente, asegúrese de comprobar que la salida del Ejemplo PruebaPilaGenerica corresponde, en esencia, con la salida del Ejemplo PruebaPila para la inserción de datos (push), y que lo correspondiente ocurre también para la eliminación de elementos (pop).

6 de marzo de 2017

Ejercicios selectos (transición).

  1. Investigue más acerca del concepto de máquina virtual y de los bytecodes ¿Fue Java el primer lenguaje de programación en incorporar dichos conceptos?
  2. Para el programa del Ejemplo Bienvenido2 pruebe lo que sucede si elimina el espacio que aparece al final de la cadena Bienvenid@. No olvide volver a compilar.
  3. Considere el Ejemplo Bienvenido3 e investigue qué otras secuencias de escape existen, también infórmese si dichas secuencias coinciden o no con las que se utilizan en el lenguaje de programación C.
  4. En base a lo expuesto para el Ejemplo Bienvenido4 investigue qué otros especificadores de formato existen. Infórmese también si dichos especificadores coinciden o no con los que se utilizan en el lenguaje de programación C.
  5. Escriba un programa que imprima en la salida estándar su nombre (el nombre del lector). El nombre o nombres deberá aparecer en el primer renglón, el primer apellido en el segundo renglón y el segundo apellido en el tercer renglón.
  6. Utilice el Ejemplo Lectura y cambie la clase Integer por el tipo primitivo int. Compruebe si es necesario o no hacer algún otro tipo de cambio para que el programa funcione.
  7. En base a lo descrito en el Ejemplo Lectura modifique dicho ejemplo para agregar las cuatro operaciones aritméticas: suma, resta multiplicación y división. Los operadores aritméticos son los tradicionales: +, -, * y /. Realice lo anterior para números enteros representados como objetos (Integer), y como tipos de datos primitivos.
  8. Modifique el Ejemplo Lectura para procesar ahora números Float y Double. Tome en cuenta que deberá consultar el API para cambiar el método nextInt por el método apropiado. Investigue también si existen los tipos de datos primitivos correspondientes (float y double).
  9. Con base en lo descrito en el Ejemplo Lectura, modifique dicho ejemplo para que solicite al usuario su nombre completo. Tome en cuenta que deberá leer tres cadenas como se describe a continuación, y que su programa deberá imprimir el nombre completo por apellidos en una sola línea:
    1. Su nombre o nombres.
    2. Su primer apellido.
    3. Su segundo apellido

 10. Para el Ejemplo If, ¿qué sucede si cambia la estructura de selección if por if-else? ¿Se obtiene el mismo funcionamiento? Si cambia el funcionamiento, ¿por qué? Determine su respuesta y después corroborela haciendo los cambios correspondientes. No olvide volver a compilar.

 11. Escriba un programa que presente un menú con las cuatro operaciones aritméticas y una opción para salir. Su programa deberá procesar la opción y realizar la operación correspondiente, así como mantenerse repitiendo el menú hasta que se proporcione la opción de "Salir". Investigue también si el lenguaje Java incorpora una estructura de selección múltiple (switch) como la del lenguaje C. Para este ejercicio, basta con utilizar una estructura if-else anidada. El menú deberá ser como se describe a continuación:

  • Suma.
  • Resta.
  • Multiplicación.
  • División
  • Salir.

 12. Modifique el Ejemplo Arreglo para que, utilizando el procesamiento de argumentos en la línea de comandos como el que se hizo en el Ejemplo MainArgs acepte tres argumentos. Su programa deberá generar un arreglo de n enteros con un valor inicial para el primer elemento y un incremento para los demás. Observe que los elementos de args son cadenas, por lo que tendrá que consultar el API para utilizar métodos de conversión de cadenas a números enteros.

Sugerencia: consulte el método parseInt de la clase Integer. Los argumentos a procesar en la línea de comandos son:

  • Tamaño del arreglo (n).
  • Valor inicial (valor).
  • Incremento (incremento).

 13. Siguiendo la idea del ejercicio anterior, escriba un programa que procese el tamaño (n) de un arreglo desde la línea de comandos y:

  • Genere un arreglo de enteros de tamaño n.
  • Solicite y almacene n enteros en el arreglo.
  • Determine, haciendo un recorrido del arreglo, el número mayor.
  • Determine, haciendo un recorrido del arreglo, el número menor.
  • Determine, haciendo un recorrido del arreglo, el promedio (media aritmética).
  • Determine, haciendo un recorrido del arreglo, la moda (el número que más veces aparece).
  • Determine, haciendo un recorrido del arreglo, si un número determinado está o no en el arreglo (búsqueda lineal).

 14. Consulte al menos la entrada Excepciones para introducirse en el tema y ampliar un poco más su conocimiento respecto al uso y manejo básico de excepciones en Java.
 15. Considere el Ejemplo Coleccion. Investigue en el API las colecciones, particularmente la colección ArrayList y determine el funcionamiento del programa antes de compilarlo y ejecutarlo en la máquina virtual de Java. Compruebe su deducción respecto al funcionamiento con la salida real del programa. 


Un vistazo al lenguaje Java.

   El lenguaje de programación Java (Java es una marca registrada de Oracle Corporation) es amado por muchos y odiado por otros y no participaré en un debate estéril y ocioso. Sólo diré que, como casi todo en la vida, Java tiene sus ventajas y desventajas; lo que es innegable es que es un lenguaje de programación ampliamente difundido y utilizado y en ese sentido, resulta importante conocerlo y familiarizarse con él, independientemente de las posturas ideológicas y preferencias personales.

   Esta entrada del blog no es un tratado del lenguaje; es más, ni siquiera se acerca a un resumen, es más bien un compendio de referencia a la mano para el lector familiarizado con algún lenguaje de programación.

   La referencia obligada para esta entrada y para todo el blog en general es el API (Application Programming Interface) correspondiente a la versión 8 del lenguaje.

Orígenes y características.
   Java es un lenguaje de programación originalmente desarrollado por James Gosling cuando trabajaba en la desaparecida empresa Sun Microsystems, la cual fue adquirida por Oracle Corporation en el año 2009.
 
   El lenguaje fue publicado oficialmente en 1995 y deriva su sintaxis de C y C++; sin embargo, cuenta con menos facilidades de bajo nivel que cualquiera de ellos. Por otro lado, las aplicaciones o programas de Java son generalmente compiladas a bytecodes, los cuales pueden procesarse en cualquier máquina virtual Java (JVM) sin importar la arquitectura de la computadora, permitiendo así una mayor portabilidad del software entre plataformas.
 
   Java es un lenguaje de programación de propósito general, concurrente, basado en clases, y orientado a objetos, diseñado específicamente para tener tan pocas dependencias de implementación como fuera posible. Su intención es permitir que los desarrolladores de aplicaciones escriban el programa una vez, y lo ejecuten en cualquier dispositivo (WORA - Write Once, Run Anywhere).

Estructura general de una clase.
   La estructura general de la definición de una clase en Java es como la que se muestra en el Ejemplo EstructuraJava.

   Las líneas 1-5 muestran el uso de comentarios, se recomienda ampliamente iniciar la definición de cada clase con un comentario que describa el propósito de la clase. También es posible definir comentarios de la forma que se muestra en las líneas 10, 13, 17, y 18.

   La línea 6 muestra la definición de paquetes. Un paquete es un conjunto de clases relacionadas y la palabra reservada package, especifica a qué paquete pertenecen todas las clases definidas en el archivo fuente.

   En Java es posible definir más de una clase por archivo pero sólo una de ellas puede ser pública, y su nombre (identificador) debe corresponder con el nombre del archivo que la contiene. Para el caso del ejemplo anterior, el nombre de la clase es EstructuraJava y el del archivo EstructuraJava.java.

   La línea 7 muestra la importación de clases. Las clases se encuentran ubicadas en paquetes dentro del API, y cuando una clase utiliza alguna clase definida en algún otro paquete, ésto se especifica con la palabra reservada import.

   Las clases se definen por medio de la cláusula class y un identificador. En general, Java define tres niveles de acceso:
  1. public (público) hace que una clase, método o atributo sea accesible para cualquier otra clase.
  2. protected (protegido) hace a un método o atributo accesible únicamente por las clases del mismo paquete, o por subclases de la clase.
  3. private (privado) hace a un método o atributo accesible únicamente desde su propia clase.
   La línea 11 muestra la definición de atributos, los cuales están compuestos por un identificador para el objeto (objeto), y el nombre de la clase de la que se derivará o instanciará. La definición de todos los atributos necesarios para la clase, siguen la misma estructura.

   Las líneas 14-15 muestran la definición de constructores. En la instanciación o generación de nuevos objetos, los objetos se construyen, por lo que no es posible crear un nuevo objeto sin un constructor. Un constructor es el código que se procesa cuando se crea un objeto a través de la cláusula new. La construcción de un objeto es un mecanismo mucho más elaborado del que aquí se describe, pero la explicación a detalle queda fuera de los alcances de esta entrada, y con la generalidad que se ha comentado antes, basta por ahora.

   Finalmente, las líneas 19-20 muestran la definición de métodos. Los métodos siguen los niveles de acceso descritos con anterioridad, pueden o no regresar un objeto de alguna clase (ClaseR) o un tipo de dato primitivo; y puede haber tantos como servicios quiera proporcionar la clase que los define.

Bienvenid@ a Java.
   El Ejemplo Bienvenido1 muestra el primer programa en Java que se describirá. La línea 5 del ejemplo muestra el punto de entrada de cualquier programa en Java: el método main, éste método siempre tendrá la forma que se muestra, la cual se conoce en general como la firma del método. La explicación y los detalles de args se revisará en los Ejemplos selectos de transición.

   En Java sólo es posible enviar mensajes a objetos que definan métodos públicos; sin embargo, la clase Bienvenido1 establece que su método main es static, lo cual quiere decir que puede ser invocado (llamado) sin una instancia específica u objeto que lo reciba. De hecho ésto es un mecanismo que utiliza Java para poder generar clases de utilerías o servicios sin embargo su uso debería ser minimizado, ya que se incurre en el estilo de la programación estructurada al utilizar dichas clases como bibliotecas de funciones.

   Finalmente, la forma más común de imprimir mensajes en la salida estándar (pantalla), es la que aparece en la línea 6 del Ejemplo Bienvenido1. System es una clase de servicios que tiene Java, y entre dichos servicios está el método println del objeto out, el cual recibe como argumento un objeto que representa una cadena (String), y lo envía a la salida estándar imprimiendo un salto de línea al final. La salida del Ejemplo Bienvenido1 se muestra en la siguiente figura:

Salida del Ejemplo Bienvenido1.

   Una versión ligeramente modificada del Ejemplo Bienvenido1 se muestra en el Ejemplo Bienvenido2.

   Observe ahora que en la línea 6 se ha cambiado el método println por el método print. Este último hace lo mismo que el método println con la diferencia de que el método print no imprime un salto de línea al final, de ahí que se haya dejado un espacio al final de la cadena Bienvenid@. La salida del Ejemplo Bienvenido2 es idéntica a la del Ejemplo Bienvenido1.

   Un tercera variación del Ejemplo Bienvenido1 se muestra en el Ejemplo Bienvenido3, el cual muestra el uso de la secuencia de escape \n para introducir un salto de línea en cualquier parte de la cadena que se desea imprimir. La salida del Ejemplo Bienvenido3 se muestra en la siguiente figura:

Salida del Ejemplo Bienvenido 3.

   Finalmente se presenta el Ejemplo Bienvenido4, el cual hace uso de la función printf para imprimir un mensaje en la pantalla. Aquellos lectores que estén familiarizados con el lenguaje de programación C se sentirán cómodos utilizando dicha función.

   Sin entrar mucho en detalles, sólo se indicará que %s es un especificador de formato que le indica a la función printf que imprima una cadena, misma que tomará después de la primera coma (,). Es importante señalar que por cada especificador de formato (tres en el ejemplo) debe existir su correspondiente cadena. La salida del Ejemplo Bienvenido4 es idéntica a la salida del Ejemplo Bienvenido3.

Compilación.
   Existen diferentes entornos de programación o (IDE - Integrated Development Environment) que pueden ser utilizados para desarrollar programas en Java, tales como JavaBeans, JCreator, Eclipse, etc., se recomienda al lector buscar y familiarizarse con alguno de ellos o con algún otro IDE que sea de su preferencia.

   Esta sección describe muy brevemente los pasos para la compilación desde la línea de comandos, ya que los programas de ejemplo de todo el blog pueden ser visualizados y editados en cualquier editor de texto, y compilados con el compilador de Java (javac) sin necesidad de un IDE. Es importante aclarar que se asume que se tiene instalado el jdk (java development kit). Si tiene dudas al respecto, consulte los detalles de instalación del jdk en la página oficial de Java.

   Para saber la versión de Java que tiene instalada, puede escribir desde la línea de comandos:

   $ javac -version

   Lo que deberá aparecer en su pantalla es la versión correspondiente del compilador; si aparece un mensaje distinto, es probable que no tenga instalado el jdk o que las rutas de acceso no sean las correctas.

   Para compilar el programa del Ejemplo Bienvenido1 (descrito en párrafos anteriores), tiene que escribir:

   $ javac Bienvenido1.java

   Cuando un programa se compila siguiendo la idea planteada, se buscarán todas las clases requeridas dentro del directorio de trabajo actual, mismo que corresponde al directorio en donde se haya ejecutado el compilador de Java. Si alguna clase tuviera algún problema, se reportará dicha situación antes de volver a visualizar el símbolo del sistema ($); en otro caso, se visualiza de manera casi inmediata el símbolo del sistema y observará que se han creado nuevos archivos, los cuales corresponden a los nombres de las clases pero con extensión class, mismos que representan los bytecodes que interpreta la máquina virtual de Java.

   La máquina virtual de Java o JVM es la encargada de interpretar y procesar los bytecodes que representan las instrucciones del programa compilado. La JVM está representada por el programa java, por lo que, para ejecutar un programa en Java, se debe proporcionar a la JVM la clase principal, la cual es la que contiene el método main:

   $ java Bienvenido1

   La salida del comando anterior, debería ser el mensaje presentado en la siguiente figura:

Salida esperada después de compilar y ejecutar el Ejemplo Bienvenido1.  




1 de febrero de 2017

Referencias sugeridas.

  1. Bracha, Gilad, "Generics", Oracle Corporation, The Java Tutorials, (último acceso febrero 2017).
  2. Budd, Timothy A., "An Introduction to Object-Oriented Programming", Addison Wesley.
  3. Byte Magazine, Smalltalk, Volume 06 Number 08, A McGraw Hill Publication.
  4. Deitel, H. M. y Deitel, P. J., "Cómo Programar en Java", Prentice Hall.
  5. Goldberg Adele and Robson David, "Smalltalk-80 The Language and its Implementation", Addison-Wesley Publishing Company.
  6. Gökel Canol, Computer Programming using GNU Smalltalk, GNU Free Documentation License 1.3.
  7. Kuhn, Thomas Samuel., "The Structure of Scientific Revolutions", University of Chicago Press.
  8. Langsam, Yedidyah, Augenstein, M. J. and Tenenbaum, A. M., "Estructuras de Datos con C y C++", Prentice Hall Hispanoamericana.
  9. Mark Allen Weiss, "Estructuras de Datos en Java", Addison Wesley.
  10. McConnell, Steve, "Code Complete", Microsoft Corporation.
  11. Oracle, The Java Tutorials, Java Documentation.
  12. Shalloway, Alan and Trott James R., "Design Patterns Explained A New Perspective on Object Oriented Design", Addison Wesley.
  13. Sierra, Kathy and Bates, Bert, "Sun Certified Programmer & Developer for Java 2", Mc Graw Hill/Osborne.
  14. Schildt, Herbert, "C++: The Complete Reference", Mc Graw Hill Osborne.
  15. Stroustrup, Bjarne, "El Lenguaje de Programación C++", Addison Wesley.
  16. Unified Modeling Language Web Site, (último acceso febrero 2017).
  17. Wirth, Niklaus, "Algoritmos y Estructuras de Datos", Prentice Hall.