Indice Introducción a Java (II). Indice 2 Índice Introspección Introducción a las relaciones...
-
Upload
doroteo-avena -
Category
Documents
-
view
224 -
download
0
Transcript of Indice Introducción a Java (II). Indice 2 Índice Introspección Introducción a las relaciones...
Indice
Introducción a Java (II)
Indice 2
Índice
• Introspección• Introducción a las relaciones• Herencia• Agregación
Indice
Introspección
Indice
Introducción• Java tiene interesantes mecanismos para obtener
información interna de las clases, es decir, los objetos Java pueden informarnos de su propia estructura. Por ello se llaman utilidades de introspección
• Vamos a centrarnos en lo fundamental de la introspección: un objeto o una clase puede informarnos de la clase a la que pertenece
• La clase más importante es java.lang.Class. Es una clase que nos permite describir cualquier clase Java. Dicho de otra forma, es un descriptor o referencia de una clase.
• Un paquete relevante es java.lang.reflect
4
Indice
Conocer el tipo• En tiempo de ejecución puede saber el tipo (clase) que
manejamos:– Si tenemos un objeto, puedo conocer su clase:
Coche miCoche = new Coche();Class clase = miCoche.getClass();System.out.println("Clase:" +clase.getName() );
Lo que hemos hecho es obtener un descriptor de clase (tipo Class) mediante miCoche.getClass(). Este descriptor nos devuelve su nombre mediante getName()
– Si tenemos una clase (no hay instancias):Coche.class.getName()
– getName() nos devuelve el nombre de la clase, incluyendo la jerarquía de paquetes. En nuestro ejemplo:
newInstance.dominio.Coche
5
Indice
newInstance()• Podemos cargar de manera dinámica un objeto, es decir, determinar en tiempo de ejecución (y no en tiempo
de programación) la clase que vamos a instanciar• Primero haremos una pequeña clase (Coche) de prueba:
package newInstance.dominio;public class Coche extends Vehiculo {
private int plazas = 5;
public Coche() {}public Coche( int plazas ) { this.plazas = plazas; }public int getPlazas() { return plazas; }public void setPlazas( int p ) { plazas = p; }public String toString() {
try { return super.toString() + " Plazas:" + String.valueOf(plazas); }catch (Exception e) { return "-1"; }
}}
• A continuación crearemos un objeto del tipo Class, que es un descriptor de la estructura de datos o clase. Lo conseguimos con forName(). El paso siguiente es crear una instancia de la clase con newInstance().
Class clase = Class.forName( “newInstance.dominio.Coche” );Object objeto = clase.newInstance();System.out.println(“Coche:" + objeto.toString() );
• Necesitamos al menos un constructor sin parámetros (Coche()) para el uso de newInstance() o bien no especificar ninguno. Pero, si se especifica uno que tenga parámetros (Coche( int plazas )), entonces debe también implementar uno sin parámetros, ya que este es el que usa newInstance()
• Hemos determinado la clase desde una cadena en el código (newInstance.dominio.Coche). Esto no es muy dinámico. Pero las posibilidades de carga dinámica son evidentes:
– Un ejemplo puede ser que en función de una acción del usuario podemos instanciar una clase u otra. En un archivo properties podemos tener la asociación de acciones y clases.
– Otro ejemplo: puedo cargar todas las clases que haya en un determinado directorio, sin tener que determinar estáticamente (a priori en el código) las clase que cargo.
6
Indice
Introducción a las relaciones
Indice
Introducción (I)
• Estamos acostumbrados a ver que diferentes tipos de objetos (naturales o artificiales) se relacionan, por ejemplo en una fabrica, universidad o un despacho de abogados
• Con las clases ocurre algo parecido, se relacionan entre si. Los dos tipos fundamentales de relaciones son:– Relación de clasificación o herencia (jerarquía de
tipos).– Relación de agregación (jerarquía de partes)
8
Indice
Introducción (II)La relación de clasificación o herencia es bastante común. Un autobús o un camión son tipos de vehículo, del mismo modo que un ingeniero o un contable son tipos de empleados. Las subclases (como el autobús o el camión) tienen atributos comunes, por el hecho de ser tipos de la clase vehículo. También decimos que heredan atributos de su superclase:
9
Vehículo
marcamodeloprecio...
Camión
cargaMax
Autobús
plazasAdemás heredan Marca,
Modelo y precio
Lo que no significa que toda clasificación sea sensata. Borges habla de una enciclopedia china donde “está escrito que los animales se dividen en: a) pertenecientes al emperador b) embalsamados c) amaestrados d) lechones e) sirenas f) fabulosos g) perros sueltos h) incluídos en esta clasificación i) que se agitan como locos j) innumerables k) dibujados con un pincel finísimo de pelo de camello l) etcétera. m) que acaban de romper el jarrón n) que de lejos parecen moscas“ (“El idioma analítico de John Wilkins”, Otras inquisiciones, Emecé Editores, Buenos Aires, 1960, p. 142)
Indice
Introducción (III)• La relación de agregación es también bastante frecuente. Por ejemplo,
cuando decimos que: – Una empresa se compone de una serie de departamentos– El catálogo comercial se compone de una serie de productos– La carta de un restaurante se compone de una serie de platos y bebidas
• Tipos:– Composición. La parte (departamento) desaparece cuando desaparece el todo
(Empresa). Otro ejemplo: casa/cocina.– Contenedor. El componente (Ratón) no desaparece con el contenedor
(Computadora). Otro ejemplo: cesta/manzanas.
10
Catálogo Producto
Empresa Departamento
Indice
Herencia
La relación en la que una clase base es el supertipo o generalización de una subclase o subtipo (como la clase
base Persona y la subclase Librero). Entender los mecanismos de la herencia implica comprender el
polimorfismo
Indice
Un primer ejemplo de herencia• Empezamos representando la relación de herencia entre vehículo y
autobus, para una agencia de alquiler:
12
public class Vehiculo {
public int precioDia = 36;
public String marcaModelo = "Volvo" ;
public Vehiculo() {
System.out.println( "Construyo un vehículo");
}
public void mostrarCaracteristicas() {
System.out.print( "Marca: " + marcaModelo +
“ Precio: " + precioDia);
}
}
public class Autobus extends Vehiculo {
public int plazas = 55;
public Autobus() {
System.out.println( "Ha creado un autobus" );
}
public void mostrarCaracteristicas() {
System.out.print( "Marca: " + marcaModelo +
“ Precio: " + precioDia);
System.out.println( " Plazas: " + plazas );
}
} public static void main(String[] args) {
Autobus v1 = new Autobus();
v1.mostrarCaracteristicas();
}
Construyo un vehículo
Ha creado un autobus
Marca: Volvo Precio: 36 Plazas: 55
Es un primer ejemplo en el que el objeto v1 (clase “Autobus”) hereda los atributos de la clase “Vehículo” (usa la palabra extends):
marcaModelo precioDia
Indice
Algunas críticas• El ejemplo anterior mostraba la herencia de
atributos, pero tenía varios inconvenientes, entre los que destaca:– No aprovecha la herencia de métodos– Los atributos no son parametrizados (¿sólo hay
Volvos?)– Repetimos la sentencia System.out.print( "Marca: " + marcaModelo + "Precio:
" + precioDia); en la clase hija– Los datos no son protegidos o encapsulados (principio
de ocultamiento de información)• En resumen, podemos hacerlo mejor
13
Indice
Mejorando el ejemplo
14
public class Vehiculo {
protected int precioDia;
protected String marcaModelo ;
public Vehiculo( String marcaModelo, int precioDia ) {
defMarcaModelo( marcaModelo );
defPrecioDia( precioDia );
System.out.println( "Construyo un vehículo");
}
public void mostrarCaracteristicas() {
System.out.print( "Marca: " + marcaModelo +
“ Precio: " + precioDia);
}
public void defPrecioDia( int precioDia ) {
this.precioDia = precioDia;
}
...
}
public class Autobus extends Vehiculo {
private int plazas = 55;
public Autobus( String marcaModelo, int precioDia,
int plazas) {
super( marcaModelo, precioDia );
this.plazas = plazas;
System.out.println( "Ha creado un autobus" );
}
public Autobus( String marcaModelo, int precioDia ) {
super( marcaModelo, precioDia );
System.out.println( "Ha creado un autobus" );
}
void mostrarCaracteristicas() {
super.mostrarCaracteristicas();
System.out.println( " Plazas: " + plazas );
}
}
DESDE MAIN():
Autobus v1 = new Autobus( "Volvo 550", 120, 57);
v1.mostrarCaracteristicas();
v1.defPrecioDia( 130 );
v1.mostrarCaracteristicas();
Construyo un vehículo
Ha creado un autobus
Marca: Volvo 550 Precio: 120 Plazas: 57
Marca: Volvo 550 Precio: 130 Plazas: 57
Indice
Notas al ejemplo• En Vehiculo:
– Los atributos son protected: esto significa que serán privados para las clases hijas
• En Autobus:– Usamos extends para indicar que es una clase hija de Vehiculo– El atributo plazas tiene un valor por defecto de 55, que sería el que tomase con el
segundo constructor. Observar que el segundo constructor no recibe valor para esta variable, por tanto, su valor sería el de la inicialización (el 55)
– El uso de super() nos permite enviar valores al constructor de la clase madre– El método mostrarCaracteristicas() sobreescribe o anula el método idéntico de la clase
madre. Para acceder al método anulado usa super.:super.mostrarCaracteristicas();
De esta forma el interprete Java sabe que queremos ejecutar la versión de mostrarCaracteristicas() que está en la clase madre
• En main:– Observar la herencia de métodos: el objeto de la clase Autobus llama a defPrecioDia(),
método heredado de la clase Vehiculo
15
Indice
Los usos de la palabra super• En la forma de llamada a una función significa
una llamada al constructor de la clase madre. Debe colocarse en la primera línea del constructor de la hija:
Autobus( String marcaModelo, int precioDia, int plazas) { super( marcaModelo, precioDia ); this.plazas = plazas; System.out.println( "Ha creado un autobus" );}
• En la forma de referencia a un objeto (super.) significa que llamamos a un método de la clase madre que ha sido sobreescrito:
void mostrarCaracteristicas() { super.mostrarCaracteristicas(); System.out.println( " Plazas: " + plazas ); }
16
Indice
Modificadores de acceso y herencia
17
Indice
Herencia de datos privados (private)• Si algo es private en la clase madre: NO SE PUEDE ACCEDER
DESDE LA SUBCLASE (NO SE HEREDA). Pero si se puede acceder desde la subclase a un método de la clase madre que usa ese dato. Por ejemplo, si hago que el precio sea privado:
public class Vehiculo { private int precioDia; ....
}
• Entonces puedo mostrar por pantalla el precio PORQUE LO MUESTRA UN METODO HEREDADO DE LA SUPERCLASE, getPrecioDia() :
public class j08_agencia_alquiler { public static void main(String[] args) { Autobus v = new Autobus( "XXX 800", 140, 58); System.out.println( v.getPrecioDia() );
}}
• v.getPrecioDia() es un método heredado de la clase madre y es este método el responsable de mostrar el precio por pantalla. Pero desde la clase Autobus (subclase) no puedo usar precioDia, tampoco desde la clase j08_agencia_alquiler, que esta en el mismo paquete
18
Indice
Herencia de datos protected (protegidos)• Si algo es protected en la clase base: SE PUEDE ACCEDER
DESDE LA SUBCLASE Y LAS CLASES DEL MISMO PAQUETE (aunque no sean subclases). Ejemplo siguiendo lo anterior: si ponemos:
public class Vehiculo { protected int precioDia; ....
}
• Las siguientes líneas en main() serían correctas:public class j08_agencia_alquiler {
public static void main(String[] args) { Autobus v = new Autobus( "XXX 800", 140, 58); System.out.println( v.getPrecioDia() ); System.out.println( v.precioDia ); // NO ES UN ERROR
}}
• Puede observarse que el atributo precioDia es accesible para la clase j08_agencia_alquiler (donde está main()), PORQUE ESTA EN EL MISMO PAQUETE.
19
Indice
Herencia de datos public (públicos)• Accesible para cualquier subclase o clase
(dentro o fuera del paquete).
• POR DEFECTO: Si no pongo modificador, los atributos y métodos son:– Públicos para las clases del mismo paquete o
subclases– Privados para las clases de otros paquetes o
subclases
20
Indice
Herencia de datos: un consejo
• A modo de resumen y consejo general:
– Si queremos que algo se herede y se pueda usar desde otro paquete, usar public
– Si queremos que algo se herede y no se pueda usar desde otro paquete (pero si desde el propio paquete), usar protected
– Si no se quiere heredar ni acceder, private.
21
Indice
Otro ejemplo de sobreescritura de métodosLas clases hijas (Rectangulo y Circulo) sobreescriben el método getArea():
22
public class Figura {
protected Punto posicion;
public void setPosicion(Punto posicion) {
this.posicion = posicion;
}
public Punto getPosicion() { return posicion; }
public double getArea() { return 0; }
}
public class Circulo extends Figura {
private double radio;
static final public double PI = 3.1416;
…
public double getArea() {
return radio * radio * PI;
}
}
public class Rectangulo extends Figura {
private double ancho;
private double largo;
…
public double getArea() {
return ancho * largo;
}
}
Cada subtipo de “figura” tiene un método getArea(), pero cada método implementa a su manera el cálculo del área. Por ello se dice que nos encontramos ante polimorfismo: un interfaz y múltiples implementaciones.
Un interfaz ya que el interfaz (la declaración del método) es idéntico.
Múltiples implementaciones: hay múltiples formas de calcular el área.
Indice
Polimorfismo• La sobreescritura de métodos permite el polimorfismo. La clase madre
especifica un método, que las clases derivadas sobreescriben, de tal forma que cada clase derivada tiene su implementación del método
• Por ello, se habla de polimorfismo: un interfaz (la misma declaración de método) y múltiples implementaciones (múltiples formas de calcular el área)
• Veamos el siguiente ejemplo. Tenemos una referencia (f) de la clase madre Figura ¿cómo sabe el programa el método getArea() que debe ejecutar?, en función del objeto que se crea (en este caso un círculo):
public static void main(String[] args) { Figura f; f = new Circulo( 40, 40, 3 ); System.out.println( f.getArea() ); f = new Rectangulo( 12, 10, new Punto(30, 33) ); System.out.println( f.getArea() ); }
23
Indice
Clases abstractas• En nuestro ejemplo de figuras la clase madre no tiene implementación del método
getArea()• Esto no es raro en una clase madre, en muchas ocasiones las clases más elevadas en una
jerarquía de herencia especifican un interfaz (declaración de función), sin definir una implementación. En nuestro ejemplo no se puede calcular el área de una figura (si de un Rectángulo o un Círculo)
• Denominamos clases abstractas a aquellas que tienen o heredan un método abstracto, es decir, métodos declarados pero no implementados.
• No puede haber instancias de las clases abstractas, lo que no resulta extraño: nos puede interesar que se hagan instancias de Rectangulo o Circulo, pero no permitir instancias de Figura.
• En nuestro ejemplo la clase madre puede ser:package figuras.dominio;abstract public class Figura { protected Punto posicion;
public void setPosicion(Punto posicion) { this.posicion = posicion; } public Punto getPosicion() { return posicion; } abstract public double getArea();}
24
Las clases hijas deben implementar el método abstracto
Indice
final
• La palabra final tiene varios sentidos:– Precediendo a una variable: la define como una
constante y no puede ser modificada– Precediendo a un método: indica que el método
no puede ser sobrescrito– Precediendo a una clase: indica que no se pueden
definir subclases
25
Indice
Agregación
Una relación en la que el objeto es una parte de otro objeto (como la batería es
una parte del teléfono móvil). Nos ayudaremos de la clase Vector del JDK
Indice
Agregación: composición• En ocasiones interesa crear objetos que se
componen de otros objetos• La parte desaparece cuando desaparece el todo
27
public class Casa {
private Dormitorio dormitorios[];
private Salon salon;
private Cocina cocina;
…
}
Indice
Agregación: contenedor
• El componente (por ejemplo, un producto de un catálogo comercial) no desaparece con el contenedor (catálogo)
• Conviene usar alguna de las clases del JDK para hacer colecciones de objetos, por ejemplo Vector, ArrayList, etc.
• Están en el paquete java.util
28
Indice
Vector (I)• En Java las matrices son de longitud fija, una vez creadas no se puede
modificar su tamaño• Hay un truco para saltarse esta limitación: crear otra matriz más grande y
copiar de la matriz original a la nueva• Java nos suministra una clase, denominada Vector, que nos permite utilizar
una matriz de longitud variable• Constructores:
– Vector(): tamaño inicial de 100 y se incrementa duplicando tamaño– Vector( int tamaño ): señala el tamaño y se incrementa duplicando tamaño– Vector( int tamaño, int incremento ): señala tamaño e incremento
• Para añadir: add( Object elemento ). Ejemplo:Vector v = new Vector(50,5);v.add( new Persona( “Pedro” ) );
• En muchos métodos se tiene en cuenta el índice o posición dentro del Vector. Por ello, conviene recordar que el primer elemento es el cero. Para insertar en una posición determinada (desplazando el resto hacia la derecha): insertElementAt( Object elemento, int posición )
29
Indice
Vector (II)• Para conocer el número de elementos, así como la
capacidad del Vector tenemos int size() e int capacity():
Vector v = new Vector(50,5);v.addElement( new Persona( “Pedro” ) );System.out.println( v.size() ); // Muestra 1System.out.println( v.capacity() ); // Muestra 50
• Para eliminar un elemento: removeElementAt( int posición ). Desplaza los elementos de la derecha para llenar el hueco.
• Para obtener el elemento de la posición indicada: Object elementAt( int posición ) u Object get( int posición )
30
Indice
Versión “final” del proyecto de las figuras
• Vamos a ver como queda finalmente el proyecto de las figuras, en el que se ha aplicado:
– Modularización: se ha separado en tres paquetes la presentación, clases del dominio y la clase de inicio
– Encapsulamiento: datos privados. Tambien la complejidad de la presentación queda encapsulada (oculta), al final no hay más que llamar al método “VistaFiguras.mostrar()”, pasando como argumento el objeto que se quiere mostrar
– Sobrecarga de métodos– Herencia: hay una clase madre “Figura” que tiene métodos y atributo para la posición
de la figura– Clase abstracta: la clase “Figura” es además abstracta, ya que tiene el método abstracto
“getArea()”– Agregación: la clase Pagina es un contenedor de figuras– Una clase (Figura) usa de otra (Punto). El atributo “Punto posicion” de la clase Figura
hace referencia al punto de su posición– Atributo static y final: “Circulo.PI”, ya que sólo hay un número PI, aunque haya cero o
millones de círculos– Métodos static: “VistaFigura.mostrar()”, no hace falta instanciar la clase para llamar al
método. Es una forma de implementación común en clases de utilidad.
31
Indice
Versión “final” del proyecto de las figuras. Los puntos y las figuras
package figuras.dominio;
public class Punto { private int x; private int y;
public Punto(int x, int y) { setPunto(x, y); } public Punto(Punto p) { setPunto(p ); } public void setPunto(int x, int y) { this.x = x; this.y = y; } public void setPunto(Punto p) { x = p.getX(); y = p.getY(); }
public int getX() { return x; } public int getY() { return y; }
public String toString() { return "(" + x + "," + y + ")"; }}
32
Todas las figuras usan de Punto, ya que todas heredan el atributo 'posicion' de Figura
Figura es una clase abstracta
package figuras.dominio;
abstract public class Figura { protected Punto posicion;
public void setPosicion(Punto posicion) { this.posicion = posicion; } public Punto getPosicion() { return posicion; } abstract public double getArea();}
Indice
Versión “final” del proyecto de las figuras. Los círculospackage figuras.dominio;
public class Circulo extends Figura { private double radio; static final public double PI = 3.1416;
public Circulo() { } public Circulo( double nuevoRadio, Punto nuevaPosicion ) { setRadio( nuevoRadio ); setPosicion( nuevaPosicion ); } public Circulo( double nuevoRadio, int posicionX, int posicionY ) { setRadio( nuevoRadio ); posicion = new Punto( posicionX, posicionY ); } public Circulo( Circulo circulo ) { setRadio( circulo.getRadio() ); setPosicion( circulo.getPosicion()); } public void setRadio( double radio ) { this.radio = radio; } public double getRadio() { return radio; } public double getArea() { return radio * radio * PI; } public String toString() { return "Radio: " + radio + " Posicion: " + posicion.toString() + " Area: " +
getArea(); }}
33
Los círculos heredan de Figura, por tanto tienen que implementar el método getArea()
Indice
Versión “final” del proyecto de las figuras. Los rectángulospackage figuras.dominio;
public class Rectangulo extends Figura { private double ancho; private double largo;
public Rectangulo( double ancho, double largo, Punto posicion ) { setDimensiones( ancho, largo ); setPosicion(posicion); } public void setDimensiones( double ancho, double largo ) { this.ancho = ancho; this.largo = largo; } public double getArea() { return ancho * largo; }
public String toString() { return "Ancho: " + ancho + " Largo: " + largo + " Posicion: " + posicion.toString() " Superficie: " + getArea(); }}
34
Indice
Versión “final” del proyecto de las figuras. La página (agregador)package figuras.dominio;
import java.util.*;
public class Pagina { private Vector vecFiguras = new Vector(); public void agregar( Figura fig ) {
vecFiguras.add( fig ); } public boolean desagregar( int indice ) { if ( indice < vecFiguras.size() ) { try { vecFiguras.remove(indice); return true; } catch (Exception e) { return false; } } return false; } public Figura obtener( int indice ) { if ( indice < vecFiguras.size() ) { try { return (Figura) vecFiguras.get(indice); } catch (Exception e) { return null; } } return null; } public int tamanio() { return vecFiguras.size(); }} 35
• Utilizamos la clase java.util.Vector para agregar figuras
• La utilidad de usar la herencia (o los interface lógicos): en “agregar()” usamos una referencia genérica (Figura fig). Este método puede recibir cualquier clase hija (Rectangulo, Circulo, etc.). Es muy interesante ya que, si no fuese así, tendríamos que implementar el método agregar para cada una de las subclases
• En “obtener()” no eliminamos o quitamos el objeto del vector, simplemente devolvemos una referencia al objeto. ¿Por qué necesitamos hacer casting?
• Gestionamos excepciones con try - catch
Indice
Versión “final” del proyecto de las figuras. La visualizaciónpackage figuras.presentacion;
import figuras.dominio.*;
public class VistaFiguras {
public static void mostrar( Circulo cir ) { if ( cir != null ) System.out.println( cir.toString() ); else System.out.println( "Error al intentar mostrar el círculo" );
}
public static void mostrar( Rectangulo rec ) { if ( rec != null ) System.out.println( rec.toString() ); else System.out.println( "Error al intentar mostrar el rectángulo" ); }
public static void mostrar( Pagina pag ) { if ( pag == null ) { System.out.println("Error al intentar mostrar la página"); return; } for ( int i = 0; i < pag.tamanio(); i++ ) { Figura fig = pag.obtener(i); System.out.println( fig.toString() ); } }}
36
Aspectos a resaltar:
• Polimorfismo con sobrecarga de métodos: todos los métodos se llaman igual, pero actúan de forma diferente (patrón estrategia)
• Para mostrar las figuras de una página: recorremos los elementos con un for(). Dentro del for(), para obtener la figura (sea un rectángulo o un círculo) usamos una referencia genérica del tipo “Figura” (clase madre), a la que mandamos el mensaje toString(). Un ejemplo típico de polimorfismo: usamos el mismo mensaje (toString) para producir comportamiento diferente, en función del objeto unos se representan de una forma y otros de otra
Indice
Versión “final” del proyecto de las figuras. El inicioimport figuras.presentacion.*;
class Inicio { //// Usa agregador public static void main(String[] args) { Circulo primero = new Circulo( 23, 2, 3 ); Circulo copia = new Circulo( primero ); Circulo tercero = new Circulo( 17, new Punto(8,9) ); Rectangulo rectangulo = new Rectangulo(3.5, 2, new Punto(55,54)); Pagina pag = new Pagina(); pag.agregar( primero); pag.agregar( copia); pag.agregar( tercero ); pag.agregar( rectangulo );
VistaFiguras.mostrar( pag ); }}
37
Puesto que el método mostrar() es static podemos usar VistaFiguras sin
instanciarla