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


12 de abril de 2020

Implementación del ADT racional.

   El Ejemplo Racional muestra una posible implementación de ADT racional definido en la entrada Tipos de Datos Abstractos (ADT).

   Las líneas 7 y 8 definen los atributos de un número racional (p / q). Los detalles relacionados con los métodos de tipo set y get (líneas 25-41), ya han sido comentados en la sección Métodos y atributos de la entrada POO (mensajes y métodos) y no se repetirán aquí.

   Por otro lado, los constructores definidos (líneas 10-23) requieren de una ampliación en su explicación, ya que difieren un poco de lo hasta ahora comentado para los constructores.

   El constructor principal o base es el definido en las líneas 18-23. Observe que dicho constructor recibe dos argumentos, mismos que representan el numerador (n) y el denominador (d) del número racional que se desea inicializar. En la línea 19, el constructor realiza una verificación del denominador (cláusula de condición), de tal forma que si éste es cero, se crea (new) y lanza (throw) una excepción (consulte la sección de Excepciones de la entrada Ejemplos selectos de transición) para indicar el error a través de la clase ArithmeticException. Note también que el mismo comportamiento es considerado en las líneas 30 y 31 para el método estableceDenominador. Una posible salida del programa al presentarse la situación anteriormente planteada, se muestra en la siguiente figura:

Una salida de la excepción generada en el Ejemplo PruebaRacional al intentar crear un número racional cuyo denominador sea cero.
 
   Los constructores de las líneas 10-12 y 14-16 se apoyan del constructor base de las líneas 18-23 para realizar la inicialización del objeto a través del uso de la cláusula this. La cláusula this es una referencia que tienen todos los objetos hacia sí mismos, por lo que en el contexto de los constructores invoca al constructor sobre cargado que corresponda con el tipo y número de argumentos que envía, que para el caso de las líneas 11 y 15 corresponden con el constructor base de las líneas 18-23.

   La implementación de las operaciones de suma y producto aparecen en las líneas 43-50 y 52-59 respectivamente (la implementación de las operaciones resta y división se dejan como ejercicio para el lector). Observe que en las líneas 46 y 47 (55 y 56) se accede directamente a los atributos p y q a través del operador punto (.), es decir, sin hacer uso de los métodos de acceso set y get; ésto es así debido a que el objeto s (m) es de la misma clase que define al método suma (multiplica), por lo que el acceso es concedido sin ningún tipo de inconveniente (recuerde lo planteado en el Ejercicio 2 de la entrada Ejercicios selectos (POO) y analice la diferencia).

   Por otro lado, note también que para el objeto (this) que recibe el mensaje suma (multiplica), los atributos p y q de las líneas 46 y 47 (55 y 56) son accedidos sin ningún tipo de operador ni mensaje, es decir, son accedidos por contexto, ya que el objeto que recibe el mensaje conoce cuáles son sus propios atributos y métodos por lo que dentro del método la expresión:

p * r.obtenDenominador( )

es equivalente a la expresión:

this.p * r.obtenDenominador( )

   Por último en lo que respecta a los método suma y multiplica, es importante resaltar que ambos métodos utilizan un objeto local al método (s y m respectivamente) para almacenar el resultado que generan y poder regresarlo como valor de retorno del método, por lo que es posible deducir que si un método requiere de variables u objetos para realizar su labor, éstos pueden declarase locales al método. En este sentido, sería un error respecto al paradigma declarar dichas variables u objetos como atributos de la clase, ya que no describen alguna propiedad o característica inherente a los objetos que deriven de la clase, sino que son sólo elementos necesarios para el almacenamiento del resultado y la realización del cálculo correspondiente a la responsabilidad (comportamiento) del método. Lo anterior se aplica aún cuando se utilizara una sola variable para almacenar el resultado de cualquiera de las cuatro operaciones aritméticas.

   Finalmente el método toString (líneas 61-63) requiere una mención aparte, ya que este método sobre escribe (razón por la cual se preservó el nombre del método) y en consecuencia re define el comportamiento previamente establecido en el método la clase Object del API de Java. Dicho método tiene como responsabilidad el regresar una representación en cadena del objeto que recibe el mensaje.

   La clase de prueba del Ejemplo Racional es la del Ejemplo PruebaRacional. Una posible salida corresponde a la mostrada en la siguiente figura:

Una posible salida de la ejecución del Ejemplo PruebaRacional. 
 
   Por último, aunque el Ejemplo PruebaRacional se explica a sí mismo, se resaltarán los siguientes aspectos:
  • Las líneas 8 y 9 crean los números racionales r1 y r2, donde r1 = 1 / 3 y r2 = 2 / 5.
  • Las líneas 13 y 14 envían los mensajes suma y multiplica respectivamente al objeto r1. Note que el argumento de ambos métodos es r2 y que al valor de retorno de dichos métodos (un número racional), le es enviado de manera implícita el mensaje toString para obtener la representación en cadena de los resultados correspondientes. Esto último sucede también con las líneas 11 y 12 pero para los objetos r1 y r2 respectivamente.

Mensajes y métodos (C++).

Mensajes y métodos (C++).
   Una de las principales inquietudes que expresan los estudiantes acerca del paradigma orientado a objetos está relacionada con los mensajes y los métodos. En este sentido, se iniciará con un ejemplo sumamente sencillo el cual irá evolucionando progresivamente con la finalidad de ilustrar dichos conceptos.

   Métodos sin argumentos.
   El Ejemplo Parvulo1 muestra la definición de la clase parvulo1, misma que no contiene atributos pero sí un método cuyo identificador o nombre es "mensaje" (línea 10).

   El método "mensaje" se encuentra definido en las líneas 13-15, y tiene como única responsabilidad la impresión en la salida estándar de una cadena (los detalles generales del funcionamiento de cout << son presentados en la entrada Un vistazo a C++). La definición de un método en C++ tiene en general la siguiente estructura:

tipo_de_retorno clase::metodo();

donde : : es el operador de resolución de alcance, y esencialmente le indica al compilador que "metodo" pertenece o está en el alcance de "clase".

   Con base en base a lo anterior, la clase parvulo1 es una especie de plantilla capaz de generar objetos con una única responsabilidad o servicio. Para poder instanciar objetos el Ejemplo Parvulo1 muestra la función main en las líneas 17-21, en donde se crea un objeto "parvulo" de la clase parvulo1 (línea 18) y se le envía el mensaje "mensaje" (línea 20). En general, esta será la estructura de la mayoría de los ejemplos que se utilizarán en el blog.

   Una vez que el objeto existe, es decir, una vez que el objeto ha sido instanciado, es posible entonces interactuar con él por medio de mensajes para solicitarle acciones que correspondan con las responsabilidades o servicios definidos para dicho objeto que, para el caso del objeto parvulo, es sólo una.

   La solicitud del único servicio que puede proporcionar el objeto parvulo se realiza a través del mensaje "mensaje", el cual es enviado (invoca la función miembro correspondiente) al objeto en la línea 20:

parvulo.mensaje();

   La expresión anterior se interpreta como: "se envía el mensaje <<mensaje>> al objeto parvulo". El envío de mensajes no es otra cosa más que la invocación explícita de un método (función miembro de la clase) a través de su identificador; es utilizar el método correspondiente para realizar una acción, misma que está relacionada con el comportamiento o las responsabilidades definidas en la clase del objeto en cuestión.

   La salida del Ejemplo Parvulo1 se muestra a continuación. Asegúrese de comprender lo descrito hasta aquí antes de continuar.

Salida del Ejemplo PruebaParvulo1.

   Métodos con argumentos.
   En esta sección se presenta una versión ligeramente distinta del Ejemplo Parvulo1, en el cual se presentó el envío de mensajes sin argumentos. Tómese el tiempo necesario para comparar el Ejemplo Parvulo2 de esta sección con el Ejemplo Parvulo1 de la sección anterior, y compruebe que son esencialmente iguales.

   El parámetro "nombre" en el método mensaje constituye la diferencia de los dos ejemplos anteriormente mencionados. En el Ejemplo Parvulo2 el método mensaje (línea 10) define ahora la capacidad de recibir un argumento de tipo cadena (un objeto de la clase string) referido por el objeto "nombre". El método "mensaje" imprime en la salida estándar un cadena conformada por un texto predefinido (línea 14), y la concatenación de la cadena referida por "nombre" y "\n".

   Por otro lado, la función main es también similar a la del Ejemplo Parvulo1 excepto en la forma en que se envía el mensaje al objeto "parvulo" (línea 21). Observe que el mensaje enviado tiene ahora una cadena como argumento, la cual es referida por el objeto "nombre" del método mensaje (línea 13) en el momento en que se le envía el mensaje "mensaje" al objeto "parvulo".

   Asegúrese de realizar una labor analítica al comparar línea a línea tanto las clases parvulo1 y parvulo2 como las respectivas funciones main, así como de comprender sus diferencias con base en lo descrito hasta ahora.

   La salida del Ejemplo Parvulo2 se muestra a continuación:


Salida del Ejemplo PruebaParvulo2.

   Note en las líneas 10 y 13 que el parámetro ha sido declarado como "const string& nombre". Esto es una conveniencia más que una regla, dicha declaración le indica al compilador que el parámetro "nombre" es una referencia constante a una cadena, esto quiere decir, que no se genera una copia de la cadena y que ésta no va a poder ser modificada dentro del método o función miembro. En general, por eficiencia, esta será la estructura que se utilice para la mayoría de los métodos de este tipo.

   Métodos y atributos.
   Por el principio de ocultación de información es conveniente que únicamente se tenga acceso a los atributos de una clase a través de su interfaz. La interfaz de un objeto está representada por sus métodos públicos (public).

   El Ejemplo Parvulo3 hace uso de dicho principio al definir, con un acceso restringido o privado (private), el atributo nombre (línea 10) para la clase parvulo3. En C++, por definición, todo lo que se defina dentro de una clase es privado a no ser que se especifique lo contrario (línea 12), por lo que es muy común omitir la palabra reservada private. Es posible intercalar los modificadores de acceso (public, protected y private), se utilizará el último definico hasta que se encuentre otro o el fin de la clase.

   Observe que la clase del ejemplo en cuestión declara también tres métodos públicos (líneas 13-15), los cuales establecen la interfaz de los objetos que sean instanciados:
  • establece_nombre: este tipo de métodos son utilizados comúnmente para ajustar o establecer el valor de un atributo; y al menos en principio, debería haber un método de este tipo por cada atributo que contenga la clase, siempre y cuando se requiera manipular desde el exterior. Este tipo de métodos son comúnmente referidos como métodos de tipo set.
  • obten_nombre: este tipo de métodos son utilizados comúnmente para recuperar u obtener el valor de un atributo, y al igual que antes, debería haber, al menos en principio, un método de este tipo por cada atributo que contenga la clase siempre y cuando se requiera visualizar desde el exterior. Este tipo de métodos son comúnmente referidos como métodos de tipo get.
  • mensaje: este ha sido descrito con anterioridad. Note que el método está definido como el del Ejemplo Parvulo1 (sin argumentos), pero funciona como el del Ejemplo Parvulo2. Asegúrese de comprender ésto antes de continuar.
   Observe que el método mensaje (líneas 26-29) se vale del método obten_nombre (línea 22) para acceder al atributo nombre pero no necesariamente tiene que ser así, ya que un método puede acceder directamente a los atributos de la clase, siempre y cuando ambos estén definidos dentro de la misma clase (encapsulamiento). Por otro lado, si el atributo tiene un nivel de acceso protegido (protected), los métodos de las clases derivadas por herencia también podrían acceder a los atributos de la clase de la que derivan (clase padre o super clase).

   Los métodos de tipo set sólo deben trabajar sobre un atributo, por lo que habitualmente sólo reciben un argumento, mismo que se corresponde con la clase (tipo) del atributo a modificar (string para el caso del Ejemplo Parvulo3). De manera análoga, los métodos de tipo get no reciben ningún tipo de argumento, y la clase de objetos que regresan está directamente relacionada con la clase (tipo) del atributo al que accederán (string para el caso del Ejemplo Parvulo3).

   Es importante hacer notar también que la clase Parvulo3, a diferencia de las anteriores, establece ya una característica representada y definida por el atributo nombre, de tal forma que los objetos derivados de ella (párvulos) compartirán dicha característica (un párvulo es un niño pequeño en edad preescolar), aunque cada uno poseerá su propia identidad (nombre).

   El Ejemplo PruebaParvulo3 muestra también la función main. Al igual que en los ejemplos anteriores, observe que en la línea 32 se define y crea el objeto parvulo.

   Las líneas 34 y 36 hacen uso del método obten_nombre a través del envío del mensaje correspondiente, mientras que la línea 35 envía el mensaje establece_nombre con un argumento específico. Finalmente, la línea 37 muestra el envío del mensaje mensaje, el cual debería resultarle ya familiar al lector.

   La salida del Ejemplo PruebaParvulo3 se muestra a continuación. Observe que inicialmente el atributo nombre del objeto parvulo aparece en blanco.

