RSS

Archivos Mensuales: mayo 2011

Orientación a objetos (V) – Implementación de interfaces y tipos de retorno.

Implementando Interfaces

Decíamos con anterioridad que las interfaces son una especie de “contrato”, una declaración del conjunto de métodos que podemos utilizar si una clase implementa dicha interfaz.

Por tanto, una clase al implementar una interfaz debe definir cada uno de los métodos de ésta. De esta forma, cualquier persona que instancie un objeto de la clase implementadora tiene la seguridad de que puede llamar a los métodos de la interfaz.

Cuando Java encuentra una clase que dice implementar una interfaz la comprueba. Si falta algún método por definir nos dará error de compilación. Basta que tengamos la definición de los métodos aunque no tengamos ningún código para ellos.

Por ejemplo, esta clase no daría ningún error:

public interface Cultivable{
  public void plantar();
  public void regar();
  public int cosechar();
}

public class cultivoFresas implements Cultivable {
....
  public void plantar() {}
  public void regar(){}
  public int cosechar(){}
...
}

También es posible que nuestra clase implementadora sea abstracta. En este caso la clase puede elegir entre implementar algunos métodos, todos o ninguno. Y sería alguna clase concreta de ésta la que defina los restantes.

Por ejemplo:

 public abstract class Cultivos implements Cultivable {
   public void plantar() { ... } //Ejemplo de clase abstracta
   public void regar() { ... } // que define algunos métodos
 }

public class Maiz extends Cultivos {
   public void int cosechar() { ... } //Y el que falta en la concreta.
}

Las clases que implementan una interfaz deben seguir las misas normas que las clases que extienden una clase abstracta:

  • Deben definir todos los métodos de la interfaz.
  • Seguir todas las normas para las sobrescrituras (overrides).
  • Declarar sólo las excepciones comprobadas definidas en la interfaz o subclases de éstas.
  • Mantener la signatura de los métodos y el tipo devuelto (o un subtipo de éste).

Hay que recordar también que:

  • Una clase puede extender a sólo una clase.
  • Una clase puede implementar varias interfaces.
  • Una interfaz puede extender una o varias interfaces, pero nunca puede implementar.


Tipos de Retorno Legales

Vamos a hacer un pequeño resumen de qué podemos declarar como tipos de retorno:

  • Métodos sobrecargados: un método sobrecargado es un método totalmente diferente y nuevo.
    Si tenemos un método en una clase y lo sobrecargamos en una subclase no tiene que regirse por las normas de los métodos sobrescritos. Podemos declarar cualquier tipo de retorno.Hay que recordar que la sobrecarga no sólo se puede basar en un tipo diferente de retorno, debía diferenciarse del método original por el número o tipo de parámetros.
  • Métodos sobrescritos: un método sobrescrito consistía en dar una implementación distinta a un  método en una subclase. El método debía coincidir exactamente. A partir de Java 5, se permite también que el tipo de retorno en el método sobrescrito sea una subclase del tipo de retorno del método en la superclase.

Para devolver correctamente un valor para que se ajuste al tipo de retorno, hay que tener en cuenta:

  • Sólo podemos devolver null si el tipo de retorno es una variable de referencia (objeto y no tipo primitivo).
  • Si el tipo de retorno es un tipo primitivo cualquier valor que sea del tipo o que se pueda convertir
    implícitamente o explícitamente (usando un cast).
  • Nunca retornar un valor si el tipo de retorno es void.
  • Si el tipo de retorno es una variable de referencia (objeto y no tipo primitivo) podemos devolver un objeto de una subclase, ya que se hace el cast implícitamente.
  • Si el tipo de retorno es una interfaz podemos devolver cualquier objeto de una clase que implemente dicha interfaz.
 
1 comentario

Publicado por en 26 mayo, 2011 en Estudio, Tema 2

 

Etiquetas: , , , ,

Orientación a objetos (IV) – Conversiones de tipos

Operador instanceof

Como hemos visto, es posible utilizar objetos mediante referencias a sus superclases. Por lo tanto, a veces es necesario saber con qué objetos reales estamos tratando. Para ello tenemos el operador instanceof.

