0% encontró este documento útil (0 votos)
73 vistas25 páginas

Informe 2

Este documento explica cómo las clases en Java pueden implementar múltiples interfaces al mismo tiempo a pesar de no permitir herencia múltiple. Una interfaz define una lista de métodos públicos abstractos que debe proporcionar cualquier clase que la implemente. Una clase puede implementar varias interfaces separadas por comas. Las interfaces pueden extenderse entre sí para heredar métodos, y cualquier clase que implemente una interfaz secundaria debe implementar todos los métodos heredados.

Cargado por

MELO
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
73 vistas25 páginas

Informe 2

Este documento explica cómo las clases en Java pueden implementar múltiples interfaces al mismo tiempo a pesar de no permitir herencia múltiple. Una interfaz define una lista de métodos públicos abstractos que debe proporcionar cualquier clase que la implemente. Una clase puede implementar varias interfaces separadas por comas. Las interfaces pueden extenderse entre sí para heredar métodos, y cualquier clase que implemente una interfaz secundaria debe implementar todos los métodos heredados.

Cargado por

MELO
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 25

Implementación de interfaces (Implementing Interfaces).

Aunque Java no permite la herencia múltiple, sí permite que las clases implementen cualquier
número de interfaces. Una interfaz es un tipo de datos abstracto que define una lista de métodos
públicos abstractos que debe proporcionar cualquier clase que implemente la interfaz. Una
interfaz también puede incluir una lista de variables constantes y métodos predeterminados, que
cubriremos en esta sección. En Java, una interfaz se define con la palabra clave de interfaz,
análoga a la palabra clave de clase utilizada al definir una clase. Una clase invoca la interfaz
utilizando la palabra clave implements en su definición de clase. Consulte las Figuras 5.4 y 5.5 para
conocer el uso correcto de la sintaxis.

FIGURA 5. 4 Definición de una interfaz

FIGURA 5.5 Implementación de una interfaz

Como puede ver en este ejemplo, una interfaz no se declara una clase abstracta, aunque tiene
muchas de las mismas propiedades de la clase abstracta. Observe que se asumen los
modificadores de método en este ejemplo, abstracto y público. En otras palabras, ya sea que los
proporcione o no, el compilador los insertará automáticamente como parte de la definición del
método. Una clase puede implementar múltiples interfaces, cada una separada por una coma,
como en el siguiente ejemplo:

Nota: creando los abstract de WalksOnFourLegs, HasTrunk, Herbivore. Elephant no marca errores.
En el ejemplo, si alguna de las interfaces definiera métodos abstractos, se requeriría la clase
concreta Elephant para implementar esos métodos. Una novedad en Java 8 es la noción de
métodos de interfaz estáticos y predeterminados, que cubriremos al final de esta sección. .

Definición de una interfaz (Defining an Interface).

Puede resultar útil pensar en una interfaz como un tipo especializado de clase abstracta, ya que
comparte muchas de las mismas propiedades y reglas que una clase abstracta. La siguiente es una
lista de reglas para crear una interfaz, muchas de las cuales debe reconocer como adaptaciones de
las reglas para definir clases abstractas.

1. Las interfaces no se pueden instanciar directamente.

2. No se requiere una interfaz para tener ningún método.

3. Es posible que una interfaz no esté marcada como final.

4. Se supone que todas las interfaces de nivel superior tienen acceso público o predeterminado, y
deben incluir el modificador abstracto en su definición. Por lo tanto, marcar una interfaz como
privada, protegida o final desencadenará un error del compilador, ya que es incompatible con
estos supuestos.

5. Se supone que todos los métodos no predeterminados de una interfaz tienen modificadores
abstractos y públicos en su definición. Por lo tanto, marcar un método como privado, protegido o
final desencadenará errores del compilador, ya que son incompatibles con las palabras clave
abstractas y públicas.

La cuarta regla no se aplica a las interfaces internas, aunque las clases y las interfaces internas no
están dentro del alcance del examen OCA. Las tres primeras reglas son idénticas a las tres primeras
reglas para crear una clase abstracta. Imagina que tenemos una interfaz WalksOnTwoLegs,
definida de la siguiente manera:

Se compila sin problemas, ya que no se requieren interfaces para definir ningún método. Ahora
considere los siguientes dos ejemplos, que no se compilan:

hay que agregar llaves { }