Salida del Ejemplo PruebaParvulo3.

   Métodos y constructores.
   Un constructor es un método especial que se invoca implícitamente cuando se crea o instancia un objeto. Cuando se crea un objeto, se genera la memoria necesaria para representarlo y se le inicializa por medio de un constructor. En este sentido, puede haber más de una forma de inicializar un objeto y, en consecuencia, más de un constructor.

   El identificador o nombre de los métodos constructores debe coincidir con el identificador o nombre de la clase que los define; con base en lo anterior, se tiene que puede haber más de un constructor compartiendo el mismo identificador. El mecanismo que tienen los constructores para distinguirse entre sí, es a través del número y clase o tipo de los parámetros que definen, constituyendo con ello una forma de polimorfismo comúnmente conocida como sobrecarga (la sobrecarga se trata con un poco más de detalle en la siguiente sección). En la creación del objeto, se invoca el constructor que coincida con el número y tipo de argumentos proporcionados en su definición.

   Las líneas 14 y 20-23 del Ejemplo Parvulo4 muestran la declaración y la definición respectivamente del constructor parvulo4; observe cómo el nombre del constructor coincide exactamente con el nombre de la clase. Dicho constructor define un único parámetro n el cual es un objeto de la clase string.

   La inicialización que hace el constructor Parvulo4 consiste únicamente de la asignación del objeto n al atributo representado por el objeto nombre (línea 22). Note que en C++ la forma de hacer lo anterior dentro de un constructor, es invocar al constructor correspondiente del parámetro que se desea inicializar (línea 21).

   Es importante mencionar que la labor de inicialización de un constructor en particular puede ser un proceso mucho más elaborado que el descrito hasta ahora, y que estará en función directa de las responsabilidades de inicialización con que se quiera dotar al constructor, así como de la problemática en particular que se esté resolviendo por medio del objeto en cuestión.

   Los elementos restantes de la clase Parvulo4 han sido previamente abordados en las secciones anteriores por lo que no se repetirán aquí.

   El Ejemplo Parvulo4 tiene también la función main (líneas 37-44). Observe que a diferencia de los ejemplos anteriores, en la línea 38 se proporciona un argumento al constructor, lo cual hace que desde la creación del objeto parvulo se le esté definiendo un nombre.

   Observe también cómo la secuencia de mensajes subsecuentes coincide con las del Ejemplo PruebaParvulo3, excepto que en la línea 41 del Ejemplo Parvulo4, se envía el mensaje establece_nombre al objeto parvulo para asignarle el nombre completo (con apellidos) al párvulo.

   Asegúrese de comprender antes de continuar, que la siguiente muestra la salida correspondiente a la ejecución del Ejemplo Parvulo4:


Salida del Ejemplo PruebaParvulo4.

   Sobrecarga.
   La sobrecarga (overload) es un tipo de polimorfismo que se caracteriza por la capacidad de poder definir más de un método o constructor con el mismo nombre (identificador), siendo distinguidos entre sí por el número y la clase (tipo) de los argumentos que se definen.

   El Ejemplo Parvulo5 muestra la sobrecarga de constructores. Note que las líneas 13 y 20-20 declaran y definen respectivamente el mismo constructor que el en Ejemplo Parvulo4 excepto por el nombre, y que se ha añadido o sobrecargado un nuevo constructor (líneas 15 y 24-27), el cual recibe tres argumentos que representan el nombre (n), el primer apellido (a1), y el segundo apellido (a2) de un párvulo. Note también la inicialización del atributo "nombre" en la línea 25.

   La sobrecarga de constructores se da porque ambos constructores tiene el mismo identificador (parvulo5) pero distinto número de parámetros.

   No puede existir sobrecarga para constructores o métodos con el mismo identificador y el mismo número o clase (tipo) de parámetros; tiene que haber algo que los distinga entre sí, ya que en otro caso habría ambigüedad y un buen compilador la detectará.

   Los métodos restantes del Ejemplo Parvulo5 ya ha sido comentados y descritos con anterioridad en otras secciones de esta misma entrada.

   Respecto al método main, es también muy similar a los anteriormente explicados. Únicamente cabe resaltar la creación del objeto parvulo en la línea 42. Note que ahora se le proporcionan tres argumentos al constructor, lo cual hace que el constructor utilizado sea el definido en las líneas 24-27.

   La salida del Ejemplo Parvulo5 se muestra a continuación. Compare dicha salida con la del ejemplo anterior y asegúrese de comprender la diferencia.