Por ejemplo, si tenemos la siguiente jerarquía de clases, utilizaríamos el operador instanceof para realizar unas acciones u otras dependiendo del objeto que realmente tengamos:

public class Contrato{...}

 

 public class Deposito extends Contrato{...}

 

 public class Prestamo extends Contrato{...}

 

public void finalizarContrato(Contrato c){
    if (c instanceof Deposito){
        //Devolver intereses
        //Poner indicador de contrato vivo a falso
    }
    else if (c instanceof Prestamo){
        //Poner importePendiente a 0
        //Poner indicador de contrato vivo a falso
    }
}

¿Como podemos realizar estas acciones si para nuestro compilador c es de tipo Contrato y no podríamos acceder a métodos de Deposito y de Prestamo? Haciendo una conversión de tipos.

Conversión de tipos

En los casos como el anterior, en el que ya hemos comprobado mediante el operador instanceof que el objeto es una subclase concreta, es posible acceder a toda la funcionalidad del objeto convirtiendo la referencia.

Para el ejemplo anterior sería:

public void finalizarContrato(Contrato c){
    if (c instanceof Deposito){
        Deposito d = (Deposito) c;
        //Devolver intereses
        d.devolverIntereses();
        //Poner indicador de contrato vivo a falso
        d.setIndContratoVivo(false);
    }
    else if (c instanceof Prestamo){
        Prestamo p = (Prestamo)c;
        //Poner importePendiente a 0
        p.setImportePendiente(0);
        //Poner indicador de contrato vivo a falso
        p.setIndContratoVivo(false);
    }
}

Si no realizamos la conversión, cualquier llamada a un método provocará error al no localizar tal método en la superclase.

Debemos tener en cuenta:

  • Las conversiones en dirección ascendente (de un objeto de una subclase a una referencia a una superclase o interfaz) siempre están permitidas, y de hecho, no precisan el operador de conversión. Se pueden hacer mediante una simple asignación.
    Prestamo pr = new Prestamo();
    Contrato c = pr;  //"Padre" = "Hijo" siempre posible y no necesario cast.
    

    Si Prestamo implementa Firmable con el método firmar, también es legal la asignación directa:

    Prestamo pr = new Prestamo();
    Firmable f = prestamo;
    f.firmar(); //podremos llamar a los métodos de la interfaz,
    //ya que están implementados en Prestamo.
    

    Y más aún, si tenemos la clase PrestamoHipotecario que hereda de Prestamo, que implementa a Firmable, también podemos hacer la asignación directa. Ya que PrestamoHipotecario hereda también la función Firmar por Prestamo implementar a Firmable.

    PrestamoHipotecario hip = new PrestamoHipotecario();
    Firmable f = hip;
    f.firmar();
    
  • En las conversiones de dirección descendente (de un objeto de una superclase a una referencia a una subclase), el compilador debe poder considerar que la conversión es posible, para esto, la clase destino de la conversión tiene que ser una subclase del tipo de referencia actual.
    Contrato c = new Contrato();
    Prestamo p = (Prestamo) c; // "Hijo" = "Padre" necesita cast.
    //El compilador no dará fallo pues parece posible la conversión.
    //Prestamo es una subclase de Contrato.
    
    Empleado e = new Empleado();
    Prestamo p = (Prestamo) e; //En este caso el compilador considera
    //que es imposible la conversión. Ni siquiera está en la jerarquía.
    //Provocará un error 'Inconvertible types'
    
  • Si el compilador permite la conversión, todavía no podemos cantar victoria, el tipo de objeto se comprueba durante el tiempo de ejecución. Si el objeto que se va a convertir no es realmente del tipo al que se hace la conversión, se producirá un error en tiempo de ejecución. Será una excepción ClassCastException.
    Contrato c1 = new Contrato();
    Contrato c2 = new Prestamo();
    
    Prestamo p1 = (Prestamo) c1; //Compila pero genera ClassCastException al ejecutar.
    Prestamo p2 = (Prestamo) c2; //Compila y se ejecuta correctamente.
    
 
1 comentario

