Prog. Ampliación POO

De MediaWiki
Ir a la navegación Ir a la búsqueda



Métodos get y set



Librerías

  • Vimos durante el curso que las clases que tienen algún tipo de relación entre ellas, deben de agruparse bajo un mismo 'paquete'.
  • Recordar también que las clases que se encuentran dentro de un mismo paquete tienen ciertos privilegios de acceso a los métodos/atributos del resto de las clases (modificar de acceso protected).
Por lo tanto podemos considerar los paquetes como una forma de 'limitar' el acceso a métodos y atributos definidos en las clases que se encuentran en el paquete (forma de encapsulación).


  • También vimos que para hacer uso de una clase definida en un paquete es necesario utilizar la orden import.


  • Una librería en un conjunto de clases relacionadas las cuales pueden estar definidas en diferentes paquetes, pero todas dentro del mismo 'archivo físico' el cual no es más que un archivo comprimido donde se encuentras las clases compiladas en sus correspondientes paquetes (directorios).
Por ejemplo, los paquetes que proporciona Java en el JDK y que ya vimos en un punto anterior de este manual se encuentran en el archivo /direc_instalación_jdk_o_jre/lib/rt.jar como podemos ver en la siguiente imagen:
Prog librerias 1.jpg





  • Ejercicio propuesto:
  • Modifica el ejercicio del programador novato y crea una librería en la que se encuentren las clases Vehiculo, Coche y Transporte (que están dentro del paquete 'transportes').
Crea el archivo jar y después haz uso del mismo dentro del proyecto donde se encuentra la clase Principal.
  • Añade a la misma librería las clases creadas en el ejercicio final y que se encuentran dentro del paquete 'material.deportivo'.
Modifica el proyecto donde se encuentra la clase Principal para que haga uso de la librería.



Excepciones

  • Podéis consultar la jerarquía de excepciones en este gráfico.
  • Nosotros trabajamos sobre las que derivan de la clase Exception, ya que las que derivan de la clase Error son errores de la máquina virtual de Java (por ejemplo que no disponga de memoria) y esos errores no son gestionados por las aplicaciones desarrolladas por nosotros.


  • Heredando de la clase Exception tenemos la clase la clase RunTimeException. Este tipo de excepción junto a todas sus subclases, son denominadas excepciones de tipo 'unchecked'. A diferencia de las otras, no se obliga a poner un try...catch cuando una instrucción lanza este tipo de excepción.
Esto es debido a que estas excepciones son lanzadas cuando hay un problema en el código del programa (el programa está mal hecho) por lo que normalmente querremos que el programa acabe.
Imaginemos que disponemos de un objeto de la clase Scanner para leer datos del teclado. Supongamos que en un momento dado, dentro de nuestro código 'cerramos' el objeto (llamando al método close) y después intentamos leer del teclado. Esto daría consigo que se lanzaría la excepción 'IllegalStateException' (que derive de RunTimeException). El compilador de Java no nos obliga a tener un try..catch ya que es un fallo de programación.
A diferencia de las excepciones 'checked' las cuales se lanzan no porque el programa esté mal, sino porque vienen datos inadecuados o situaciones ajenas al programa, como por ejemplo, intentar abrir un fichero que no existe o intentar dividir por cero un dato que fue introducido por el usuario. Este tipo de excepciones sí que son necesarias capturarlas para que el programa funcione con normalidad.




Delegación de excepciones

  • Vimos en teoría que las excepciones son errores no controlados por el usuario y que normalmente se producen en el envío de datos no adecuados a los métodos de una clase.
Por ejemplo, cuando intentamos acceder a una posición de una cadena que no existe: String nombre = "Angel"; char letra = nombre.charAt(55);
Sintácticamente el código es correcto, pero la ejecución dará un error en tiempo de ejecución que hará que se pare el programa.


  • Si un método no gestiona las excepciones que puedan producirse, el error irá desplazándose hasta el método que realizó la llamada y así sucesivamente hasta llegar a un método que gestione el error y en el caso de que no exista, llegará hasta la máquina virtual de Java, que detendrá la ejecución del programa.


  • En este manual realizamos ejercicios en el manejo de las excepciones.
En esos ejercicios, la 'captura' de la excepción se realizaba en el propio método donde se podía producir el error.


Supongamos que tenemos una clase Lectura que se encarga de leer por teclado diferentes tipos de datos.
 1 package EntradaSalida;
 2 
 3 import java.util.Scanner;
 4 
 5 /**
 6  *
 7  * @author clase
 8  */
 9 public class Lectura {
10     private static Scanner sc = new Scanner(System.in);
11     
12     public static int leerNumeroEntero() {
13 
14         int numero = sc.nextInt();
15         return numero;
16     }
17 }


  • Si hacemos uso de esta clase, puede darse el caso de que el usuario no introduzca un número entero, produciéndose una excepción del tipo: java.util.InputMismatchException