Salida del Ejemplo PruebaParvulo5.

20 de marzo de 2020

Implementación de Herencia (Java).

   El Ejemplo Persona muestra la implementación en Java de la clase Persona mostrada en el diagrama de clases de la entrada POO (herencia). Todos los detalles de la clase Persona de dicho ejemplo ya han sido expuestos ahí, por lo que se recomienda encarecidamente al lector los revise y analice, y que los compare con lo descrito para diagrama UML correspondiente.

   Por otro lado, el Ejemplo Cientifico contiene la implementación de la clase Científico mostrada también en el diagrama de clases mencionado anteriormente. Observe que la herencia es implementada en Java a través del uso de la cláusula extends (línea 6), seguida de la clase de la que se hereda (Persona) en la definición de la clase Científico.

   Es importante que el lector note el uso de la cláusula super en los constructores (líneas 11 y 18). El uso en Java de dicha cláusula sólo puede hacerse en el contexto de constructores y sirve para delegarle al constructor correspondiente de la clase padre, los detalles de inicialización del objeto en cuestión que, para el caso de la clase Científico, corresponden a los constructores de dos y tres argumentos de la clase Persona respectivamente. Asegúrese de comprender también ésto antes de continuar.

   Al igual que antes, se deja como ejercicio de análisis para el lector tanto los detalles restantes de implementación de la clase Científico, como el asegurarse de comprender la relación del diagrama UML con el código del Ejemplo Científico.

   En este punto, es importante también que el lector ponga especial atención en los métodos mensaje del Ejemplo Persona (líneas 61-63), y del Ejemplo Científico (líneas 33-35), ya que serán fundamentales para comprender la sobre escritura (override) de métodos que se discute más adelante en la clase de prueba (Ejemplo PruebaHerencia).

   La clase de prueba para la herencia se presenta en el Ejemplo PruebaHerencia y se describe brevemente a continuación. La línea 10 define el objeto persona y genera una instancia de la clase Persona con características específicas; note que el constructor que está siendo utilizado es el definido en las líneas 24-28 del Ejemplo Persona.

   Observe cómo en las líneas 13, 15, 19 y 21 del Ejemplo PruebaHerencia se envían mensajes específicos al objeto persona para:
  1. Obtener su nombre.
  2. Cambiarle el nombre.
  3. Volver a obtener su nombre.
  4. Solicitarle comer.
  5. Solicitarle un mensaje.
   La salida de lo anterior se ve expresada en las primeras cuatro líneas de la siguiente figura:

Salida del Ejemplo PruebaHerencia.
 
    Por otro lado, la línea 26 define y crea el objeto cientifico como una instancia de la clase Cientifico. Al igual que antes, note cómo el constructor utilizado es el definido en las líneas 17-20 del Ejemplo Cientifico.

   A su vez, las líneas 28 y 30 del Ejemplo PruebaHerencia realizan algo análogo a lo que se hizo con el objeto persona, es decir, envían mensajes al objeto cientifico para obtener y cambiar el nombre del científico.

   La línea 32 por su parte, cambia la especialidad del científico a través de un mensaje de tipo set.

   Finalmente, las líneas 34, 36, 38 y 40 envían mensajes específicos al objeto cientifico para:
  1. Obtener su nombre.
  2. Solicitarle comer.
  3. Solicitarle un mensaje.
  4. Solicitarle un mensaje especial.
   Note cómo para el mensaje mensaje las respuestas del objeto persona y el objeto cientifico difieren por completo, debido a que aunque el mensaje es el mismo, el objeto que los recibe responde de manera distinta a dicho mensaje; esto último fue lo que en su momento se definió como polimorfismo. En este ejemplo el polimorfismo está expresado en su forma de sobre escritura (override) de métodos.