Publicado por en 12 mayo, 2011 en Estudio, Tema 2

 

Etiquetas: , , , ,

Orientación a objetos (III) – Sobrecarga Constructores

Sobrecarga de constructores

Una clase puede definir varios constructores para permitir inicializar un objeto con más o menos atributos.
Podríamos tener un constructor sin argumentos para crear un objeto con unos atributos por defecto y varios constructores con argumentos dependiendo de a qué atributos queramos dar valor.

Por ejemplo, para la clase Tecnico:

 public class Tecnico{
 public static final double SALARIO_BASE = 600;
 private String nombre;
 private int departamento;
 private float sueldo;

public Tecnico(){   //Constructor sin parámetros.
 nombre = "";
 departamento = 0;
 sueldo = SALARIO_BASE;
 }

public Tecnico(String nombre, int departamento, float sueldo){   //Constructor con todos los parámetros.
 this.nombre = nombre;
 this.departamento = departamento;
 this.sueldo = sueldo;
 }

public Tecnico(String nombre, float sueldo){   //Constructor con tres parámetros, que utiliza this().
 this(nombre, 0, sueldo);
 }

}

El primer constructor sin parámetros inicializa todos los atributos con valores por defecto para nombre, departamento y sueldo.

El segundo constructor tiene parámetros para cada atributo de Tecnico.
El tercer constructor tiene dos parámetros, nombre y sueldo. La referencia this se utiliza como forma de envío de llamada a otro constructor (siempre dentro de la misma clase), en este caso el segundo constructor.

Es decir en esta llamada:

Tecnico t3 = new Tecnico (“Jose Luis”, 1300.50f);
Estaremos invocando al constructor con tres parámetros así:  Tecnico(“Jose Luis”, 0, 1300.50f)

Los constructores no se heredan

Aunque las subclases heredan todos los métodos y variables de sus superclases, no heredan sus constructores.

Las clases sólo pueden obtener un constructor de dos formas: debe escribirlo el programador o, si éste no lo escribe, debe usar el constructor predeterminado. Siempre se llama al constructor del nivel superior además de llamar al constructor subordinado.

Llamada a constructores de nivel superior

Puede llamar a un determinado constructor de una superclase como parte de la inicialización de una subclase utilizando la palabra clave super en la primera línea del constructor de la subclase. Es preciso suministrar los argumentos adecuados a super().

Cuando no hay ninguna llamada a super con argumentos, se llama implícitamente al constructor de la superclase que tenga cero argumentos. Si en la superclase no hubiera un constructor sin argumentos se produciría un error de compilación.

La llamada super() puede adoptar cualquier cantidad de argumentos adecuados para los distintos constructores disponibles en la superclase, pero debe ser la primera sentencia del constructor.

Recordar:

  • Si se utilizan, es preciso que super o this sean la primera línea del constructor.
  • Si un constructor no contiene ninguna llamada a super(…) ni a this(…), el compilador introduce una llamada al constructor de la superclase sin argumentos.
  • Otros constructores pueden llamar también a super(…) o this(…), con lo que iríamos encadenando llamadas a constructores. Al final el constructor de la clase de nivel superior se ejecutará antes que ningún otro constructor subordinado.
  • Un constructor nunca puede tener ambas llamadas super(…) y this(…).


 
1 comentario

Publicado por en 9 mayo, 2011 en Estudio, Tema 2

 

Etiquetas: , , , ,

Orientación a objetos (II)

Polimorfismo

Hemos hablado anteriormente del polimorfismo cuando hablabamos sobre la herencia. Decíamos que una variable es polimórfica porque puede hacer referencia a objetos de distintas formas.

Y vimos que Java permitía hacer referencia a un objeto con una variable del tipo de una superclase.
Empleado e = new Tecnico();

Usando la variable e podíamos acceder únicamente a las partes del objeto que son componentes de Empleado (la superclase). Esto se debía a que para el compilador la variable e es de tipo Empleado y no Tecnico.

Pero supongamos que ambas clases tienen un método toString().
Sabemos que si declaramos lo siguiente y llamamos a toString()