El primer ejemplo no se compila, ya que WalksOnTwoLegs es una interfaz y no se puede instanciar
directamente. El segundo ejemplo, WalksOnEightLegs, no se compila ya que es posible que las
interfaces no se marquen como finales por la misma razón que las clases abstractas no se pueden
marcar como finales.

La cuarta y quinta regla sobre "palabras clave asumidas" pueden ser nuevas para usted, pero debe
pensar en ellas de la misma manera que el compilador inserta un constructor sin argumentos
predeterminado o una declaración super () en su constructor. Puede proporcionar estos
modificadores usted mismo, aunque el compilador los insertará automáticamente si no lo hace.
Por ejemplo, las siguientes dos definiciones de interfaz son equivalentes, ya que el compilador las
convertirá en el segundo ejemplo:

Nota: los dos códigos funcionan, obviamente no pueden estar los dos.

En este ejemplo, la palabra clave abstracta se agrega primero automáticamente a la definición de


interfaz. Luego, cada método se antepone con palabras clave abstractas y públicas. Si el método ya
tiene alguna de estas palabras clave, no se requiere ningún cambio. Veamos un ejemplo que
infringe las palabras clave asumidas:
Cada línea de este ejemplo no se compila. La primera línea no se compila por dos razones.
Primero, se marca como final, que no se puede aplicar a una interfaz ya que entra en conflicto con
la palabra clave abstracta asumida. A continuación, se marca como privado, lo que entra en
conflicto con el acceso requerido público o predeterminado para las interfaces. La segunda y
tercera línea no se compilan porque se supone que todos los métodos de interfaz son públicos y
marcarlos como privados o protegidos arroja un error de compilación. Finalmente, la última línea
no se compila porque el método está marcado como final y dado que se supone que los métodos
de interfaz son abstractos, el compilador lanza una excepción para usar palabras clave abstractas y
finales en un método.

Asegúrese de revisar el ejemplo anterior y comprender por qué cada una de las líneas no se
compila. Es probable que haya al menos una pregunta en el examen en la que una interfaz o un
método de interfaz utilice un modificador no válido.

Heredar una interfaz (Inheriting an Interface).

Hay dos reglas de herencia que debe tener en cuenta al ampliar una interfaz:

1. Una interfaz que extiende otra interfaz, así como una clase abstracta que implementa una
interfaz, hereda todos los métodos abstractos como sus propios métodos abstractos.

2. La primera clase concreta que implementa una interfaz, o extiende una clase abstracta que
implementa una interfaz, debe proporcionar una implementación para todos los métodos
abstractos heredados.

Como una clase abstracta, una interfaz puede extenderse usando la palabra clave extend. De esta
manera, la nueva interfaz secundaria hereda todos los métodos abstractos de la interfaz principal.
Sin embargo, a diferencia de una clase abstracta, una interfaz puede extender múltiples interfaces.
Considere el siguiente ejemplo:
Cualquier clase que implemente la interfaz Seal debe proporcionar una implementación para
todos los métodos en las interfaces principales, en este caso, getTailLength () y
getNumberOfWhiskers ().

¿Qué pasa con una clase abstracta que implementa una interfaz? En este escenario, la clase
abstracta se trata de la misma manera que una interfaz que extiende otra interfaz. En otras
palabras, la clase abstracta hereda los métodos abstractos de la interfaz pero no es necesaria para
implementarlos. Dicho esto, como una clase abstracta, la primera clase concreta en extender la
clase abstracta debe implementar todos los métodos abstractos heredados de la interfaz.
Ilustramos esto en el siguiente ejemplo:

Nota: para que funciona hay que implementar todos los métodos abstract @Override
(getTailLength(), getNumberOfWhiskers())

En este ejemplo, vemos que HarborSeal es una clase abstracta y se compila sin problemas.
Cualquier clase que amplíe HarborSeal deberá implementar todos los métodos en la interfaz
HasTail y HasWhiskers. Alternativamente, LeopardSeal no es una clase abstracta, por lo que debe
implementar todos los métodos de interfaz dentro de su definición. En este ejemplo, LeopardSeal
no proporciona una implementación para los métodos de la interfaz, por lo que el código no se
compila.

Clases, interfaces y palabras clave (Classes, Interfaces, and Keywords).

A los creadores de exámenes les gustan las preguntas que combinan la terminología de la clase y
la interfaz. Aunque una clase puede implementar una interfaz, una clase no puede extender una
interfaz. Asimismo, mientras que una interfaz puede extender otra interfaz, una interfaz no puede
implementar otra interfaz. Los siguientes ejemplos ilustran estos principios:
El primer ejemplo muestra una clase
que intenta extender una interfaz que
no se compila. El segundo ejemplo
muestra una interfaz que intenta
extender una clase, que tampoco se
compila.

Tenga cuidado con los ejemplos del examen que combinan definiciones de clase e interfaz.
Asegúrese de que la única conexión entre una clase y una interfaz sea con la sintaxis de la interfaz
de implementación de la clase.

Métodos abstractos y herencia múltiple (Abstract Methods and Multiple Inheritance).

Dado que Java permite la herencia múltiple a través de interfaces, es posible que se pregunte qué
sucederá si define una clase que hereda de dos interfaces que contienen el mismo método
abstracto:
En este escenario, las firmas para los dos métodos de interfaz eatPlants () son compatibles, por lo
que puede definir una clase que cumpla con ambas interfaces simultáneamente:

¿Por qué funciona esto? Recuerde que los métodos de interfaz en este ejemplo son abstractos y
definen el "comportamiento" que debe tener la clase que implementa la interfaz. Si dos métodos
de interfaz abstracta tienen comportamientos idénticos, o en este caso la misma firma de método,
la creación de una clase que implementa uno de los dos métodos implementa automáticamente el
segundo método. De esta manera, los métodos de interfaz se consideran duplicados ya que tienen
la misma firma. ¿Qué sucede si los dos métodos tienen firmas diferentes? Si el nombre del método
es el mismo pero los parámetros de entrada son diferentes, no hay conflicto porque esto se
considera una sobrecarga del método. Demostramos este principio en el siguiente ejemplo:
En este ejemplo, vemos que la clase que implementa ambas interfaces debe proporcionar
implementos de ambas versiones de eatPlants (), ya que se consideran métodos separados. Tenga
en cuenta que no importa si el tipo de retorno de los dos métodos es el mismo o diferente, porque
el compilador trata estos métodos como independientes.

Desafortunadamente, si el nombre del método y los parámetros de entrada son los mismos pero
los tipos de retorno son diferentes entre los dos métodos, la clase o interfaz que intenta heredar
ambas interfaces no se compilará. La razón por la que el código no se compila tiene menos que ver
con las interfaces y más con el diseño de clases, como se discutió en el Capítulo 4. No es posible en
Java definir dos métodos en una clase con el mismo nombre y parámetros de entrada pero
diferentes tipos de retorno. Dadas las siguientes dos definiciones de interfaz para Herbivore y
Omnivore, el siguiente código no se compilará:
El código no se compila, ya que la clase define dos métodos con el mismo nombre y parámetros de
entrada pero diferentes tipos de retorno. Si elimináramos cualquiera de las definiciones de
eatPlants (), el compilador se detendría porque a la definición de Bear le faltaría uno de los
métodos requeridos. En otras palabras, no hay ninguna implementación de la clase Bear que
herede de Herbivore y Omnivore que el compilador acepte.

El compilador también lanzaría una excepción si define una interfaz o clase abstracta que hereda
de dos interfaces en conflicto, como se muestra aquí:
Incluso sin detalles de implementación, el compilador detecta el problema con la definición
abstracta y evita la compilación.

Con esto concluye nuestra discusión sobre los métodos de interfaz abstracta y la herencia
múltiple. Volveremos a esta discusión poco después de que presentemos los métodos de interfaz
predeterminados. Verá que las cosas funcionan de manera un poco diferente con los métodos de
interfaz predeterminados.

Variables de interfaz (Interface Variables).

Ampliemos nuestro análisis de interfaces para incluir variables de interfaz, que se pueden definir
dentro de una interfaz. Al igual que los métodos de interfaz, se supone que las variables de
interfaz son públicas. Sin embargo, a diferencia de los métodos de interfaz, también se supone que
las variables de interfaz son estáticas y finales.

Aquí hay dos reglas de variables de interfaz:

1. Se supone que las variables de interfaz son públicas, estáticas y finales. Por lo tanto, marcar una
variable como privada o protegida desencadenará un error del compilador, al igual que marcar
cualquier variable como abstracta.

2. El valor de una variable de interfaz debe establecerse cuando se declara, ya que está marcada
como final.

De esta manera, las variables de interfaz son esencialmente variables constantes definidas en el
nivel de interfaz. Debido a que se supone que son estáticos, se puede acceder a ellos incluso sin
una instancia de la interfaz. Al igual que en nuestro ejemplo anterior de CanFly, las siguientes dos
definiciones de interfaz son equivalentes, porque el compilador las convertirá automáticamente
en el segundo ejemplo:
Como vemos en este ejemplo, la compilación insertará automáticamente una final estática pública
en cualquier variable de interfaz constante que encuentre que faltan esos modificadores. También
tenga en cuenta que es una práctica de codificación común usar letras mayúsculas para denotar
valores constantes dentro de una clase. Con base en estas reglas, no debería sorprender que las
siguientes entradas no se compilen:

El primer ejemplo, MAXIMUM_DEPTH, no se compila porque se usa el modificador privado y se


supone que todas las variables de la interfaz son públicas. La segunda línea, UNDERWATER, no se
compila por dos razones. Está marcado como protegido, lo que entra en "conflicto con el supuesto
modificador público", y está marcado como abstracto, lo que entra en conflicto con el supuesto
modificador final. Finalmente, el último ejemplo, TYPE, no se compila porque le falta un valor. A
diferencia de los otros ejemplos, los modificadores son correctos, pero como recordará del
Capítulo 4, debe proporcionar un valor a un miembro final estático de la clase cuando se define.

Métodos de interfaz predeterminados (Default Interface Methods).

Con el lanzamiento de Java 8, los autores de Java han introducido un nuevo tipo de método en una
interfaz, denominado método predeterminado. Un método predeterminado es un método
definido dentro de una interfaz con la palabra clave predeterminada en la que se proporciona un
cuerpo de método. Contraste predeterminado

métodos con métodos "regulares" en una interfaz, que se supone que son abstractos y pueden no
tener un cuerpo de método.

Un método predeterminado dentro de una interfaz define un método abstracto con una
implementación predeterminada. De esta manera, las clases tienen la opción de anular el método
predeterminado si es necesario, pero no es necesario que lo hagan. Si la clase no anula el método,
se utilizará la implementación predeterminada. De esta manera, la definición del método es
concreta, no abstracta.

El propósito de agregar métodos predeterminados al lenguaje Java fue en parte ayudar con el
desarrollo de código y la compatibilidad con versiones anteriores. Imagine que tiene una interfaz
compartida entre docenas o incluso cientos de usuarios a los que le gustaría agregar un nuevo
método. Si solo actualiza la interfaz con el nuevo método, la implementación se interrumpiría
entre todos sus suscriptores, quienes luego se verían obligados a actualizar su código. En la
práctica, esto podría incluso disuadirlo de realizar el cambio por completo. Sin embargo, al
proporcionar una implementación predeterminada del método, la interfaz se vuelve
retrocompatible con el código base existente, al mismo tiempo que brinda a las personas que
desean usar el nuevo método la opción de anularlo.

El siguiente es un ejemplo de un método predeterminado definido en una interfaz:

Este ejemplo define dos métodos de interfaz, uno es un método abstracto normal y el otro es un
método predeterminado. Tenga en cuenta que se supone que ambos métodos son públicos, ya
que todos los métodos de una interfaz son públicos. El primer método termina con un punto y
coma y no proporciona un cuerpo, mientras que el segundo método predeterminado proporciona
un cuerpo. Cualquier clase que implemente IsWarmBlooded puede depender de la
implementación predeterminada de getTemperature () o anular el método y crear su propia
versión.

Tenga en cuenta que el modificador de acceso predeterminado definido en el Capítulo 4 es


completamente diferente del método predeterminado definido en este capítulo. Definimos un
modificador de acceso predeterminado en el Capítulo 4 como la falta de un modificador de
acceso, lo que indica que una clase puede acceder a una clase, método o valor dentro de otra
clase si ambas clases están dentro del mismo paquete. En este capítulo, estamos hablando
específicamente de la palabra clave predeterminada aplicada a un método dentro de una interfaz.
Debido a que se supone que todos los métodos dentro de una interfaz son públicos, el
modificador de acceso para un método predeterminado es público.

Las siguientes son las reglas de método de interfaz predeterminadas con las que debe estar
familiarizado:

1. Un método predeterminado solo puede declararse dentro de una interfaz y no dentro de una
clase o clase abstracta.

2. Un método predeterminado debe estar marcado con la palabra clave predeterminada. Si un


método está marcado como predeterminado, debe proporcionar un cuerpo de método.

3. No se supone que un método predeterminado sea estático, final o abstracto, ya que puede ser
utilizado o anulado por una clase que implemente la interfaz.

4. Como todos los métodos en una interfaz, se supone que un método predeterminado es público
y no se compilará si está marcado como privado o protegido.

La primera regla debería brindarle cierta comodidad, ya que solo verá los métodos
predeterminados en las interfaces. Si los ve en una clase del examen, asuma que el código no se
compilará. La segunda regla solo denota sintaxis, ya que los métodos predeterminados deben usar
la palabra clave predeterminada. Por ejemplo, los siguientes fragmentos de código no se
compilarán:

En este ejemplo, el primer método, eatMeat (), no se compila porque está marcado como
predeterminado pero no proporciona un cuerpo de método. El segundo método, getRequiredFood
Amount (), tampoco se compila porque proporciona un cuerpo de método pero no está marcado
con la palabra clave predeterminada.

A diferencia de las variables de interfaz, que se asumen como miembros de clase estáticos, los
métodos predeterminados no se pueden marcar como estáticos y requieren una instancia de la
clase que implementa la interfaz para ser invocada. Tampoco se pueden marcar como finales o
abstractos, porque se permite que se anulen en subclases, pero no se requiere que se anulen.

Cuando una interfaz extiende otra interfaz que contiene un método predeterminado, puede optar
por ignorar el método predeterminado, en cuyo caso se utilizará la implementación
predeterminada del método. Alternativamente, la interfaz puede anular el de! nición del método
predeterminado utilizando las reglas estándar para anular el método, como no limitar la
accesibilidad del método y utilizar retornos covariantes. Finalmente, la interfaz puede volver a
declarar el método como abstracto, requiriendo que las clases que implementan la nueva interfaz
proporcionen explícitamente un cuerpo de método. Se aplican opciones análogas para una clase
abstracta que implementa una interfaz.

Por ejemplo, la siguiente clase anula un método de interfaz predeterminado y vuelve a declarar un
segundo método de interfaz como abstracto:
En este ejemplo, la primera interfaz, HasFins, define tres métodos predeterminados:

getNumberOfFins (), getLongestFinLength () y doFinsHaveScales (). La segunda interfaz,


SharkFamily, extiende HasFins y anula el método predeterminado getNumberOfFins () con un
nuevo método que devuelve un valor diferente. A continuación, la interfaz SharkFamily reemplaza
el método predeterminado getLongestFinLength () con un nuevo método abstracto, lo que obliga
a cualquier clase que implemente la interfaz SharkFamily a proporcionar una implementación del
método. Finalmente, la interfaz SharkFamily anula el método doFinsHaveScales () pero no marca el
método como predeterminado. Dado que las interfaces solo pueden contener métodos con un
cuerpo marcado como predeterminado, el código no se compilará.

Debido a que los métodos predeterminados son nuevos en Java 8, probablemente habrá algunas
preguntas en el examen sobre ellos, aunque probablemente no serán más difíciles que en el
ejemplo anterior.

Métodos predeterminados y herencia múltiple (Default Methods and Multiple Inheritance).

Es posible que se haya dado cuenta de que al permitir métodos predeterminados en las interfaces,
junto con el hecho de que una clase puede implementar múltiples interfaces, Java esencialmente
ha abierto la puerta a múltiples problemas de herencia. Por ejemplo, ¿qué valor tendría el
siguiente código de salida?
En este ejemplo, Cat hereda los dos métodos predeterminados para getSpeed (), entonces, ¿cuál
usa? Dado que Walk and Run se consideran hermanos en términos de cómo se usan en la clase
Cat, no está claro si el código debe generar 5 o 10. La respuesta es que el código no genera ningún
valor, no se compila.

Si una clase implementa dos interfaces que tienen métodos predeterminados con el mismo
nombre y firma, el compilador arrojará un error. Sin embargo, hay una excepción a esta regla: si la
subclase anula los métodos predeterminados duplicados, el código se compilará sin problemas: se
ha eliminado la ambigüedad sobre qué versión del método llamar. Por ejemplo, el siguiente modi!
La implementación ed de Cat compilará y generará 1:

Puede ver que tener una clase que implementa o hereda dos métodos predeterminados
duplicados obliga a la clase a implementar una nueva versión del método, o el código no se
compilará. Esta regla es válida incluso para clases abstractas que implementan múltiples
interfaces, porque el método predeterminado se puede llamar en un método concreto dentro de
la clase abstracta.
Métodos de interfaz estática (Static Interface Methods).

Java 8 ahora también incluye soporte para métodos estáticos dentro de las interfaces. Estos
métodos se definen explícitamente con la palabra clave estática y funcionan de manera casi
idéntica a los métodos estáticos definidos en clases, como se discutió en el Capítulo 4. De hecho,
en realidad solo hay una distinción

entre un método estático en una clase y una interfaz. Un método estático definido en una interfaz
no se hereda en ninguna clase que implemente la interfaz.

Estas son las reglas del método de interfaz estática con las que debe estar familiarizado:

1. Como todos los métodos en una interfaz, se supone que un método estático es público y no se
compilará si está marcado como privado o protegido.

2. Para hacer referencia al método estático, se debe utilizar una referencia al nombre de la
interfaz.

El siguiente es un ejemplo de un método estático definido en una interfaz:

El método getJumpHeight () funciona como un método estático definido en una clase. En otras
palabras, se puede acceder sin una instancia de la clase usando la sintaxis Hop.getJumpHeight ().
Además, tenga en cuenta que el compilador insertará automáticamente el modi de acceso. er
público ya que se supone que todos los métodos en las interfaces son públicos.

El siguiente es un ejemplo de una clase Bunny que implementa Hop:

Como puede ver, sin una referencia explícita al nombre de la interfaz, el código no se compilará,
aunque Bunny implemente Hop. De esta manera, los métodos de la interfaz estática no son
heredados por una clase que implementa la interfaz. La siguiente versión modificada del código
resuelve el problema con una referencia al nombre de la interfaz Hop:
De ello se deduce, entonces, que una clase que implementa dos interfaces que contienen métodos
estáticos con la misma firma aún se compilará en tiempo de ejecución, porque la subclase no
hereda los métodos estáticos y se debe acceder a ellos con una referencia al nombre de la
interfaz.

Compare esto con el comportamiento que vio para los métodos de interfaz predeterminados en la
sección anterior: el código se compilaría si la subclase anulara los métodos predeterminados y, de
lo contrario, no se compilaría. Puede ver que los métodos de interfaz estática no tienen los
mismos problemas y reglas de herencia múltiple que los métodos de interfaz predeterminados.

Comprensión del polimorfismo (Understanding Polymorphism):

Java admite el polimorfismo, la propiedad de un objeto de adoptar muchas formas diferentes.


Para decirlo con mayor precisión, se puede acceder a un objeto Java utilizando una referencia con
el mismo tipo que el objeto, una referencia que es una superclase del objeto o una referencia que
define una interfaz que el objeto implementa, ya sea directamente o mediante una superclase. .
Además, no se requiere una conversión si el objeto se reasigna a un supertipo o interfaz del
objeto.

Ilustremos esta propiedad de polimorfismo con el siguiente ejemplo:


Resultado:

Lo más importante a tener en cuenta sobre este ejemplo es que solo se crea y se hace referencia a
un objeto, Lemur. La capacidad de una instancia de Lemur para pasar como una instancia de una
interfaz que implementa, HasTail, así como una instancia de una de sus superclases, Primate, es la
naturaleza del polimorfismo.

Una vez que al objeto se le ha asignado un nuevo tipo de referencia, solo los métodos y variables
disponibles para ese tipo de referencia son invocables en el objeto sin una conversión explícita.
Por ejemplo, los siguientes fragmentos de código no se compilarán:

En este ejemplo, la referencia hasTail tiene acceso directo solo a los métodos definidos con la
interfaz HasTail; por lo tanto, no sabe que la variable edad es parte del objeto. Del mismo modo, el
primate de referencia solo tiene acceso a los métodos definidos en la clase Primate y no tiene
acceso directo al método isTailStriped ().
Objeto frente a referencia (Object vs. Reference):

En Java, se accede a todos los objetos por referencia, por lo que, como desarrollador, nunca tiene
acceso directo al objeto en sí. Sin embargo, conceptualmente, debe considerar el objeto como la
entidad que existe en la memoria, asignada por el entorno de ejecución de Java.
Independientemente del tipo de referencia que tenga para el objeto en la memoria, el objeto en sí
no cambia. Por ejemplo, dado que todos los objetos heredan java.lang.Object, todos pueden
reasignarse a java.lang.Object, como se muestra en el siguiente ejemplo:

Aunque al objeto Lemur se le ha asignado una referencia con un tipo diferente, el objeto en sí no
ha cambiado y todavía existe como un objeto Lemur en la memoria. Lo que ha cambiado,
entonces, es nuestra capacidad para acceder a métodos dentro de la clase Lemur con la referencia
lemurAsObject. Sin un envío explícito a Lemur, como verá en la siguiente sección, ya no tendremos
acceso a las propiedades de Lemur del objeto.

Podemos resumir este principio con las siguientes dos reglas:

1. El tipo de objeto determina qué propiedades existen dentro del objeto en la memoria.

2. El tipo de referencia al objeto determina qué métodos y variables son accesibles para el
programa Java.

Por lo tanto, se deduce que cambiar con éxito una referencia de un objeto a un nuevo tipo de
referencia puede darle acceso a nuevas propiedades del objeto, pero esas propiedades existían
antes de que ocurriera el cambio de referencia.

Ilustremos esta propiedad usando el ejemplo anterior en la Figura 5.6. Como puede ver en la
figura, el mismo objeto existe en la memoria independientemente de la referencia que lo apunte.
Dependiendo del tipo de referencia, es posible que solo tengamos acceso a ciertos métodos. Para

Por ejemplo, la referencia hasTail tiene acceso al método isTailStriped () pero no tiene acceso a la
variable age definida en la clase Lemur. Como aprenderá en la siguiente sección, es posible
reclamar el acceso a la variable age mediante la conversión explícita de la referencia hasTail a una
referencia de tipo Lemur.

F I GU R A 5.6

Casting de objetos (Casting Objects):

En el ejemplo anterior, creamos una instancia única de un objeto Lemur y accedimos a ella a
través de referencias de superclase y de interfaz. Sin embargo, una vez que cambiamos el tipo de
referencia, perdimos el acceso a métodos más específicos definidos en la subclase que aún existen
dentro del objeto. Podemos recuperar esas referencias devolviendo el objeto a la subclase
específica de la que proviene:
En este ejemplo, primero intentamos convertir la referencia de primate de nuevo en una
referencia de lémur, lemur2, sin una conversión explícita. El resultado es que el código no se
compilará. Sin embargo, en el segundo ejemplo, convertimos explícitamente el objeto en una
subclase del objeto Primate y obtenemos acceso a todos los métodos disponibles para la clase
Lemur.

A continuación, se muestran algunas reglas básicas que se deben tener en cuenta al convertir
variables:

1. La conversión de un objeto de una subclase a una superclase no requiere una conversión


explícita.

2. La conversión de un objeto de una superclase a una subclase requiere una conversión explícita.

3. El compilador no permitirá conversiones de tipos no relacionados.

4. Incluso cuando el código se compila sin problemas, se puede lanzar una excepción en tiempo de
ejecución si el objeto que se está convirtiendo no es realmente una instancia de esa clase.

La tercera regla es importante; el examen puede intentar engañarte con un elenco que el
compilador no permite. Por ejemplo, pudimos lanzar una referencia de Primate a una referencia
de Lemur, porque Lemur es una subclase de Primate y, por lo tanto, está relacionada.

Considere este ejemplo:


En este ejemplo, las clases Fish y Bird no están relacionadas a través de ninguna jerarquía de clases
de la que sea consciente el compilador; por lo tanto, el código no se compilará.

El casting no está exento de limitaciones. Aunque dos clases comparten una jerarquía relacionada,
eso no significa que una instancia de una pueda convertirse automáticamente en otra. Aquí tienes
un ejemplo:

Este código crea una instancia de Rodent y luego intenta convertirlo en una subclase de Rodent,
Capybara. Aunque este código se compilará sin problemas, lanzará una ClassCastException en
tiempo de ejecución ya que el objeto al que se hace referencia no es una instancia de la clase
Capybara. Lo que hay que tener en cuenta en este ejemplo es que el objeto que se creó no está
relacionado con la clase Capybara de ninguna manera.

Métodos virtuales (Virtual Methods).

La característica más importante del polimorfismo, y una de las principales razones por las que
tenemos una estructura de clases, es admitir métodos virtuales. Un método virtual es un método
en el que la implementación específica no se determina hasta el tiempo de ejecución. De hecho,
todos los métodos Java no finales, no estáticos y no privados se consideran métodos virtuales, ya
que cualquiera de ellos puede anularse en tiempo de ejecución. Lo que hace que un método
virtual sea especial en Java es que si llama a un método en un objeto que anula un método,
obtiene el método anulado, incluso si la llamada al método está en una referencia principal o
dentro de la clase principal.

Ilustraremos este principio con el siguiente ejemplo:


Como vio en ejemplos similares en la sección "Reemplazo de un método", el método getName ()
se reemplaza en la clase secundaria Peacock. Sin embargo, lo que es más importante, el valor del
método getName () en tiempo de ejecución en el método displayInformation () se reemplaza con
el valor de la implementación en la subclase Peacock.

En otras palabras, aunque la clase padre Bird define su propia versión de getName () y no sabe
nada sobre la clase Peacock durante el tiempo de compilación, en el tiempo de ejecución la
instancia usa la versión anulada del método, como se define en la instancia. del objeto.

Enfatizamos este punto usando una referencia a la clase Bird en el método main (), aunque el
resultado habría sido el mismo si se hubiera usado una referencia a Peacock.

Ahora conoce el verdadero propósito de anular un método y cómo se relaciona con el


polimorfismo. La naturaleza del polimorfismo es que un objeto puede adoptar muchas formas
diferentes. Al combinar su comprensión del polimorfismo con la anulación de métodos, verá que
los objetos pueden interpretarse de formas muy diferentes en tiempo de ejecución,
especialmente en los métodos definidos en las superclases de los objetos.

Parámetros polimórficos (Polymorphic Parameters).

Una de las aplicaciones más útiles del polimorfismo es la capacidad de pasar instancias de una
subclase o interfaz a un método. Por ejemplo, puedes de! ne un método que toma una instancia
de una interfaz como parámetro. De esta manera, cualquier clase que implemente la interfaz se
puede pasar al método. Dado que está transmitiendo de un subtipo a un supertipo, no se requiere
una transmisión explícita. Esta propiedad se conoce como parámetros polimórficos de un método
y la demostramos en el siguiente ejemplo:
Centrémonos en el método de alimentación (reptiles reptiles) en este ejemplo. Como puede ver,
ese método pudo manejar instancias de Alligator y Crocodile sin problemas, porque ambas son
subclases de la clase Reptile. También pudo aceptar un tipo de clase Reptile coincidente. Si
hubiéramos intentado pasar una clase no relacionada, como las clases Rodent o Capybara
previamente definidas, o una superclase como java.lang.Object, al método feed (), el código no se
habría compilado.

Polimorfismo y anulación de método (Polymorphism and Method Overriding).

Concluyamos este capítulo volviendo a las últimas tres reglas de anulación de métodos para
demostrar cómo el polimorfismo requiere que se incluyan como parte de la especificación de Java.
Verá que sin estas reglas en su lugar, es fácil construir un ejemplo con polimorfismo en Java.

La primera regla es que un método anulado debe ser al menos tan accesible como el método que
está anulando. Supongamos que esta regla no es necesaria y consideremos el siguiente ejemplo:

Para el propósito de esta discusión, ignoraremos el hecho de que la implementación de getName


() en la clase Gorilla no se compila porque es menos accesible que la versión que reemplaza en la
clase Animal.
Como puede ver, este ejemplo crea un problema de ambigüedad en la clase ZooKeeper. La
referencia animal.getName () está permitida porque el método es público en la clase Animal, pero
debido al polimorfismo, el objeto Gorilla en sí ha sido anulado con una versión menos accesible,
no disponible para la clase ZooKeeper. Esto crea una contradicción en el sentido de que el
compilador no debería permitir el acceso a este método, pero debido a que se hace referencia a él
como una instancia de Animal, está permitido. Por lo tanto, Java elimina esta contradicción,
impidiendo que un método sea anulado por una versión menos accesible del método.

También podría gustarte