En los ejercicios anteriores vimos que la excepción la podemos controlar nosotros dentro del método de la forma:
 1     public static int leerNumeroEntero() {
 2         int numero=0;
 3         try{
 4          numero = sc.nextInt();
 5         }
 6         catch(java.util.InputMismatchException ime){
 7             numero = 0;
 8         }
 9         return numero;
10     }
En este tipo de gestión de las excepciones, el método es el que se encarga de corregir el error del usuario, pero puede darse el caso de que no queramos que sea el método quien se encargue de eso, y que sea el que haga uso del método el que se preocupe de controlar el error y realizar las acciones adecuadas para subsanarlo.
  • Para ello, en la definición del método podemos indicar que tipos de excepciones va a lanzar, de la forma:
1 public void nombreMetodo() throws TipoExcepcion1[, TipoExcepcion2,....] {
2 
3 }
Como podemos ver podemos indicar que el método va a 'lanzar' diferentes tipos de excepciones, ya que puede ser que dentro del código se ejecuten sentencias que puedan producir diferentes tipos.
A esta forma de gestionar las excepciones se denomina Delegación de excepciones.


  • Aplicado a nuestro ejemplo:
1     public static int leerNumeroEntero() throws java.util.InputMismatchException{
2 
3         int numero = sc.nextInt();
4         return numero;
5     }


Ahora quien tiene que gestionar la excepción es la clase que haga uso del método leerNumeroEntero().
Lo podemos comprobar:
Prog excepciones 1.jpg
Ahora, cuando una clase hace uso de este método puede comprobar como va a lanzar una excepción y el tipo de excepción que puede lanzar.
Por lo tanto, el try-catch lo tendrá que hacer la clase que hace uso de este método:
 1 package proyectojava;
 2 
 3 import EntradaSalida.Lectura;
 4 /**
 5  *
 6  * @author clase
 7  */
 8 public class Principal2 {
 9     
10     public static void main (String arg[]){
11         
12       try {
13           System.out.printf("Numero entero:%d",Lectura.leerNumeroEntero());
14       }  
15       catch(java.util.InputMismatchException ime){
16           System.out.println("El dato introducido no es un número entero.");
17       }
18         
19     }
20     
21     
22 }


Ejercicios propuestos

  • Podéis responder a algunas de las preguntas planteadas en este enlace.




Lanzar excepciones

  • Puede darse el caso de que en ciertas circunstancias queramos generar nosotros una excepción para informar a quien llama a un determinado método, de que los datos enviados no son los correctos (por ejemplo).
Esto lo conseguimos 'lanzando' una excepción. Lanzar una excepción es equivalente a que se produzca una excepción en nuestro código y que provocará que nuestro programa 'pare' si no está implementado el try-catch correspondiente.
  • Imaginemos que disponemos de una clase OperacionesIntervalos en la que disponemos de un método que calcula el número de números pares en un intervalo, siempre que este esté entre los números 1 y 1000.
El usuario podría enviar dos números fuera de los límites de dicho intervalo.
El usuario podría enviar los números del intervalo en orden contrario, enviando primero el número mayor, por ejemplo...
En ese caso, el método podría devolver un valor 'por defecto', como por ejemplo 0, pero este funcionamiento no es muy adecuado, ya que podría pensarse que el número de números pares es ese valor.
Para evitar este funcionamiento, podemos hacer que si los números del intervalo están fuera de los límites o si el orden no es el adecuado, provocaremos una excepción.
Para eso haremos uso de la instrucción throw.


  • Throw lleva asociado la instanciación de un tipo de excepción (recordar que las excepciones son clases). Dependiendo del tipo de error podremos lanzar un tipo de excepción.