Empleado e1 = new Empleado();
Tecnico e2 = new Tecnico();

e1.toString();
e2.toString();

e1 llama al método toString() de la clase Empleado y e2 llama al método toString de la clase Tecnico.

¿Pero que ocurre si hacemos esta llamada?

Empleado e3 = new Tecnico();
e3.toString();

Se obtiene el comportamiento asociado al objeto al que hace referencia la variable durante el tiempo de ejecución. El comportamiento no está determinado por el tipo de la variable en el momento de la compilación. Este es un aspecto del polimorfismo que a menudo recibe el nombre de llamada a métodos virtuales.

e3.toString() llama al método del tipo real del objeto, Tecnico.

Colecciones hetereogéneas

Es posible crear colecciones de objetos que son de una misma clase. Estas colecciones se llaman homogéneas. Por ejemplo, cualquier array que construyamos de una clase es una colección homogénea:

Clientes[ ] clientesNuevos = new Clientes[100];
clientesNuevos[0] = new Cliente();
clientesNuevos[1] = new Cliente();
clientesNuevos[2] = new Cliente();

Sin embargo, Java permite crear colecciones compuestas por objetos de distintos tipos. Estas colecciones se llaman hetereogéneas.

Por ejemplo, una colección con objetos de las subclases de Empleado que vimos anteriormente:
Empleado[] empleados = new Empleado[3];
empleados[0] = new Tecnico();
empleados[1] = new Secretario();
empleados[2] = new Contable();

Es incluso posible hacer una colección con objetos de cualquier clase, ya que en Java todos los objetos heredan de Object. Podemos tener una colección como esta:

Object  [] objetos = new Object[5];
objetos[0] = new Empleado();
objetos[1] = new Batman();
objetos[2] = new Libro();
objetos[3] = new String(“Maria”);
objetos[4] = new Bicicleta();

Podríamos recorrer el array y llamar a un método que fuera común, por ejemplo toString().


Sobrecarga de métodos

En algunos casos podemos necesitar definir varios métodos que realicen la misma función pero con diferentes parámetros. Java permite reutilizar un mismo nombre de método para varios métodos si al llamarlos es posible distinguir unos y otros.

Reglas de los métodos sobrecargados:

  • Las listas de argumentos deben ser diferentes. Deben poder determinar sin ambigüedad qué método es el que se quiere llamar.
  • Los tipos de retorno pueden ser diferentes. El tipo de retorno puede ser diferente pero no es suficiente si ésta es la única diferencia.
  • El modificador de acceso puede cambiar.
  • Puede declarar nuevas o más amplias excepciones comprobadas.

Y a tener en cuenta:

  • Un método se puede sobrecargar en la misma clase o en una subclase.


Llamadas a métodos sobrecargados

Cuando llamamos a un método sobrecargado, existe más de un método con el mismo nombre. Decidir qué método es el que se debe llamar se hace en base a los argumentos.

Si tenemos un método sumar(int a, int b), y otro método sumar(double a, double b).
La llamada sumar(10, 7) llama a sumar con argumentos enteros y la llamada sumar(2.7, 2.3) llama a sumar con argumentos double.

¿Pero qué ocurre cuando en lugar de parámetros de tipos primitivos tenemos parámetros de tipos de referencia?

Supongamos estos métodos sobrecargados de la clase Proyecto.

public void añadirPersonal (Empleado e);
public void añadirPersonal (Tecnico t);

Con estas llamadas:
Empleado e = new Empleado();
Tecnico t = new Tecnico();
Empleado tec = new Tecnico();

e.añadirPersonal(e); //Como esperamos e es de tipo Empleado y llama a añadirPersonal(Empleado e)
e.añadirPersonal(t); //Como es de esperar, t es de tipo Tecnico y llama a añadirPersonal(Tecnico t)
e.añadirPersonal(tec); //En este caso tec es una referencia de Empleado pero es en realidad un objeto Tecnico.

En este ultimo caso se llama al método añadirPersonal(Empleado e).

