Subido por fer3641

polimorfismo

Anuncio
INSTITUTO TECNOLÓGIGO SUPERIOR DE COMALCALCO
ALUMNA:
Hernández Romero Ángeles Fernanda
MATRICULA:
TE170573
MATERIA:
Programación avanzada
MAESTRO:
Elías Rodríguez Rodríguez
SEMESTRE: 7mo.
GRUPO: “C”
ACTIVIDAD:
Investigación de conceptos
CARRERA:
Ingeniería mecatrónica
Comalcalco, Tabasco 26 de Octubre del 2020
Polimorfismo en JAVA
Método de Polimorfismo Java
En programación orientada a objetos, polimorfismo es la capacidad que tienen los
objetos de una clase en ofrecer respuesta distinta e independiente en función de los
parámetros (diferentes implementaciones) utilizados durante su invocación. Dicho
de otro modo, el objeto como entidad puede contener valores de diferentes tipos
durante la ejecución del programa.
En JAVA el término polimorfismo también suele definirse como ‘Sobrecarga de
parámetros’, que así de pronto no suena tan divertido, pero como veremos más
adelante induce a cierta confusión. En realidad, suele confundirse con el tipo de
polimorfismo más común, pero no es del todo exacto usar esta denominación.
El polimorfismo es la habilidad de una función, método, variable u objeto de poseer
varias formas distintas. Podríamos decir que un mismo identificador comparte varios
significados diferentes.
El propósito del polimorfismo es implementar un estilo de programación llamado
envío de mensajes en el que los objetos interactúan entre ellos mediante estos
mensajes, que no son más que llamadas a distintas funciones.
Java tiene 4 grandes formas de polimorfismo (aunque conceptualmente,
muchas más):