Para lanzar una excepción podemos poner: throw new TipoExcepcion(mensaje);
Mensaje es una cadena en la que podemos indicar cual fue el motivo de la excepción.
  • Aplicado a nuestro ejemplo:
 1 package varios;
 2 
 3 /**
 4  *
 5  * @author clase
 6  */
 7 public class OperacionesIntervalos {
 8     public static final int LIMITE_INFERIOR = 1;
 9     public static final int LIMITE_SUPERIOR = 1000;
10 
11     public static int obtenerNumerosParesIntervalo(int inferior, int superior) throws IndexOutOfBoundsException{
12         int numPares = 0;
13         
14         if (inferior > superior){
15             throw new IndexOutOfBoundsException("Limite inferior no puede ser menor que el superior");
16         }
17         if (inferior < LIMITE_INFERIOR || superior > LIMITE_SUPERIOR){
18             throw new IndexOutOfBoundsException("Los límites del intervalo se pasan de los valores permitidos");
19         }
20         for(int cont=inferior;cont<=superior;cont++){
21             if (cont%2==0){
22                 numPares++;
23             }
24         }
25         return numPares;
26     }
27     
28 }
  • Línea 11: Indicamos que este método va a poder lanzar una excepción del tipo 'IndexOutOfBoundsException'
  • Línea 15: En caso de que el número que indica el límite inferior del intervalo, tenga un valor mayor que el segundo número, lanzaremos una excepción del tipo indicado, enviando un texto explicativo del motivo de la excepción.
  • Línea 18: Igual que en la línea 11 pero esta vez debido a que nos pasamos de los límites que puede tener el intervalo.


 1 package proyectojava;
 2 
 3 import varios.OperacionesIntervalos;
 4 /**
 5  *
 6  * @author clase
 7  */
 8 public class Principal2 {
 9     
10     public static void main (String arg[]){
11         
12       try {
13           System.out.printf("Numero de pares entre 2 y 50:%d",OperacionesIntervalos.obtenerNumerosParesIntervalo(2,50));
14           System.out.printf("Numero de pares entre 30 y 5:%d",OperacionesIntervalos.obtenerNumerosParesIntervalo(30,5));
15       }  
16       catch(IndexOutOfBoundsException iobe){
17           System.out.println("\nEl intervalo no es correcto");
18           System.out.println("Mensaje de la excepción:" + iobe.getMessage());
19       }
20       try {
21           System.out.printf("Numero de pares entre -10 y 50:%d",OperacionesIntervalos.obtenerNumerosParesIntervalo(-10,50));
22       }  
23       catch(IndexOutOfBoundsException iobe){
24           System.out.println("\nEl intervalo no es correcto");
25           System.out.println("Mensaje de la excepción:" + iobe.getMessage());
26       }
27         
28     }
29 }
  • Línea 14: Esta llamada va a provocar que se lance una excepción, ya que el primer número es mayor que el segundo.
  • Línea 18: Podemos recoger el texto que viene con la excepción.
  • Línea 21: Esta llamada va a provocar que se lance una excepción, ya que el intervalo no está entre 1 y 1000.
  • Línea 25: Podemos recoger el texto explicativo de la excepción.


  • Recordar que un método puede lanzar múltiples excepciones, teniendo que indicar los tipos de excepciones que lanza en la definición del método, después de la palabra clave throws separadas por comas.


Lanzar excepciones propias

  • Puede suceder que el error que estamos controlando no tenga una correspondencia con alguno de los tipos de excepción de Java.
Por ejemplo, en el caso anterior, lanzamos una excepción de tipo IndexOutOfBoundsException, que normalmente se emplea cuando intentamos acceder a una posición de un array que no existe.
En el caso anterior, podríamos darla por 'buena' en el caso de que intentemos calcular los números pares fuera del intervalo 1 - 1000, pero en el caso de que enviemos los números del intervalo en orden diferente, no se ajustaría al significado del tipo de excepción.
  • Es en estos casos en los que podemos 'crear' nuestras propias clases de excepciones.
Recordar que una excepción es una clase, por lo tanto, lo que tenemos que hacer es definir una clase que derive de la clase base 'Exception' y que tenga un constructor con un parámetro de tipo String, que será el mensaje que guardará la excepción (esto si queremos que tenga dicha funcionalidad).
1 public class MiExcepcion{
2    
3    public MiExcepcion(String msg){
4        super(msg);
5    }
6 
7 }
Como vemos, en el constructor llamaremos al constructor de la clase Exception enviando ese mimos mensaje.


  • Apliquemos esto al ejemplo anterior:

Clase IncorrectoIntervaloException (representa el tipo de excepción personalizado)

1 package varios;
2 
3 public class IncorrectoIntervaloException extends Exception{
4     
5     public IncorrectoIntervaloException(String msg){
6         super(msg);
7     }
8     
9 }


Clase OperacionesIntervalos

 1 package varios;
 2 
 3 /**
 4  *
 5  * @author clase
 6  */
 7 public class OperacionesIntervalos {
 8     public static final int LIMITE_INFERIOR = 1;
 9     public static final int LIMITE_SUPERIOR = 1000;
10 
11     public static int obtenerNumerosParesIntervalo(int inferior, int superior) throws IndexOutOfBoundsException,IncorrectoIntervaloException{
12         int numPares = 0;
13         
14         if (inferior > superior){
15             throw new IncorrectoIntervaloException("Limite inferior no puede ser menor que el superior");
16         }
17         if (inferior < LIMITE_INFERIOR || superior > LIMITE_SUPERIOR){
18             throw new IndexOutOfBoundsException("Los límites del intervalo se pasan de los valores permitidos");
19         }
20         for(int cont=inferior;cont<=superior;cont++){
21             if (cont%2==0){
22                 numPares++;
23             }
24         }
25         return numPares;
26     }
27     
28 }
  • Línea 15: Aquí lanzamos la excepción personalizada. Fijarse que no hace falta importar la clase de la excepción ya que en el ejemplo se encuentra en el mismo paquete.


Clase Principal2

 1 package proyectojava;
 2 
 3 import varios.OperacionesIntervalos;
 4 import varios.IncorrectoIntervaloException;
 5 /**
 6  *
 7  * @author clase
 8  */
 9 public class Principal2 {
10     
11     public static void main (String arg[]){
12         
13       try {
14           System.out.printf("Numero de pares entre 2 y 50:%d",OperacionesIntervalos.obtenerNumerosParesIntervalo(2,50));
15           System.out.printf("Numero de pares entre 30 y 5:%d",OperacionesIntervalos.obtenerNumerosParesIntervalo(30,5));
16       }  
17       catch(IndexOutOfBoundsException iobe){
18           System.out.println("\nMensaje de la excepción:" + iobe.getMessage());
19       }
20       catch(IncorrectoIntervaloException iie){
21           System.out.println("\nMensaje de mi excepción:" + iie.getMessage());
22       }
23       try {
24           System.out.printf("%nNumero de pares entre -10 y 50:%d",OperacionesIntervalos.obtenerNumerosParesIntervalo(-10,50));
25       }  
26       catch(IndexOutOfBoundsException iobe){
27           System.out.println("\nMensaje de la excepción:" + iobe.getMessage());
28       }
29       catch(IncorrectoIntervaloException iie){
30           System.out.println("\nMensaje de mi excepción:" + iie.getMessage());
31       }
32         
33     }
34 }
  • Línea 4: Importamos la clase del nuevo tipo de excepción.
  • Línea 20: Capturamos el tipo de excepción personalizada.



printStackTrace()

  • Este método lo encontramos dentro de cualquier objeto que pertenezca a una clase de excepción.
Cuando realizamos el catch, indicamos el nombre del objeto que va a capturar el tipo de excepción indicado.
Ese objeto tiene la información del error, que la obtenemos con la llamada al método 'getMessage()' y también podemos obtener el origen del error con la llamada a 'printStackTrace()'.


  • Como comentamos anteriormente, cuando se produce una excepción el error se va propagando entre los métodos hasta llegar a uno que gestione el error (tenga el catch).
Con la llamada a printStackTrace, podemos saber en qué método se originó la excepción.


  • Veamos un ejemplo:

Clase MiClase

 1 package proyectojava;
 2 
 3 /**
 4  *
 5  * @author clase
 6  */
 7 public class MiClase {
 8     
 9     public void metodo1()  {
10         
11         System.out.println("Método 1 se ejecuta");
12         metodo2();
13         
14     }
15     public void metodo2()  {
16         
17         System.out.println("Método 2 se ejecuta");
18         metodo3();
19         
20     }
21     public void metodo3()  {
22         
23         System.out.println("Método 3 se ejecuta");
24         System.out.println("Método 3 lanza excepción");
25 
26         String cad="";
27         char car = cad.charAt(10);
28     }
29     
30 }
  • Línea 27: Como vemos esta clase llama a tres métodos y el último de ellos provoca una excepción.


Clase Principal3

 1 package proyectojava;
 2 
 3 /**
 4  *
 5  * @author clase
 6  */
 7 public class Principal3 {
 8     
 9     public static void main (String arg[]){
10     
11         MiClase miclase = new MiClase();
12         try{
13             miclase.metodo1();
14         }
15         catch(Exception e){
16             System.out.println("CAPTURANDO EXCEPCION:" + e.getMessage());
17             e.printStackTrace(System.err);
18         }
19     }
20 
21 }
  • En la línea 17 mostramos la traza de errores, es decir, desde donde se produce el error y como se va 'propagando' hasta llegar a un método que gestione el error.
En este ejemplo estamos utilizando la salida de errores (System.err) y por eso la información sale en color rojo. Podríamos ponerla en la salida estándar con 'System.out'.


Prog excepciones 2.jpg


  • Normalmente lo emplearemos cuando necesitemos depurar un programa para ver el origen de un error.




Ejercicios propuestos

Ejercicio 1

  • Crea una clase de nombre MiCadena.
Cuando se instancie dicha clase, se podrá mandar como dato una cadena que se guardará en un atributo de la clase.
Dispondrá de los siguientes métodos:
  • concatenar(String dato): concatenerá a la cadena guardada la cadena del parámetro.
  • devolverLong(): Intentará convertir la cadena guardada a un long y devolverlo. Podrá lanzar una excepción 'ConversionNumeroException'.
  • convertirCadenaNumero(): analizará la cadena buscando dígitos numéricos. En caso de encontrarlo, guardará en la cadena los dígitos encontrados descartando el resto. Por ejemplo: "A12ddddd2423" dará como resultado: "122423". En caso de no encontrar números lanzará la excepción 'NoNumeroException'.
  • devolverPosicionCadena(int pos): Devolverá el caracter encontrado en la posición indicada. Podrá lanzar un IndexOutOfBoundsException.
  • compararNumero(int valor): Comparará el número enviado con el número guardado en la cadena. Si es mayor valor, devolverá false, en caso contrario devolverá true. Debes hacer uso del método devolverEntero() y la excepción no la debes gestionar tú.
  • asignarValor(String cadena): Guardará la cadena enviada como parámetro sustituyendo a la cadena guardada previamente. En caso de que la cadena a enviar sea vacía o tenga el valor null, lanzará una excepción de tipo ValorCadenaException.
  • Sobreescribe el método toString() para que muestre la cadena en caso de 'imprimir' el objeto.


Crea una clase Principal y haz una llamada a cada uno de los métodos anteriores.


Posible solución Excepciones Ejercicio 1




Ejercicio 2

  • Crea una clase de nombre Lectura.
Todos los métodos podrán ser llamados sin necesidad de instanciar la clase.
Define un único objeto Scanner.
La entrada de datos se realizará en cada uno de los métodos.
Crea los siguientes métodos:
  • leerEntero(): debe devolver un número entero entre 1 y 100. En caso de que no introduzca un número se lanzará la excepción 'InputMismatchException' y en el caso de que no esté entre los límites (valores entre 1 y 100), lanzará la excepción 'LimitesException' (es definida por el usuario). En cualquier otro caso lanzará la excepción Exception.
  • leerCadena(): debe devolver una cadena de texto introducida por teclado. Dicha cadena de texto no puede ser vacía. Las excepciones deben ser controladas por el que realiza la llamada.
  • leerBooleano(): debe devolver un booleano introducido por teclado. Las excepciones deben ser controladas por el que realiza la llamada.
Crea una clase Principal y haz una llamada a cada uno de los métodos anteriores.



Posible solución Excepciones Ejercicio 2



Clases abstractas


Aclaraciones

  • En ciertos apuntes viene indicado que las clases abstractas no pueden tener métodos privados ni métodos de clase. Esto no es cierto. Lo que no puede ser es que se declaren métodos abstractos privados ya que no podrían ser implementados por las subclases.
La única diferencia entre una clase 'normal' y una clase 'abstracta' es que no se pueden instanciar objetos de una clase abstracta.
A mayores una clase abstracta puede contener métodos abstractos que 'obligan' a que las subclases implementen su código.


  • Para que una clase sea abstracta no tiene que tener un método abstracto. Puede ser abstracta indicando la palabra reservada 'abstract' en su definición. Esto también viene mal indicado en ciertos blogs de internet.


  • La utilización de clases abstractas la podemos delimitar a dos situaciones:
  • Cuando tenemos una relación de herencia en la que no queremos que se pueda crear objetos de la superclase, sino que aprovechamos las características de la herencia para hacer que el código de ciertos métodos y atributos comunes no se repitan en cada una de las 'subclases'. En este caso haríamos la superclase 'abstract'.
  • Cuando tenemos una relación de herencia y queramos definir una funcionalidad que es común para todas las subclases, pero cuya implementación es diferente en cada una de ellas. En este caso haremos abstracto el método, obligándonos a definir como abstracta a la clase.


  • Podemos definir 'variables' del tipo de la clase abstracta y podremos con dicha variable referenciar a objetos que pertenezcan a clases que deriven de la clase abstracta. En este caso haremos uso del polimorfismo al llamar a algún método que esté definido en la clase abstracta.


  • Ejemplo de uso de clases abstractas:
  • En nuestro ejercicio del programador novato, la clase Vehiculo podría ser una clase abstracta.
  • Si tenemos una clase Figura, podemos declarar un método abstracto de nombre 'dibujar'. La implementación de dicho método será diferente en cada subclase. Subclases podrían ser Cuadrado, Circulo, Triangulo,..
  • Si tenemos una clase Intrumento con un método 'tocar', dicho método será abstracto y tendremos que implementar el código concreto en cada subclase, como podrían ser Violin, Piano, Viola,...


  • Como vimos en un punto anterior del manual cuando hablamos de polimorfismo, vimos que cuando se llama a un método se ejecuta el método que pertenece a la clase del objeto referenciado y no al de la clase de la variable que lo referencia.