Aunque el objeto en tiempo de ejecución sea un Tecnico, la decisión de a qué método llamar no se hace dinámicamente en tiempo de ejecución, asi que el tipo de referencia determina a qué metodo sobrecargado se llama.

 
1 comentario

Publicado por en 8 mayo, 2011 en Estudio, Tema 2

 

Etiquetas: , , , ,

CodingBat, practica tu Java

CodingBat es un sitio web creado por Nick Parlante que nos permite practicar Java y Python. No está relacionado directamente con la certificación SCJP pero puede ayudarnos a mejorar nuestra fluidez en Java.

Los ejercicios están divididos por niveles y dentro de cada nivel tenemos varios enunciados y una caja de texto en la que programar la solución. Una vez que codifiquemos la solución podemos hacer click en un botón Go que ejecuta el código y comprobaremos si funciona.

Tenemos la opción de crearnos una cuenta y así quedará guardado nuestro código, las preguntas que hemos acertado/fallado y nuestro progreso.

Los niveles para Java son http://codingbat.com/java

 
Deja un comentario

Publicado por en 4 mayo, 2011 en Enlaces, Recursos

 

Etiquetas: , ,

Orientación a objetos (I)

Encapsulación

La encapsulación es la forma de ocultar ciertos elementos de la implementación de una clase y, al mismo tiempo, proporcionar una interfaz pública para el software cliente.

Es una forma más de ocultar los datos, ya que la información de los atributos es un elemento significativo de la implementación de las clases. Forzamos a que el código llamante que usa nuestras clases utilicen métodos para acceder a los atributos en lugar de usar los atributos directamente.

¿Cómo hacemos esto?

  • Protegiendo los atributos de nuestras clases (usando el modificador private)
  • Suministrando métodos públicos para acceder a nuestros atributos privados.
  • Nombrando estos métodos como se nos indica en las reglas de convención para JavaBeans (getNombreAtributo, isNombreAtributo, setNombreAtributo)

Herencia

Toda clase en Java hereda de la clase Object. De modo que toda clase que creemos heredará los métodos de Object (equals, clone, notify…)

Los motivos más usuales para utilizar la herencia son:

  • Reutilizar código
  • Poder hacer uso del Polimorfismo

¿Cómo reutilizamos código en la herencia?
En la herencia, al poner los atributos y comportamiento común en la clase padre estamos evitando repetir este código en cada clase hija.
Cada subclase definirá solo los atributos y métodos que la hacen una clase más especializada y de forma automática heredará los atributos y métodos de la clase padre.

¿Cómo hacemos uso del polimorfismo en la herencia?
Un objeto sólo tiene una forma (aquella que se le asigna cuando se construye). Sin embargo, una variable es polimórfica porque puede hacer referencia a objetos de distintas formas.

Java, como la mayoría de los lenguajes de programación orientado a objetos, permite hacer referencia a un objeto con una variable que es uno de los tipos de una superclase. Por lo tanto sería posible:

Superclase variable = new Subclase();

Empleado e1 = new Tecnico();

Las variable e1, puede acceder únicamente a las partes del objeto que son componentes de Empleado; las partes específicas de Tecnico están ocultas.
Esto es porque, por lo que respecta al compilador, e1 es un Empleado, no un Gerente.

Usamos el polimorfismo en la herencia cuando en el código no nos importa qué subclase es, sino que lo que nos importa es que es una subclase de la clase padre que nos interesa.
Por ejemplo, supongamos un array con objetos de las distintas subclases de Empleado.
Podríamos recorrer este array volcando cada objeto en una variable de la superclase y acceder a un método común, getNombreEmpleado().

Empleado[] empleados = new Empleado[4];
empleados[0]=new Tecnico();
empleados[1]=new Secretario();
empleados[2]=new Contable();
empleados[3]=new Tecnico();

for (int i=0; i<4; i++){
Empleado e = empleados[i];
System.out.println(e.getNombre());
System.out.println(e.getAntiguedad());
System.out.println(e.getSueldo());

}

Read the rest of this entry »

 
1 comentario

Publicado por en 4 mayo, 2011 en Estudio, Tema 2

 

Etiquetas: , , ,

 
A %d blogueros les gusta esto: