domingo, 12 de febrero de 2012

Herencia en Java – Preparación para el certificado


(Este es el guión del podcast que publiqué anteriormente. )
Para la preparación al examen del certificado de Programador Java Associate, este es un apartado muy importante. Este resumen se divide en:
  • Definición
  • Puntos clave
  • Test


    Definición 
En Java, la herencia se encuentra en todo. Se podría decir que en Java casi nada se puede hacer sin hacer uso de la herencia.
Las 2 razones más importantes para el uso de la herencia son:
  • Reutilización de código
  • Uso del polimorfismo

Empecemos con la reutilización. Un enfoque de diseño común es crear una versión de una clase bastante genérica y después crear subclases muy especializadas que hereden de esta, por ejemplo:
public class Animal
{
    public void muevete()
    {
        System.out.println("Me estoy moviendo");
    }
}
 
 
public class Perro extends Animal
{
    public void ladra()
    {
        System.out.println("Estoy ladrando");
    }
}
 
public class PruebaAnimal
{
    public static void main(String... args)
    {
        Perro perro = new Perro();
        perro.muevete();
        perro.ladra();
    }
}
La salida del código sería:
Me estoy moviendo Estoy ladrando
Como podemos ver, la clase “Perro” está heredando el método “muevete” de la clase “Animal” y que también tiene su propio método agregado, en este caso es el método “ladra”. Aquí se está haciendo uso de la reusabilidad al utilizar un método genérico de una clase padre que en este caso es el método “muevete”, con esto podemos crear diferentes tipos de animales y todos van a poder utilizar el método “muevete” sin necesidad de implementarlo ellos mismos.
El segundo objetivo de la herencia es poder acceder a las clases polimórficamente. Imaginemos este escenario: digamos que tenemos una clase llamada “Entrenador” que quiere recorrer todos los diferentes tipos de animal e invocar al método “muévete” en cada uno de ellos, al momento de escribir la clase “Entrenador” no sabemos cuántas clases de “Animal” podría haber y seguro no vamos a querer cambiar el código sólo porque a alguien se le ocurrió crear un nuevo tipo de Animal.
Lo bueno del polimorfismo es que podemos tratar cualquier tipo de “Animal” como un “Animal”, en otras palabras podemos decir lo siguiente: No me importa qué tipo de Animal se pueda crear, siempre y cuando extienda (herede) de Animal todos van a poder moverse (método “muevete”).
Ahora miremos esto con un ejemplo:
public class Animal
{
    public void muevete()
    {
        System.out.println("Me estoy moviendo");
    }
}
 
public class Perro extends Animal
{
    public void ladra()
    {
        System.out.println("Estoy ladrando");
    }
}
 
public class Gato extends Animal
{
    public void ronronea()
    {
        System.out.println("Estoy ronroneando");
    }
}
Ahora imaginemos una clase llamada “Entrenador” que tiene un método que tiene como argumento un “Animal”, esto significa que puede tomar cualquier tipo de “Animal”, cualquier tipo de animal puede ser pasado a un método con un argumento del tipo “Animal”, ejemplo:
public class Entrenador
{
    public static void main(String... args)
    {
        Gato gato = new Gato();
        Perro perro = new Perro();
        mueveteAnimal(gato);
        mueveteAnimal(perro);
    }
 
    public static void mueveteAnimal(Animal animal)
    {
        animal.muevete();
    } 
}
La salida del código sería:
Me estoy moviendo
Me estoy moviendo
El método “mueveteAnimal” está declarando un “Animal” como argumento pero se le puede pasar cualquier sub-clase o sub-tipo de esta clase “Animal”, este método podría invocar a cualquier método dentro de la clase “Animal”. Lo que si debemos de tener en cuenta es que sólo podemos llamar a los métodos declarados por “Animal”, los métodos declarados dentro de las subclases de “Animal” son dependiente del tipo declarado, esto significa que no podríamos llamar al método “ladra” incluso si el “Animal” que se está pasando es del tipo “Perro”.
Relación IS-A, HAS-A
IS-A
En Orientada a objetos el concepto de “IS-A” (es-un) está basado en la herencia de una clase (“extends”) o en la implementación de una interface (“implements”). IS-A es una forma de decir “Esta cosa es del tipo de esta cosa” (o estos dos tipos pueden ser equivalentes), por ejemplo un “Perro” es del tipo “Animal”, en OO nosotros podemos decir: “Perro IS-A Animal”, “Lechuga IS-A Vegetal”, en java podemos expresar esta relación IS-A por medio de las palabras reservadas “extends” (para la herencia de clases) y de “implements” (para la implementación de interfaces), veamos un ejemplo:
public class Coche
{
    //cualquier código aquí
}
 
public class Toyota extends Coche
{
    /*Toyota está heredando de Coche, no olvidemos que Toyota 
    hereda los miembros de Coche incluido métodos y variables*/
} 
” Coche ” también es un vehículo así que se podría implementar un árbol de herencia de la siguiente manera:
 
public class Vehiculo{...}
public class Coche extends Vehiculo{...}
public class Toyota extends Coche{...}
En términos de OO podríamos decir lo siguiente:
  • Vehículo es la súper clase de Coche
  • Coche es la subclase de Vehículo
  • Coche es la súper clase de Toyota
  • Toyota es la subclase de Coche
  • Toyota hereda de Coche y de Vehiculo
  • Toyota deriva de Coche
  • Coche deriva de Vehículo
  • Toyota es subtipo de Coche y Vehiculo
Retornando a la relación IS-A, lo siguiente es correcto:
Toyota extends Coche significa Toyota IS-A Coche
Coche extends Vehiculo significa Coche IS-A Vehiculo
Además, una clase puede ser hijo, nieto, bisnieto, etc. de otra clase, una clase puede extender o heredar de otra directa o indirectamente ya que en el árbol de herencia pueden haber clases intermedias.
HAS-A
La relación HAS-A esta basada en el uso en lugar de la herencia, por ejemplo “A HAS-A B” si la clase “A” tiene una referencia a una instancia de la clase “B”, por ejemplo:
public class Animal{ }
 
public class Gato extends Animal
{
    CajaDeArena miCajaDeArena;
}
Aquí la clase “Gato” tiene una referencia a una variable de instancia del tipo “CajaDeArena”, entonces podemos decir “Gato HAS-A CajaDeArena”, en otra palabras, “Gato” tiene una referencia a una “CajaDeArena”, la clase “Gato” puede tener un método llamado “llenarCaja(Arena miArena)”, de esta manera los usuarios de la clase “Gato” no podrán saber nunca que cuando se invoca al método “llenarCaja” este método delega toda la responsabilidad a la clase “CajaDeArena” llamando a su método “llenarCaja”, veamos esto con un ejemplo:
public class Gato extends Animal
{
    private CajaDeArena miCajaDeArena;
    
    public void llenarCaja(Arena miArena)
    {
        miCajaDeArena.llenarCaja(miArena); /*delegando comportamiento al objeto CajaDeArena */
    }
}
 
public class CajaDeArena
{
    public void llenarCaja(Arena miArena)
    {
        System.out.println("Llenando la caja de arena");
    }
}
En OO muchas veces no queremos que la gente se preocupe por cual clase u objeto está haciendo realmente el trabajo, los usuarios de la clase “Gato” han llamado al método “llenarCaja” pero estos no saben si la misma clase hace el trabajo o no, sin embargo a ellos les parece que la propia clase “Gato” lo hace, no tienen ni idea de que existe algo como una clase llamada “CajaDeArena” que es quien realmente hace el trabajo.
Resumiendo esto y añadiendo algunas cosas más, voy a citar los puntos clave que hay que saber sobre la herencia, sobre todo teniendo en cuenta las preguntas referentes al certificado de Java:
Puntos claves:
  • La herencia se usa para ubicar código común en una clase base.
  • La herencia hace que se pueda tener un código más modularizado y facil de mantener.
  • Para notificar que una clase hereda de otra, se usa la palabra clave extends.
  • Cuando una clase hereda de otra, ésta toma todos sus métodos visibles y variables instanciadas.
  • A la clase que está siendo heredada se le llama clase base o superclase.
  • La clase que toma la funcionalidad se le llama subclase.
  • Un método de una superclase puede ser reemplazado si la subclase tiene un método con idénticas características (mismos parámetros y nombre).
  • La palabra clave super se puede usar para acceder a ese método reemplazado.
  • Una clase solo puede heredar de otra, como un hijo solo puede heredar de un único padre.
  • Una clase concreta (concrete class) es aquella que puede ser instanciada, y todos sus métodos deben ser implementados.
  • Una clase abstracta es aquella que no puede ser instanciada. Esta debe ser heredera de una superclase y puede o no puede contener métodos abstractos.
  • Cuando una clase abstracta hereda de una clase, todos los métodos abstractos deben estar implementados.
  • Una interface es una clase abstracta pura, es decir, una clase donde todos los métodos son abstractos (no se implementa ninguno). Permite al diseñador de clases establecer la forma de una clase.
  • Para indicar que una clase implementa los métodos de una interface se utiliza la palabra clave implements.
  • Una clase puede implementar múltiples interfaces utilizando una lista separada por comas.
  • Una clase que implementa una interfaz debe implementar todos los métodos contenidos en esa interface.
  • El concepto de Encapsulación es el almacenamiento en un mismo lugar de datos y código relacionado.
  • Los modificadores de acceso se usan para restringir el acceso a métodos y variables.
  • El modificador de acceso public permite que cualquier clase pueda acceder a métodos y variables públicas.
  • El modificador de acceso private permite que solo los métodos de la misma clase puedan acceder a otros métodos y variables privadas.
  • El concepto de Information Hiding(Ocultación de información)n es el uso de modificadores de acceso restringidos para ocultar los detalles de implementación de una clase.
  • Cuando se crean métodos o intancias de variables, se debe utilizar el modificador de acceso más restrictivo posible.
  • Un getter se usa para acceder al valor de una variable privada.
  • Un setter se usa para establecer el valor de una variable privada.
  • Ambos getter y setter deben seguir la misma nomenclatura de nombres de JavaBeans, deben empezar por ‘get’,’set’ o ‘is’ seguidos del nombre de la variable, empezando por letra mayúscula.
Para terminar, voy a citar unas preguntas tipo test que te podrías encontrar en el examen:
Test de certificado:
  1. ¿Cuál de estos contiene métodos y variables y puede ser instanciado?
    1. a.       Clase concreta
    2. Clase abstracta
    3. Clase Java
    4. Interface
  2. ¿Cuál es usado para definir una interfaz pública?
    1. Clase concreta
    2. Clase abstracta
    3. Clase Java
    4. d.      Interface
  3. 3.       ¿Cuál puede contener métodos sin implementar y variables y no puede ser instanciado?
    1. Clase concreta
    2. b.      Clase abstracta
    3. Clase Java
    4. Interface
  4. ¿Qué es lo que te permite la herencia? (Selecciona las que sean correctas)
    1. Tiempos de ejecución más rápido ya que los métodos pueden heredar tiempos del procesador de la superclase
    2. b.      Permite a los desarrolladores ubicar código general en una clase que otras clases más específicas pueden usar a través de la herencia
    3. c.       Promueve la reutilización de código.
    4. Es un proceso automático que permite pasar código de antiguas versiones de Java a la última versión.
  5. ¿Cómo se define a la clase que está siendo heredada? (Selecciona las que sean correctas)
    1. Subclase
    2. b.      Superclase
    3. c.       Clase base
    4. Superduperclase
  6. ¿Cómo se define  Information Hiding?
    1. Oculta muchos detalles sobre tu clase para que otros no puedan robártelos.
    2. Consiste en ocultar detalles de implementación y protección de variables ante el mal uso posterior o de otros desarrolladores dentro del proyecto.
    3. Se usa para ocultar la interconexión de tu clase y así las clases externas puedan usar la interfaz pública
  7. ¿Cuál de estos modificadores de acceso se usa para instanciar variables y métodos  que solo están disponible dentro de la misma clase donde se definieron?
    1. Public
    2. b.      Private
    3. Protected
    4. Package-private (por defecto)
  8. ¿Cuál de estos modificadores de acceso se usa para crear una interfaz a otros objetos, independientemente de su paquete o de sus clases base?
    1. a.       Public
    2. Private
    3. Protected
    4. Package-private(por defecto)
  9. ¿Cuál es el código correcto para X si hereda de la clase Z?
    1. Public class X inhertis Z{…}
    2. b.      Public class X extends Z {…}
    3. Public class X implements Z {…}
  10. ¿De cuántas clases puede heredar una clase?
    1. 0
    2. b.      1
    3. 2
    4. Todas las que se necesiten
  11. ¿Cuántas interfaces puede implementar una clase?
    1. 0
    2. 1
    3. 2
    4. d.      Todas las que se necesiten

3 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Muy bueno fran. pero revísalo que tienes algunos fallos.

    Por ejemplo una clase se llama "Coche" y luego la usas como "Carro"

    Otra cosilla que debes aclarar es que en el encapsulamiento, el uso de isAtributo, SOLO se usa para los atributos boolean y SOLO para obtener el valor, similar a un get.

    private boolean hoyLunes;

    public void setHoyLunes(){...}
    public boolean getHoyLunes(){...}
    ó
    public boolean isHoyLunes(){...}

    Salu2!!!

    ResponderEliminar
  3. Gracias CarmaZone, ya lo he arreglado :)

    ResponderEliminar