Polimorfismo de asignación
Polimorfismo puro
Sobrecarga
Polimorfismo de inclusión
Polimorfismo de asignación
El polimorfismo de asignación es el que está más relacionado con el enlace
dinámico.
En java, una misma variable referenciada (Clases, interfaces…) puede hacer
referencia a más de un tipo de Clase. El conjunto de las que pueden ser
referenciadas está restringido por la herencia o la implementación.
Esto significa, que una variable A declarada como un tipo, puede hacer referencia
a otros tipos de variables siempre y cuando haya una relación de herencia o
implementación entre A y el nuevo tipo. Podemos decir que un tipo A y un tipo B
son compatibles si el tipo B es una subclase o implementación del tipo A.
Supongamos este ejemplo:
abstract class vehiculo
{
abstract public void iniciar();
}
class Coche extends Vehiculo
{
@Override
public void iniciar()
{
}
}
En él tenemos una clase que hereda de otra. La forma normal de instanciar una
clase de tipo Coche sería esta…
Coche j = new Coche();
Sin embargo, el polimorfismo de asignación permite a una variable declarada como
otro tipo usar otra forma, siempre y cuando haya una relación de herencia o
implementación. Sabiendo esto, este fragmento demuestra el polimorfismo de
asignación:
Vehiculo j = new Coche();
En el vemos como una variable inicializada como tipo Vehiculo puede usar el
polimorfismo de asignación para hacer referencia a una clase de tipo Coche.
Podemos decir que el tipo estático de la variable j es Vehiculo, mientras que su tipo
dinámico es Coche, pero de esto hablaré en otro momento…
Esto también puede hacerse con el nombre de interfaces implementadas.
interface Comprable
{
public void comprar();
}
class Casa implements Comprable
{
@Override
public void comprar()
{
}
}
class Coche extends Vehiculo implements comprable
{
@Override
public void iniciar()
{
}
@Override
public void comprar()
{
}
}
Teniendo las anteriores clases iniciales, el siguiente código es una clara muestra
del polimorfismo de asignación en Java.
Comprable a = new Casa();
a = new Coche();
Polimorfismo Puro
El polimorfismo puro se usa para nombrar a una función o método que puede recibir
varios tipos de argumentos en tiempo de ejecución. Esto no lo debemos confundir
con la sobrecarga, que es otro tipo de polimorfismo en tiempo de compilación.
Conociendo el polimorfismo de asignación, podemos hacer una función que acepte
varios tipos de objetos distintos en tiempo de ejecución. Veamos un ejemplo,
usando las clases anteriormente mencionadas:
class PolimorfismoPuroTest
{
public function funcionPolimorfica(Comprable ob)
{
// La función acepta cualquier "comprable", es decir,
cualquier objeto que implemente esa interfaz
// El tipo de objeto se determina en tiempo de ejecución. En
nuestros ejemplos, puede ser una casa o coche.
}
}
En el ejemplo se ve como el método funcionPolimorfica es capaz de trabajar con
varios objetos gracias al polimorfismo de asignación. Esto es lo que se conoce como
polimorfismo puro y cada lenguaje lo implementa de una forma u otra.
Polimorfismo de sobrecarga
Muy similar al anterior, pero este se realiza en tiempo de compilación.
En el polimorfismo de sobrecarga, dos o más funciones comparten el mismo
identificador, pero distinta lista de argumentos. Al contrario que el polimorfismo puro,
el tipado de los argumentos se especifica en tiempo de compilación.
Es muy habitual ver esto en las clases envolventes (Integer, Float, etc…) y por eso
mismo voy a mostrar un ejemplo de sobrecarga de la clase String de Java:
public final class String
implements java.io.Serializable, Comparable, CharSequence
{
...
public static String valueOf(Object obj)
{
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[])
{
return new String(data);
}
public static String valueOf(char data[], int offset, int count)
{
return new String(data, offset, count);
}
...
}
En el ejemplo anterior vemos una misma función (valueOf) con diferentes listas de
argumentos. En función de los argumentos especificados en el mensaje, la clase
String utilizará uno u otro para adecuarse al contexto. Fijaros que la primera función
admite un Objeto, la segunda un array de Chars y la tercera Un array de Chars y
dos integers. Esto es el polimorfismo de sobrecarga, de definición similar al
polimorfismo puro, pero de implementación muy distinta.
Polimorfismo de inclusión
La habilidad para redefinir por completo el método de una superclase en una
subclase es lo que se conoce como polimorfismo de inclusión (o redefinición).
En él, una subclase define un método que existe en una superclase con una lista de
argumentos (si se define otra lista de argumentos, estaríamos haciendo sobrecarga
y no redefinición).
Un ejemplo muy básico:
abstract class Pieza
{
public abstract void movimiento(byte X, byte Y);
}
class Alfil extends Pieza
{
@Override
public void movimiento(byte X, byte Y)
{
}
}
En el ejemplo vemos como la clase Alfil sobrescribe el método movimiento. Esto es
el polimorfismo de inclusión. Un error común de un desarrollador es pensar que el
siguiente ejemplo es otra muestra de este tipo de polimorfismo:
class Caballo extends Pieza
{
public void movimiento(int X, int Y)
{
}
}
El ejemplo de la clase Caballo está sobrecargando un método definido en su
superclase. No está sobrescribiéndolo, porque para usar el polimorfismo de
inclusión debemos usar el mismo identificador y la misma lista de parámetros que
en la superclase. El método de la superclase usa dos bytes, mientras que el de la
subclase usa dos ints.
Ejemplo de Polimorfismo
Un ejemplo clásico de polimorfismo es el siguiente. Podemos crear dos clases
distintas: Gato y Perro, que heredan de la superclase Animal. La clase Animal tiene
el método abstracto makesound() que se implementa de forma distinta en cada una
de las subclases (gatos y perros suenan de forma distinta). Entonces, un tercer
objeto puede enviar el mensaje de hacer sonido a un grupo de objetos Gato y Perro
por medio de una variable de referencia de clase Animal, haciendo así un uso
polimórfico de dichos objetos respecto del mensaje mover.
class Animal {
public void makeSound() {
System.out.println("Grr...");
}
}
class Cat extends Animal {
public void makeSound() {
System.out.println("Meow");
}
}
class Dog extends Animal {
public void makeSound() {
System.out.println("Woof");
}
}
Como todos los objetos Gato y Perro son objetos Animales, podemos hacer lo
siguiente:
public static void main(String[ ] args) {
Animal a = new Dog();
Animal b = new Cat();
}
Creamos dos variables de referencia de tipo Animal y las apuntamos a los objetos
Gato y Perro. Ahora, podemos llamar a los métodos makeSound().
a.makeSound();
//Outputs "Woof"
b.makeSound();
//Outputs "Meow"
Como decía el polimorfismo, que se refiere a la idea de "tener muchas formas",
ocurre cuando hay una jerarquía de clases relacionadas entre sí a través de la
herencia y este es un buen ejemplo.
Excepciones en JAVA
En Java los errores en tiempo de ejecución (cuando se está ejecutando el programa)
se denominan excepciones, y esto ocurre cuando se produce un error en alguna de
las instrucciones de nuestro programa, como por ejemplo cuando se hace una
división entre cero, cuando un objeto es 'null' y no puede serlo, cuando no se abre
correctamente un fichero, etc. Cuando se produce una excepción se muestra en la
pantalla un mensaje de error y finaliza la ejecución del programa.
En Java (al igual que en otros lenguajes de programación), existen mucho tipos de
excepciones y enumerar cada uno de ellos seria casi una labor infinita. En lo
referente a las excepciones hay que decir que se aprenden a base experiencia, de
encontrarte con ellas y de saber solucionarlas.
Cuando en Java se produce una excepción se crear un objeto de una determina
clase (dependiendo del tipo de error que se haya producido), que mantendrá la
información sobre el error producido y nos proporcionará los métodos necesarios
para obtener dicha información. Estas clases tienen como clase padre la clase
Throwable, por tanto, se mantiene una jerarquía en las excepciones. A continuación,
mostramos algunas de las clases para que nos hagamos una idea de la jerarquía
que siguen las excepciones, pero existen muchísimas más excepciones que las que
mostramos:
A continuación, vamos a mostrar un ejemplo de cómo al hacer una división entre
cero, se produce una excepción. Veamos la siguiente imagen en el que podemos
ver un fragmento de código y el resultado de la ejecución del código:
Como vemos en nuestro programa tenemos 3 instrucciones. La primera debe de
imprimir por pantalla el mensaje "ANTES DE HACER LA DIVISIÓN", la segunda
debe de hacer la división y la última debe de imprimir por pantalla el mensaje
"DESPUES DE HACER LA DIVISIÓN". La primera instrucción la ejecuta
perfectamente, pero al llegar a la segunda se produce una "ArithmeticException"
(excepción de la clase ArithmeticException) y se detiene la ejecución del programa
ya que estamos dividiendo un número entre '0'.
Por suerte Java nos permite hacer un control de las excepciones para que nuestro
programa no se pare inesperadamente y aunque se produzca una excepción,
nuestro programa siga su ejecución. Para ello tenemos la estructura "try – catch –
finally" que la mostramos a continuación:
try {
// Instrucciones cuando no hay una excepción
} catch (TypeException ex) {
// Instrucciones cuando se produce una excepcion
} finally {
// Instruciones que se ejecutan, tanto si hay como sino hay
excepciones
}
Respecto a la estructura "try – catch – finally", se ha de decir que primero se ejecuta
el bloque "try", si se produce una excepción se ejecuta el bloque "catch" y por último
el bloque "finally". En esta estructura se puede omitir el bloque "catch" o el bloque
"finally", pero no ambos.
Sabiendo esta estructura, podemos reescribir nuestro programa para que se
ejecuten las tres instrucciones, aunque se produzca una excepción. Previamente
debemos de saber cuál va a ser la clase de la excepción que puede aparecer que
sería la "ArithmeticException" para definirla en la parte del "catch". Nuestro
programa quedaría de la siguiente forma y se ejecutaría sin problema obteniendo
también la información de la excepción:
Como vemos capturamos la excepción en un objeto "ex" de la clase
"ArithmeticException" y podemos obtener el mensaje de error que nos da la
excepción. Vemos también que el programa termina su ejecución, aunque se haya
producido una excepción.
Dentro de una misma estructura podemos definir todas las excepciones que
queramos. En el caso anterior hemos definido solo la excepción
"ArithmeticException"; pero, por ejemplo, podemos definir también la excepción
"NullPointerException", por si nos viene un valor a 'null' al hacer la división:
En resumen, hemos puesto en esta entrada un ejemplo muy sencillo para controlar
un par de excepciones bastante obvias como la división entre '0' y un 'null', que
perfectamente lo podríamos haber controlado con una sentencia de control "if"
mirando el contenido de los atributos, pero la finalidad de esta entrada era ver cómo
controlar las excepciones con la estructura "try – catch – finally", que si lo sabemos
utilizar nuestro programa deberá seguir funcionando aunque se produzcan
excepciones. Decir también que es casi imposible aprenderse todas las
excepciones que hay en Java (así que no os pongáis a empollarlas una a una
porque igual no utilizareis ni el 10% de las que hay) ya que estas las iréis
aprendiendo según os las vayáis encontrando en vuestros desarrollos.
Tipos de excepciones:
NOMBRE
DESCRIPCION
FileNotFoundException
Lanza una excepción cuando el fichero no se encuentra.
ClassNotFoundException
Lanza una excepción cuando no existe la clase.
EOFException
Lanza una excepción cuando llega al final del fichero.
ArrayIndexOutOfBoundsException Lanza una excepción cuando se accede a una posición de un
array que no exista.
NumberFormatException
Lanza una excepción cuando se procesa un numero pero este es
un dato alfanumérico.
NullPointerException
Lanza una excepción cuando intentando acceder a un miembro de
un objeto para el que todavía no hemos reservado memoria.
IOException
Generaliza muchas excepciones anteriores. La ventaja es que no
necesitamos controlar cada una de las excepciones.
Excepcion
Es la clase padre de IOException y de otras clases. Tiene la
misma ventaja que IOException.
Declaración de nuevas excepciones
Java cuenta con muchas excepciones predefinidas para determinadas situaciones,
como por ejemplo IOException para errores producidos en operaciones de
entrada/salida, como es el caso de la lectura de ficheros.
Para nuestros propios programas, a veces es útil que creemos nuestra excepción a
medida. Para ello, tenemos que declarar una nueva clase que herede de Exception.
Por ejemplo:
public class BadPostCodeException extends Exception {
public BadPostCodeException() {
super();
}
public BadPostCodeException(String message) {
super(message);
}
}
Normalmente, no necesitaremos declarar nuevos atributos ni métodos, aparte de
los constructores.
Cómo se lanza una excepción
Cuando en un método necesitamos lanzar una excepción, utilizaremos la palabra
clave throw, seguida de una instancia de la excepción a lanzar. Por ejemplo:
if (postCode.length() != 5) {
throw new BadPostCodeException("Postcodes must have 5 digits");
}
for (int i = 0; i < postCode.length(); i++) {
if (!Character.isDigit(postCode.charAt(i))) {
throw new BadPostCodeException("Postcodes can only contain
digits");
}
}
City city = lookupPostCode(postCode);
if (city == null) {
throw new BadPostCodeException("Postcode not in database: " +
postCode);
}
Salvo en el caso de un grupo especial de excepciones, en general un método que
pueda potencialmente lanzar una excepción debe indicarlo explícitamente mediante
la palabra clave throws (nótese la "s" final, no confundir con throw) seguida del
nombre de la clase de la excepción que puede lanzar:
public class PostCodeManager {
(...)
public String town(String postCode) throws BadPostCodeException {
if (postCode.length() != 5) {
throw new BadPostCodeException("Postcodes must have 5
digits");
}
for (int i = 0; i < postCode.length(); i++) {
if (!Character.isDigit(postCode.charAt(i))) {
throw new BadPostCodeException("Postcodes can only
contain digits");
}
}
City city = lookupPostCode(postCode);
if (city == null) {
throw new BadPostCodeException("Postcode not in database: " +
postCode);
}
(...)
}
(...)
}
Si el método puede lanzar más de una clase de excepción, se ponen los nombres
de todas las clases separados por comas tras un único throws.
Qué ocurre cuando se lanza una excepción
Cuando se produce una excepción, la máquina virtual interrumpe la ejecución
normal del programa y busca un bloque de código adecuado para tratar la situación.
Si no encuentra este código en el método actual, la excepción se propaga hacia el
método que lo haya invocado y se busca allí el código que la trate. Si tampoco ese
método dispone del código adecuado, se propagará a su vez al que lo haya
invocado, y así sucesivamente.En este apartado se explica el caso en que un
método no dispone de código adecuado para tratar una excepción. Como se ha
explicado, en este caso la excepción se propaga al método que lo haya invocado.
Un método que no proporcione código para tratar la excepción debe declarar que
puede lanzar la excepción con la palabra clave throws al igual que se explica en el
apartado anterior:
public class Location {
(...)
private String postCode;
public String townAsString() throws BadPostCodeException {
PostCodeManager manager = new PostCodeManager();
String town = manager.town(postCode);
return postCode + " " + town;
}
(...)
}
En el ejemplo, la invocación al método town puede potencialmente lanzar una
excepción. El método townAsString no está proporcionando código adecuado para
tratarla. Por tanto, la excepción se propagaría a través de él, y entonces también
debe declarar que puede lanzarla, como se ve en el ejemplo.Si en la llamada a town
surge una excepción, esta se propaga al método que haya invocado a townAsString.
La excepción continuará propagándose por la pila de invocaciones hasta que en
algún método sea tratada. Si se alcanza el método main, y este tampoco
proporciona ningún código para tratarla, la máquina virtual cortará la ejecución del
programa y mostrará al usuario, normalmente en pantalla, el mensaje de la
excepción y la ubicación del programa en que se haya producido.
Cómo se trata una excepción
Un método puede decidir capturar una excepción si tiene sentido colocar en ese
método el código que la trata adecuadamente. Para capturar una excepción se
utiliza un bloque try/catch:
try {
// código en que podría surgir la excepción
} catch (ClaseDeLaExcepción e) {
// código que trata la situación de la excepción
}
En el momento en que surja la excepción en el bloque try, se corta su ejecución y
se pasa a ejecutar el código del bloque catch. Una vez finaliza el bloque catch, se
continúa con la ejecución del resto del código que siga a try/catch. También se
continúa normalmente en ese punto si el bloque try finaliza sin que surjan
excepciones.
A continuación, se muestra una implementación alternativa de townAsString en que
se captura la excepción:
public class Location {
(...)
private String postCode;
public String townAsString() {
String result;
PostCodeManager manager = new PostCodeManager();
try {
String town = manager.town(postCode);
result = postCode + " " + town;
} catch (BadPostCodeException e) {
result = "Unknown location at postcode " + postCode;
}
return result;
}
(...)
}
Si en el método town surge una excepción, se corta la ejecución del bloque try, y
por tanto ya no se ejecuta la instrucción result = postCode + " " + town, sino que el
programa salta al interior del bloque catch. La instrucción return result se ejecuta
siempre, surja o no alguna excepción, porque está a continuación del bloque
try/catch.
Nótese que el método townAsString no declara en este caso que se lance la
excepción. El motivo es que, al capturar la excepción, está ya no puede propagarse
a través del método.
Referencias bibliográficas
Mazón Olivo, B. E., Cartuche Calva, J. J., Chimarro Chipantiza, V. L.,
& Rivas Asanza, W. B. (2015). Fundamentos de programación
orientada a objetos en JAVA.
Deitel, P. J., & Deitel, H. M. (2008). Java: como programar. Pearson
educacion.
Tomás Gironés, J. (2012). El polimorfismo en Java.
Moltó, G. Tema 1-Conceptos de Java para Estructuras de Datos.
Fernández, O. B. (2005). Introducción al lenguaje de programación
Java. Una guía básica, 9.
Martínez, M. Y. M. (2018). Excepciones.
Descargar