Herencia y Poliformismo Java

Post on 23-Oct-2015

24 views 3 download

Transcript of Herencia y Poliformismo Java

1

4.- Herencia y polimorfismo

1.  Herencia 2.  Polimorfismo 3.  Interfaces

2

1. Herencia

1.1. Introducción 1.2. Los constructores en la herencia 1.3. Modificadores de acceso 1.4. La clase Object 1.5 Herencia vs. composición

3

1.1.- Introducción •  La herencia permite definir clases (subclases) a partir de otra clase

más genérica (superclase). •  La subclase reúne todas la propiedades de la superclase, además

de las suyas propias. •  La herencia potencia la reutilización de código, genera código

más fiable y robusto y reduce el coste de mantenimiento.

Persona

Estudiante

Persona nombre apellidos dni

mostrarNombre mostrarDNI

Estudiante numExpediente

mostrarNumExp

class Estudiante extends Persona { . . .

4

class Persona(){ String nombre, apellidos, dni; void mostrarNombre(){ System.out.println(“Nombre: ” + apellidos + “,” + nombre); } void mostrarDNI() { System.out.println(“DNI: ” + dni); } }

class Estudiante extends Persona { String numExpediente; void mostrarNumExp(){ System.out.println(“Núm. Exp. ” + numExpediente); } }

class Ejemplo { public static void main(String args[]) { Estudiante e = new Estudiante(); e.nombre = “Ana”; e.apellidos = “García”; e.dni = “1234567”; e.numExpediente = “10001”; e.mostrarNombre(); e.mostrarNumExp(); } }

5

1.1.- Introducción •  En Java no se permite la herencia múltiple.

A B

C

A

B C

Error Correcto

•  Una subclase hereda todos los métodos y atributos de la superclase EXCEPTO: –  Atributos y métodos privados –  Constructores (no se heredan pero sí se ejecutan)

6

1.2.- Los constructores en la herencia •  Conceptos previos: toda clase, por defecto, contiene un constructor sin

parámetros y vacío.

class Esfera(){ double radio; Esfera() {} // Este constructor existe sin necesidad de escribirlo }

Esfera e1 = new Esfera(); Incorrecto. No existe constructor sin parámetros Esfera e2 = new Esfera(10); Correcto

•  El constructor por defecto se pierde si escribimos cualquier otro constructor.

class Esfera(){ double radio; Esfera(double r) { radio = r; } }

Esfera e = new Esfera(); Correcto

7

1.2.- Los constructores en la herencia •  Los constructores no se heredan.

class Esfera(){ double radio; Esfera(double r) { radio = r; } Esfera() { radio = 1; } } class Planeta extends Esfera { int numSatelites; Planeta(double r, int ns) { radio = r; numSatelites = ns; } }

Planeta p1 = new Planeta(6378, 1); ¿Correcto / Incorrecto? Planeta p2 = new Planeta(6378); ¿Correcto / Incorrecto? Planeta p3 = new Planeta(); ¿Correcto / Incorrecto? Esfera e = new Esfera(6378); ¿Correcto / Incorrecto?

8

•  Cuando creamos un objeto de una subclase, el constructor de la clase padre TAMBIÉN se ejecuta:

class A(){ A() { System.out.println(“En A”); } } class B extends A { B() { System.out.println(“En B”); } } class Demo { public static void main(String args[]) { B b = new B(); } }

Salida por pantalla: En A En B

1.2.- Los constructores en la herencia

•  Primero se ejecuta el constructor de la superclase y luego el de la subclase

9

•  ¿Qué constructor se ejecuta en la superclase? → El constructor sin parámetros, a no ser que “digamos lo contrario”

class A(){ int i; A() { i = 0; } A( int i ){ this.i = i; } } class B extends A { int j; B() { j = 0; } B( int j ){ this.j = j; } } class Demo { public static void main(String args[]) { B b1 = new B(); System.out.println(“i=“ + b1.i + “j=“ + b1.j); B b2 = new B(5);System.out.println(“i=“ + b2.i + “j=“ + b2.j); } }

Salida por pantalla: i=0 j =0 i=0 j=5

1.2.- Los constructores en la herencia

10

•  ¿Cómo podemos forzar la ejecución de un constructor determinado en la clase padre (superclase)?

class B extends A { int j; B() { j = 0; } B( int j ){ super(j); // Ejecuta un constructor en la superclase que // contiene un entero como argumento this.j = j; } }

Salida por pantalla: i=0 j =0 i=5 j=5

1.2.- Los constructores en la herencia

•  Si utilizamos super, ésta debe de ser la primera instrucción del constructor. De este modo se respeta el orden de ejecución de los constructores.

11

•  Pérdida del constructor por defecto: class Esfera { Esfera ( double r ) { radio = r; } } class Planeta extends Esfera { int numSatelites; Planeta( double r, int ns ) { radio = r; numSatelites = ns; } }

1.2.- Los constructores en la herencia

•  Cuando creo un objeto de tipo Planeta, ¿qué constructor se ejecuta en la clase Esfera?

12

class Esfera { Esfera() { radio = 1; } Esfera ( double r ) { radio = r; } } class Planeta extends Esfera { int numSatelites; Planeta( double r, int ns ) { radio = r; numSatelites = ns; } }

1.2.- Los constructores en la herencia

class Esfera { Esfera ( double r ) { radio = r; } } class Planeta extends Esfera { int numSatelites; Planeta( double r, int ns ) { super(r); numSatelites = ns; } }

Solución a

Solución b (preferible)

13

class A { A () { ... } A( int x ) { ... } } class B extends A { B() { ... } } class C extends A { C( int x ) { ... } } class D extends A { D( int x ) { super(x); } } class E extends D { E( int x ) { ... } }

1.2.- Los constructores en la herencia

B ob1 = new B(); C ob2 = new C(1); D ob3 = new D(1); E ob4 = new E(1); E ob5 = new E();

•  ¿Qué constructor se ejecuta en cada caso?

14

1.3.- Modificadores de acceso

private Sin modificador

(friendly)

protected public

Misma clase Si Si Si Si

Otra clase del mismo paquete

No Si Si Si

Subclase de diferente paquete

No No Si Si

No subclase de diferente paquete

No No No Si

15

1.4.- La clase Object •  Object es la clase base (superclase) de todas las demás clases. •  Si una clase no especifica extends, entonces se entiende que

deriva de Object → Todas las clases derivan directa o indirectamente de Object.

•  Algunos métodos de la clase Object: –  boolean equals (Object o): compara dos objetos. –  String toString(): devuelve una cadena de tipo String que contiene una

descripción del objeto. Se invoca automáticamente cuando se utiliza el objeto como si fuera una cadena:

–  void finalize(): se ejecuta automáticamente al destruirse el objeto.

Complejo c = new Complejo(); System.out.println(“c = ” + c);

16

1.5.- Herencia vs. composición •  No debe confundirse la herencia con la composición. •  Composición: mecanismo por el que se define una nueva clase

agregando componentes de otras clases.

class Punto { int x, y; . . . } class Figura { Punto origen; . . .

class Punto { int x, y; . . . } class Figura extends Punto{ . . .

•  Dadas dos clases A y B: –  ¿A es un B? → Herencia –  ¿A tiene un B? → Composición

class A extends B {

class A { B b;

•  Una figura no es un punto. Una Figura tiene un punto de origen. •  Un círculo es una figura. Un círculo no tiene una figura.

17

2. Polimorfismo

2.1. Introducción 2.2. Sobreescritura de métodos 2.3. La conversión hacia arriba 2.4. Enlace dinámico y polimorfismo 2.5. Clases abstractas 2.6. La conversión hacia abajo 2.7. Sobreescribir métodos de Object

18

2.1- Introducción •  El polimorfismo es la capacidad que tienen los LOO de ofrecer distintas

implementaciones para un mismo método:

Polimorfismo ≡ múltiples formas

•  Una llamada a un mismo método puede tener comportamientos distintos. •  Enlace estático o temprano (polimorfismo en tiempo de compilación)

–  Sobrecarga de métodos

Complejo c1, c2, c3; . . . c3 = c1.multiplica(c2); // Método polimórfico (sobrecargado) c3 = c1.multiplica(4); // Método polimórfico (sobrecargado)

–  En tiempo de compilación se establece el enlace con el código que deberá ejecutarse.

•  Enlace dinámico o tardío (polimorfismo en tiempo de ejecución) –  Sobreescritura de métodos. –  No es posible determinar en tiempo de compilación el método que se ejecutará. –  Permite realizar ciertas abstracciones sobre los tipos de datos con los que se

trabaja.

19

2.2- Sobreescritura de métodos •  En ocasiones interesa que la subclase modifique algunos de los

métodos heredados para que tengan un comportamiento distinto:

class Nave { int posX, posY, municion; . . . void disparar() { if(municion>0) municion--; } } class NaveConEscudo extends Nave { boolean escudo; . . . void activarEscudo() { escudo = true; } void desactivarEscudo() { escudo = false; } // Sobreescritura del método disparar void disparar() { if( municion>0 && escudo==false ) municion--; } }

20

2.2- Sobreescritura de métodos

class Juego { public static void main( String [] args ) { Nave nave1 = new Nave(); NaveConEscudo nave2 = new NaveConEscudo();

. . . nave1.disparar(); // disparar de Nave nave2.disparar(); // disparar de NaveConEscudo } }

21

2.2- Sobreescritura de métodos •  Hay dos formas de sobreescribir un método:

–  Reemplazo: se reescribe el método completamente, ignorando el código de la superclase.

–  Refinamiento: se amplía el método de la superclase con instrucciones extras.

class Persona { String nombre, dni; . . . void mostrarDatos() { System.out.println(“Nombre:” + nombre); System.out.println(“DNI: ” + dni); } } class MiembroUPV extends Persona { String email; . . . void mostrarDatos() { // Refinamiento super.mostrarDatos(); // Mostrar datos de Persona System.out.println(“EMAIL: ” + email); } }

22

2.2- Sobreescritura de métodos •  Si se declara un método como final, se impide su sobreescritura.

class Persona { String nombre, dni; . . . final void derechosFundamentales() { System.out.print(nombre + “ tiene derecho a ”); System.out.print(“una alimentación adecuada.”); } }

•  Si se declara una clase como final, se impide que se extienda.

final class Math { . . .

23

2.3- La conversión hacia arriba

•  Sin embargo es posible declarar una referencia de tipo A y emplearla para instanciar un objeto de tipo B, siempre y cuando B sea una subclase (o un subtipo) de A.

A ref = new B();

•  Hasta ahora el tipo de la referencia y el tipo del objeto instanciado han coincidido: A ref = new A();

Se denomina: •  Tipo estático: el tipo con el que se declara la referencia. •  Tipo dinámico: el tipo del objeto instanciado.

En la sentencia: A ref = new B(); –  Tipo estático de ref: A –  Tipo dinámico de ref: B

24

2.3- La conversión hacia arriba •  Hablamos de conversión hacia arriba cuando se instancia un

objeto mediante una referencia perteneciente a un tipo o clase que jerárquicamente está “arriba”de la clase del objeto instanciado.

Esfera

Planeta

Esfera e; e = new Planeta(); // Conversión hacia arriba

Tipo estático de e: Esfera Tipo dinámico de e: Planeta

•  Limitaciones: sólo se tiene acceso a los miembros definidos en el tipo estático.

•  En el ejemplo anterior, aunque se ha creado un objeto de tipo Planeta, mediante e sólo se tiene acceso a los atributos y métodos de Esfera.

25

2.3- La conversión hacia arriba class A { public void m1() { . . . } } class B extends A { public void m2() { . . . } }

B obj1 = new B(); // Tipo estático y dinámico de obj1: B A obj2 = new B(); // Tipo estático A y tipo dinámico B B obj3 = new A(); // Tipo estático B y tipo dinámico A. ¡ERROR!

m2()

B A m1()

m2()

B A m1()

A m1()

obj1 obj2 obj3

B obj1 = new B() A obj2 = new B() B obj3 = new A()

obj1.m1() OK obj1.m2() OK

obj2.m1() OK obj2.m2() ERROR

ERROR

26

2.3- La conversión hacia arriba •  Conversión hacia arriba + sobreescritura

¿Para qué sirve todo esto?

class A { public void m1() { . . . } } class B extends A { // Sobreescribimos m1 public void m1() { . . . } public void m2() { . . . } }

A obj = new B(); obj.m1(); // ¿Qué método m1 se ejecuta? ¿El de A o el de B?

•  El tipo estático determina QUÉ se puede hacer. El tipo dinámico determina CÓMO se hace.

27

2.4- Enlace dinámico y polimorfismo class Figura { Color c; double area() { return 0; // No sabemos qué área tiene una // figura genérica } } class Rectangulo extends Figura { double alto, ancho; . . . double area() { // Sobresscritura del metodo area return alto*ancho; } } class Circulo extends Figura { double radio; . . . double area() { // Sobresscritura del metodo area return Math.PI*radio*radio; } }

28

2.4- Enlace dinámico y polimorfismo public class EnlaceDinamico { public static void main(String[] args) { // Creamos 10 referencias de tipo Figura Figura [] v = new Figura[10]; // En función de ciertas acciones tomadas por el // usuario creamos rectángulos o círculos for(int i=0; i<10; i++) { if( el_usuario_realiza_cierta_accion ) v[i] = new Rectangulo(10,10); // Conv. hacia arriba else v[i] = new Circulo(5); // Conv. hacia arriba } // Mostramos las áreas de las figuras creadas for(int i=0; i<10; i++) { double a = v[i].area(); // Enlace dinámico System.out.println("Area="+a); } } }

29

2.4- Enlace dinámico y polimorfismo •  El polimorfismo permite realizar ciertas abstracciones sobre los

tipos de datos. •  No es necesario conocer el tipo exacto de los datos para poder

realizar ciertas operaciones. ¡Puedo obtener el área de una figura, o dibujarla, sin saber exactamente de qué figura se trata!

•  La siguiente clase permite dibujar un conjunto de figuras, ¡sin necesidad de conocer de qué tipo de figuras se trata!

class ConjuntoDeFiguras { Figura [] v = new Figura[1000]; int numFiguras = 0; void añadirFigura( Figura f ) { // El objeto pasado como parámetro v[numFiguras++] = f; // puede ser una subclase de Figura } // (conversión hacia arriba) void dibujaTodo() { for(int i=0; i<numFiguras; i++) { v[i].dibuja(); // Desconozco qué tipo de figura } // estaré dibujando } }

30

2.4- Enlace dinámico y polimorfismo

class Ordena { static void seleccionDirecta( Conjunto c ) { int pos_min, N = c.getNumElementos(); for( int i = 0; i <= N-2; i++ ) { pos_min = i; for( int j = i+1; j < N; j++ ) { if( c.menor(j, pos_min) ) pos_min = j; } c.intercambiar(i, pos_min); } } }

•  La siguiente clase permite ordenar conjuntos de cualquier tipo (números enteros, colores, personas, …)

•  Requerimientos: –  El objeto que le pasemos como parámetro al método

seleccionDirecta debe ser un subtipo (subclase) de Conjunto. –  La clase Conjunto debe contener los métodos getNumElementos,

menor e intercambiar. –  El objeto que pasemos como parámetro puede tener sobreescritos los

métodos de la clase Conjunto.

31

2.4- Enlace dinámico y polimorfismo

class Figura { . . . double area() { return 0; }

boolean mismaArea(Figura otra) { return this.area() == otra.area(); } } class Rectangulo extends Figura { . . . double area() { return alto * ancho; } } class Circulo extends Figura { . . . double area() { return Mat.PI * radio * radio; } }

•  Una situación algo más compleja:

•  ¿Qué método ejecuta en la llamada this.area()?

32

2.5- Clases abstractas

class Figura { . . . } class Rectangulo extends Figura { . . . double area() { return alto * ancho; } } class Circulo extends Figura { . . . double area() { return Mat.PI * radio * radio; } }

•  Si no vamos a utilizar nunca el método area de la clase Figura, podríamos quitarlo…

Figura [] v = new Figura[10]; . . . // Añado a v Rectangulos y Circulos for(int i=0; i<v.length; i++) System.out.println(“Area=“ + v[i].area());

…pero, ¿es correcto el siguiente código?

33

2.5- Clases abstractas •  Tiene poco sentido implementar un método que nunca

voy a utilizar.

•  Además, si en Figura implementamos el método area, existe la posibilidad de que alguna subclase no implemente su propia versión de area, en cuyo caso heredaría la implementación (errónea) dada en Figura.

•  Lo ideal sería:

1.  Incluir el método area pero no implementarlo (sin código)

2.  Obligar a las subclases directas que lo implementen

34

2.5- Clases abstractas abstract class Figura { . . . abstract double area(); } class Rectangulo extends Figura { . . . double area() { return alto * ancho; } } class Circulo extends Figura { . . . double area() { return Mat.PI * radio * radio; } }

•  El método area es abstracto. Se incluye la cabecera del método (tipo, nombre y parámetros) pero no la implementación (el código).

•  Como la clase Figura tiene un método abstracto, también debe ser abstracta.

•  Las subclases de Figura deberán implementar el método area.

35

2.5- Clases abstractas Cosas que hay que saber:

•  Una clase abstracta no puede ser instanciada.

•  Si una subclase que extiende una clase abstracta no implementa alguno de los métodos abstractos declarados en la superclase, entonces debe ser declarada también como abstracta.

•  Una clase abstracta puede tener métodos no abstractos.

•  Se pueden declarar variables referencia cuyo tipo sea una clase abstracta.

•  Aunque las clases abstractas no se pueden instanciar, sí que pueden tener constructores.

36

2.5- Clases abstractas abstract class Figura { int origenX, origenY; Color color; Figura(int x, int y, Color c) { origenX = x; origenY = y; color = c; } void mover(int despX, int despY) { origenX += despX; origenY += despY; } abstract double area(); } class Circulo extends Figura { private double radio; Circulo(int x, iny y, double r, Color c) { super(x, y, c); radio = r; } double area() { return Mat.PI * radio * radio; } void setRadio(int r) { radio = (r>=0 ? r : 0); } }

37

2.6- La conversión hacia abajo •  Conversión hacia arriba: se gana generalidad pero se pierde

información acerca del tipo concreto con el que se trabaja. Figura f; f = new Circulo(...);

•  Qué ocurre si quiero hacer una operación propia del tipo concreto con el que estoy trabajando? f.setRadio(5); // Error

•  Conversión hacia abajo: cambio del tipo de la referencia a un subtipo (a un tipo que jerárquicamente está “por abajo”).

Figura f = new Circulo(...); // Conversión hacia arriba . . . Circulo c; c = (Circulo)f; // Conversión hacia abajo c.setRadio(5); // Correcto

•  O simplemente:

((Circulo)f).setRadio(5);

38

2.6- La conversión hacia abajo •  Peligros de la conversión hacia abajo: Debo estar seguro de

convertir la referencia al tipo correcto.

•  ¿Cómo puedo conocer el tipo dinámico de f? Solución: instanceof

Figura f; if(cierta_condicion) f = new Circulo(...); else f = new Rectangulo(...);

if ( f instanceof Circulo ) // f es un círculo ((Circulo)f).setRadio(5); else if ( f instanceof Rectangulo ) // f es un rectángulo ((Rectangulo)f).setDim(5,5);

39

2.6- La conversión hacia abajo •  La conversión hacia abajo debemos usarla cuando no haya otra

solución posible. •  Se pierde la abstracción y generalidad que habíamos ganado con la

conversión hacia arriba. •  Si lo que pretendía era cambiar el tamaño de la figura, hubiese sido

preferible la siguiente solución:

abstract class Figura { . . . abstrac void zoom( double factorEscala); } class Circulo extends Figura { . . . void zoom( double factorEscala ) { radio *= factorEscala; } } class Rectangulo . . . void zoom( double factorEscala ) { base *= factorEscala; altura *= factorEscala; } }

40

2.7- Sobreescribir métodos de Object •  La clase Object tiene métodos que puede interesar sobreescribir.

–  boolean equals (Object o): compara dos objetos. –  String toString(): devuelve una cadena de tipo String que contiene una

descripción del objeto. Se invoca automáticamente cuando se utiliza el objeto como si fuera una cadena:

–  void finalize(): se ejecuta automáticamente al destruirse el objeto. •  Uso del método toString:

Complejo c = new Complejo(2,3); System.out.println(c);

•  Al utilizar c como si fuera un String, se invoca automáticamente al método toString. System.out.println(c.toString());

•  Si no está sobreescrito, se invova toString de Object. •  No esperemos que Object sepa cómo mostrar un Complejo.

41

2.7- Sobreescribir métodos de Object •  Sobreescritura de toString: class Complejo { double real, imag; . . . public String toString() { String s = real + “+” + imag + “i”; return s; } }

Complejo c = new Complejo(2,3); System.out.println(c); // Se invoca el método toString

•  Salida por pantalla: 2+3i

42

3. Interfaces

3.1. Introducción 3.2. Declaración e implementación de interfaces 3.3. Polimorfismo mediante interfaces 3.4. Definición de constantes 3.5. Herencia entre interfaces

43

3.1- Introducción •  En Java no existe la herencia múltiple.

•  Las interfaces ofrecen algunas de las ventajas de la herencia múltiple sin ninguno de sus inconvenientes.

•  Una interfaz guarda muchas similitudes con una clase abstracta con todos sus métodos abstractos y atributos constantes y estáticos (final static).

A B

C

No genera conflictos si todos los métodos de A y B son abstractos.

44

3.2.- Declaración e implementación de interfaces

•  Declaración: acceso interface nombre_interfaz { [public static final] tipo var1; [public static final] tipo var2; ... [public] tipo metodo1( ... ) ; [public] tipo metodo2( ... ) ; }

interface Coleccion { void añadirElemento( Object o ); int getNumElementos(); void mostrar(); }

•  Ejemplo:

45

3.2.- Declaración e implementación de interfaces

•  Una interfaz define qué operaciones se pueden realizar pero no especifica cómo se realizan.

•  Una interfaz puede ser implementada por una o varias clases.

•  Todo lo que tiene que hacer una clase para implementar una interfaz es sobreescribir todos sus métodos.

class Conjunto implements Coleccion { private Object[] v; private int numElementos;

public void añadirElemento( Object o ) { . . . } public int getNumElementos() { . . . } public void mostrar() { . . . } }

46

3.2.- Declaración e implementación de interfaces

•  Es posible que varias clases sin relación de herencia implementen una misma interfaz y que una misma clase implemente varias interfaces.

I2

E F

B

C D

A

I1

47

3.3.- Polimorfismo mediante interfaces

•  Una interfaz es un tipo de dato. Es posible declarar referencias de tipo interfaz (aunque no se puedan instanciar objetos de este tipo).

•  La conversión hacia arriba se puede aplicar también a las interfaces. interface Coleccion { void añadir(Elemento e); void borrar(Elemento e); } class Conjunto implements Colección { . . . } class ListaEnlazada implements Colección { . . . } class Ejemplo { public static void main(String [] args) { Coleccion c; c = new Conjunto(); // Conv. Hacia arriba c.añadir( ... ); . . .

48

3.4.- Definición de constantes

•  Las interfaces también pueden emplearse para definir constantes

•  Aunque no se especifique explícitamente, los atributos de una interfaz siempre son estáticos y constantes (static final).

interface CteMat{ double pi = 3.14159265; double e = 2.71828182; } class Ejemplo { public static void main(String [] args) { double r = 4; double area = CteMat.pi * r * r; . . .

49

3.5.- Herencia entre interfaces

•  Es posible definir herencia entre interfaces.

•  Se permite la herencia múltiple.

interface I1 { void metodo1(); void metodo2(); } interface I2 { void metodo3(); } interface I3 extends I1, I2 { void metodo4(); } class C implements I3 { // Deberá implementar metodo1, metodo2, // metodo3 y metodo4 }