En el ejemplo del enlace, podríamos tener un array definido de la siguiente forma: Europeo europeos[] = {new Espanol(....), new Frances(....), new Espanol(...)}
Si intentamos utilizar un elemento del array de la forma: europeos[0], sólo podremos hacer uso de los métodos y atributos definidos en Europeo (a pesar de que es realmente un objeto de la clase Espanol).
Si quisiéremos hacer uso de los métodos y atributos definidos en la clase Espanol, podemos hacer un cast de la forma: ((Espanol)europeos[0])
Si recorremos el array y queremos saber a que clase pertenece cada uno de los elementos del array, podemos hacer uso del método instanceof de la forma: if (europeos[cont] instanceof Espanol){ .... }



Ejercicios


Nota: Todos estos ejercicios están obtenidos de Internet. El sitio original está referenciado en su correspondiente enlace de la solución.



  • Declara una clase Persona que pueda ser instanciada con los atributos: nombre, apellidos, edad y estado civil (soltero/casado).
Declara una clase abstracta Legislador que herede de la clase Persona, con un atributo provinciaQueRepresenta (tipo String), partido político (tipo String) y despacho que ocupa (byte). Declara un método abstracto getCamaraEnQueTrabaja que devuelve una cadena String. Crea dos clases concretas que hereden de Legislador: la clase Diputado y la clase Senador que sobreescriban los métodos abstractos necesarios. Crea una lista de legisladores y muestra por pantalla la cámara en que trabajan (harás uso del polimorfismo).
En todas las clases sobreescribe el método toString para que muestre la información de cada clase e 'imprime' cada uno de los legisladores.
Para crear una variable de tipo array en la que cada elemento sea un objeto de una clase debemos de definirlo de la forma: Clase miembros[] = { new Clase(), new Clase()};
Recuerda que para hacer referencia a un elemento de un array debemos de indicar su posición de la forma nombre_array[pos] siendo pos un número que va desde 0 (posición inicial del array) hasta nombre_array.length - 1
Posible solución => Enlace 1


  • Declara una clase Persona que no pueda ser instanciada con los atributos: nombre, apellidos y edad.
Crea una clase Profesor que sea una subclase de la clase Persona, que tenga como atributos un idProfesor (numérico) y un método abstracto, importeNomina() que devolverá un número con decimales.
Crea una subclase de Profesor de nombre ProfesorTitular, en el que la nómina es igual a 30 horas lectivas multiplicadas por 43.20 euros cada hora.
Crea una subclase de Profesor de nombre ProfesorInterino, que tendrá como dato la fecha de inicio de la interinidad. Dicha fecha será enviada en el formato (dd/mm/yyyy) y tendrá un método devolverAños() que devolverá un número de tipo byte, que representará el número de años que lleva el Interino trabajando.
La nómina en el caso de los interinos es igual a 30 horas lectivas multiplicadas por 35.60 euros cada hora.
En todas las clases sobreescribe el método toString para que muestre la información.


Posible solución => Enlace 2.
Con respecto a la solución dada se tienen que considerar las siguientes diferencias:
  • Persona se pide que se defina como abstracta.
  • Las horas y precio por hora deben ser declaradas como constantes.
  • La fecha es enviada en forma de cadena y cuando se quiera calcular el número de años, deberá de hacerse uso de la clase Calendar.


  • La empresa XYZ requiere una aplicación informática para administrar los datos de su personal.
Del personal se conoce: número de DNI, nombre, apellidos y año de ingreso.
Existen dos categorías de personal: el personal con salario fijo y el personal a comisión. Los empleados con salario fijo tienen un sueldo básico y un porcentaje adicional en función del número de años que llevan: menos de dos años salario base, de 2 a 3 años: 5% más; de 4 a 7 años: 10% más; de 8 a 15 años: 15% más y más de 15 años: 20% más. Los empleados a comisión tienen un salario mínimo que será constante para todos los empleados de este tipo e igual a 750.00€, un número de clientes captados y un monto por cada cliente captado. El salario se obtiene multiplicando los clientes captados por el monto por cliente, si el salario por los clientes captados no llega al salario mínimo, cobrará esta cantidad.
Se contará con una clase padre Empleado de la cual no se podrán crear objetos y de la que heredan las clases EAsalariado y EComision. En todas las clases debe haber un constructor con parámetros para todos los atributos y otro vacío. En todos deben crearse los getters and setters correspondientes. Empleado contará con un método imprimir() y un método obtenerSalario().
Se creará una clase gestora y en el método main se creará un vector con los siguientes objetos:
Javier Gómez, DNI: 569587A, desde 2008, salario fijo base = 1225.00€.
Eva Nieto, DNI: 695235B, desde 2010, 179 clientes captados a 8.10€ cada uno.
José Ruiz, DNI: 741258C, desde 2012, 81 clientes captados a 7.90€ cada uno.
María Núñez, DNI: 896325D, desde 2013, salario fijo base = 1155.00€.
Los dos primeros se crearán utilizando el constructor con todos los parámetros y los dos últimos con el constructor vacío y utilizando los setters adecuados.


Nota: Un vector es un array. Para definir un array con un tamaño determinado debemos de indicarlo de la forma: NombreClase nombre_array[] = new NombreClase[tamaño_del_array];
Recordar que para recorrer un array necesitamos utilizar un bucle (por ejemplo for) que vaya desde 0 hasta nombre_array.length()-1. Para referenciar cada elemento del array dentro del bucle: nombre_array[contador]


Desde el método main se llamará a estos otros dos métodos:
sueldoMayor(): Llevará como parámetro un array de objetos Empleado y mostrará el nombre, apellido y salario del que más cobra.
mostrarTodos(): Llevará como parámetro un array de objetos Empleado, lo recorrerá imprimiendo los datos de todos ellos.
Posible solución => Enlace 3



Interfaces

  • Una interface es una clase en la que todos sus métodos son abstractos.
Todos los atributos que se definen en una interface tienen que ser final y static.
Al ser obligatorio estas dos condiciones, no es necesario indicarlo en la definición de los métodos y los atributos.
No puede tener constructores y como las clases abstractas, no puede ser instanciada.
Como norma (aunque no es obligatorio) los nombre de las interfaces deben acabar en 'able' (ser capaz de)
  • El objetivo de utilizar una interface puede ser:
  • Para implementar la herencia múltiple: Recordar que en Java sólo podemos heredar de una clase. Si quisiéramos 'heredar' de más clases (es decir, que tuviéramos varias clases que fueran superclases) podríamos hacer uso de las interfaces. Una clase puede implementar múltiples interfaces. La herencia en este caso está limitada ya que sólo vamos a poder heredar definiciones de métodos, no su implementación.
  • Utilizar un mecanismo (el de la interface) que nos va a permitir relacionar a clases diferentes. Es decir, todas las clases que implementen una interface van a tener que implementar los mismos métodos (los definidos en la interface) y por lo tanto, podríamos hacer uso del polimorfismo y llamar a los métodos utilizando como referencia a objetos que implementen la interface aunque pertenezcan a clases diferentes.
  • Disponer de una forma de definir constantes a un nivel global y que puedan ser utilizadas por clases diferentes. De esta forma evitamos definir las mismas constantes en diferentes clases (que no tienen una relación jerárquica)


  • Una interface puede tener modificadores 'public' o no llevar modificador.
  • Una intercace puede 'heredar' de otra interface.
  • Como comentamos antes, una interface permite separar el qué puede hacer con el cómo hacerlo (definimos los métodos, nombre, tipo de retorno y parámetros, pero no especificamos el código).



Ejercicios

  • Crea una interface Figura en la que se tenga definido una constante PI y un método getArea.
Crea las clases Cuadrado (con atributo lado), Triangulo (con los atributos base y altura) y Circulo (con atributo radio), implementa la interface, el método getArea() (devuelve el área de la figura) y el método getTipo() (devuelve un cadena informando del tipo de figura) en cada una de ellas.
Sobreescribe el método toString para que imprima la cadena: 'Figura: area', por ejemplo 'Circulo:33,23', utilizando los dos métodos anteriores.
Crea una clase Figuras en la que se defina un array de objetos de la clase Figura (de cuatro elementos) y crea un cuadrado, dos triángulos y un círculo dentro del constructor asignándolos a cada uno de los elementos del array.
Define un método recorrerFiguras() que recorra el array de figuras e imprímiles.
Define un método obtenerAreaMayor() que devuelva la figura del array que tenga el área más grande (esto no viene en la posible solución).
Crea una clase Principal que haga uso de la clase Figuras y que llame a los métodos recorrerFiguras() y obtenerAreaMayor().
Convierte la figura obtenida de la llamada al método obtenerAreaMayor() a su tipo (haz uso del método instanceof) e imprime sus atributos específicos.
Aclaraciones:
  • En este caso podemos hacer Figura una interface ya que no tiene atributos (sólo constantes).
  • Podríamos hacer Figura una clase abstracta y hacer derivar de ella a Cuadrado, Triangulo y Circulo, teniendo el mismo funcionamiento.
  • La ventaja de definirlo como interface, es que podemos aplicarla a cualquier clase en la que queramos que se implementen los métodos definidos en la interface.
Por ejemplo, podría aplicar dicha interface a una clase de nombre Cubo, formado por seis objetos de la clase Cuadrado. La implementación de dicha interface llevará consigo la implementación del método que calcula el área del Cubo. Y podría crear un objeto de dicha clase dentro del array anterior y llamar a los métodos recorrerFiguras() y obtenerAreaMayor().
Intenta hacerlo :)


Posible solución => Enlace 1.
En la solución se emplea la clase ArrayList que no hemos visto.
Existen ciertas diferencias entre los enunciados.
En la solución anterior, al final de todo, aparece otro ejercicio.



  • Disponemos de una empresa de gestión de parkings en la que nos encargamos de aparcar motos, coches y camiones. Define las clases necesarias para guardar una información básica de cada uno de ellos. Todos ellos tendrán un 'protocolo' determinado para aparcarlos en el parking. Define el método aparcar de la forma más adecuada, teniendo en cuenta que cada uno de ellos se aparca de forma diferente.
Implementa un ejemplo de uso.



  • Define una interface Comunicarse con el método hablar() y que sea utilizada por las clases Pajaro, Persona y Perro.
Además las clases Pajaro y Perro utilizan la interface Moverse con el método desplazar(), en la que se debe indicar como se desplazan.
Crea una clase Principal con un pájaro, una persona y un perro
Crea un método hacerHablar, al que se le pueda pasar como parámetro un objeto de alguna de las tres clases (¿ de que tipo será el parámetro definido ?) y se llame al método hablar().
Llama a dicho método pasando como dato cada uno de los objetos de las clases anteriores.
Crea un método hacerDesplazarse, al que se le pueda pasar como parámetro un objeto de alguna de las tres clases (¿ de que tipo será el parámetro definido ?) y se llame al método desplazar().
Llama a dicho método pasando como dato cada uno de los objetos de las clases anteriores.
Posible solución => Enlace 2.
Viene un ejemplo parecido.


Queremos dar un precio a cada uno de los objetos de cada una de las clases anteriores. Para ello es necesario definir un método obtenerValor.
¿ Dónde definirías dicho método ?
Este método devolverá un número con decimales. Para calcular el valor será necesario hacer uso de un atributo antigüedad, de tipo numérico sin decimales definido en cada una de las clases (Pajaro, Persona, Perro) y de una constante definida en cada una de las clases de nombre valorUnitario (podrá tener decimales y valores diferentes en cada clase).
¿ Dónde definirías dicha constante y el atributo antigüedad ?
El método obtenerValor devuelve un número que se obtiene multiplicando el valorUnitario por la antigüedad y por una de las constantes definidas en la interface ConstantesGestion indicada a continuación.
Crea una interface de nombre ConstantesGestion en la que estén definidas las constantes:
  • VALOR_ANO_2017 = 34.4
  • VALOR_ANO_2018 = 54.3
Implementa la interface en las clases Pajaro, Persona y Perro.
El método obtenerValor tendrá que comprobar el año actual y en función de su valor, utilizar la constante adecuada.
Crea un método imprimirValor que tenga como parámetro un objeto de una clase que permite enviar objetos de las clases Pajaro, Persona y Perro
Muestra el valor de cada objeto llamando al método imprimirValor con el formato: separador de miles, con coma y 3 dígitos decimales.




Recursividad

Torres de Hanoi

Solución Torres de Hanoi
 1 public class Hanoi {
 2     
 3     public static void hacerHanoi(int num, int o, int d, int aux){
 4         
 5         if (num==1){
 6             System.out.println("Mover disco " + num + " de la torre " + o + " a la torre " +d);
 7         }
 8         else
 9         {
10             hacerHanoi(num-1,o,aux,d);
11             System.out.println("Mover disco " + num + " de la torre " + o + " a la torre " + d);
12             hacerHanoi(num-1,aux,d,o);
13         }
14         
15         
16     }
17     public static void main(String[] arg){
18         hacerHanoi(3, 1, 3, 2);
19     }
20 }



Tipos de relaciones en POO

  • Aclaraciones ejercicio propuesto:
  • En el enunciado dice una cosa y el el modelo dice otra. Por lo tanto tenemos dos posibilidades:
  • El enunciado está mal y donde dice que lo Hora es una agregación debería decir Composición y a la inversa con el Display. De esta forma se correspondería con el modelo UML. Pero en este caso, el constructor de Reloj está mal. Al ser Hora una relación por 'composición' no se le puede pasar el objeto, sino que se tiene que crear dentro de la clase Reloj (o bien se le pasa sólo para que coja los datos de la hora).
  • El enunciado está bien y por tanto está mal el modelo UML, teniendo que intercambiar al agregación por la composición.
  • Por otro lado, llamar al método start() dice que imprimir los datos del reloj. Lo lógico es que se impriman de acuerdo al display. Por lo tanto es mejor cambiar el método setDisplayHora() por displayHora() y que muestre el valor del reloj en función del tipo de display.


UML













-- Ángel D. Fernández González -- (2017).