Clases abstractas, interfaces y herencia múltiple

Clases abstractas

Una clase abstracta es aquella que no implementa alguno de sus métodos. Veamos un ejemplo:

abstract class Figura {
    protected double x;
    protected double y;
 
    public Figura(double x, double y){
       this.x = x;
       this.y = y;
    }  
    abstract double perimetro();
}
abstract class Figura(val x: Double,
                      val y: Double) {  // Propiedades no abstractas
   abstract fun perimetro(): Double     // función abstractos
}

Observa como el método perimetro() de esta clase no se implementa por lo que hay que utilizar la palabra reservada abstract. Este método se dice que es abstracto.  Toda clase que contenga algún método abstracto también es abstracta, e igualmente, hay que etiquetarla con la palabra reservada abstract.
No se puede instanciar un objeto de una clase abstracta, dado que no se ha definido todo su comportamiento.  
Una clase abstracta solo se utiliza para definir subclases. Veamos un ejemplo:

class Circulo extends Figura {

    private double radio;

    public Circulo(double x, double y, double radio){
        super(x,y);
        this.radio = radio;
    }

    public double perimetro(){
        return 2*Matth.PI*radio;
    }
}
class Circulo(x: Double,
              y: Double,
              val radio: Double): Figura(x, y) {

    override fun perimetro(){
        return 2*kotlin.math.PI*radio;
    }
}

Se puede crear una referencia a una clase abstracta:

Figura miFigura;
var miFigura: Figura

Pero no se puede crea un objeto de una clase abstracta:

miFigura = new Figura(100,100);
miFigura = Figura(100.0, 100.0)

Podemos asignar cualquier objeto descendiente a una referencia de una clase abstracta, siempre que el descendiente no sea abstracto:

miFigura = new Circulo(100,100,50);
val miFigura: Figura = Circulo(100.0, 100.0, 50.0)

Recuerda que este tipo de operaciones se conoce como polimorfismo.

Interfaces

La interfaz es una clase abstracta pura, es decir, una clase donde se indican los métodos, pero no se implementa ninguno. Permite al programador establecer una estructura que ha de seguir toda clase que implemente esta interfaz. En la interfaz solo se indica los nombres de los métodos, sus parámetros y tipos que retornan, pero no el código de cada método. Una interfaz también puede contener constantes, pero nunca pude campos de otro tipo.  En Java estos campos serán de tipo static y final. En Kotlin estos campos se declaran dentro de un companion object. Veamos un ejemplo:

public interface Dibujable {
    int AZUL = 0x0000FF;
    void dibuja(int color);
    void borra();
    boolean estaDibujado();
}
interface Dibujable {
    fun dibuja(color: Int)
    fun borra()
    fun estaDibujado(): Boolean

    companion object {
        val AZUL = 0x0000FF
    }
}

Aunque en Java no se haya indicado, AZUL es declarado de tipo static y final. Es decir, una interfaz puede definir constantes, pero nunca encapsular datos.

Una clase puede implementar una interface. En este caso está obligada a definir todos los métodos indicados. Veamos un ejemplo:

public class Dibujo implements Dibujable { 
   …
   void dibuja(int color){
      …
   }
   void borra(){
      …
   }
   boolean estaDibujado(){
      …
   }
}
class Dibujo: Dibujable { 
   …
   fun dibuja(color: Int){
      …
   }
   fun borra(){
      …
   }
   fun estaDibujado(): Boolean{
      …
   }
}

La clase Dibujo implementa la interface, por lo que ha de declarar todos los métodos y escribir el código correspondiente.

NOTA: En Kotlin y Java 8 una interface puede contener métodos estáticos.

Herencia múltiple

Algunos lenguajes orientados a objeto, como C++, Perl o Python, soportan la herencia múltiple. Es decir, pueden extender simultáneamente más de una clase. Esta característica presenta cierta ambigüedad a la hora de acceder a los atributos de los padres, lo que hace que aumente la complejidad del lenguaje.

Otros lenguajes, como Java, C#, Delphi, Objective-C o Kotlin, no soporta herencia múltiple. Por lo que solo pueden extender de una clase; lo que simplifica en gran medida el lenguaje. Lo que sí que van a permitir estos lenguajes es que una clase además de extender de su padre puedan implementar varios interfaces.

La implementación de una interface resulta mucho más sencilla dado que no incorpora variables, solo obliga a la clase a definir una lista de métodos. Veamos un ejemplo:

class Circulo extends Figura implements Dibujable {
   …
}
class Circulo: Figura, Dibujable {
   …
}

En este caso Circulo extiende Figura; por lo que va a incorporar las variables x e y además del método abstracto perimetro(). Además, implementa Dibujable por lo que ha de definir los tres métodos de esta interfaz.

De esta forma la clase Circulo tiene un doble comportamiento el heredado de Figura, además nos aseguramos que puede ser dibujado al implementar Dibujable:

Ejercicio paso a paso: La interfaz RepositorioLugares

En este ejercicio vamos a crear una interfaz que nos permita almacenar una lista de objetos Lugar. A lo largo del curso esta interfaz será implementada por dos clases. En esta unidad usaremos una lista almacenada en memoria y en la última unidad una base de datos. Usar esta interface nos va a permitir desacoplar la forma en la que almacenamos los datos del resto de la aplicación. Por ejemplo, si en un futuro queremos que los datos se almacenen en la nube, solo será necesario cambiar la implementación de esta interface, dejando idéntica el resto de la aplicación.

1.   Dentro del explorador del proyecto mislugares > java > com.example.mislugares, pulsa con el botón derecho y selecciona New > Java Class o New > Java Class.

2.   Introduce en la  nueva ventana en Name: RepositorioLugares, y en Kind: Interface.

3.    Reemplaza el código por el siguiente (dejando la línea del package):

public interface RepositorioLugares {
   Lugar elemento(int id); //Devuelve el elemento dado su id 
   void añade(Lugar lugar); //Añade el elemento indicado
   int nuevo(); //Añade un elemento en blanco y devuelve su id 
   void borrar(int id); //Elimina el elemento con el id indicado
   int tamaño(); //Devuelve el número de elementos
   void actualiza(int id, Lugar lugar); //Reemplaza un elemento
}
interface RepositorioLugares {
   fun elemento(id: Int): Lugar  //Devuelve el elemento dado su id
   fun añade(lugar: Lugar)      //Añade el elemento indicado
   fun nuevo(): Int       //Añade un elemento en blanco y devuelve su id
   fun borrar(id: Int)    //Elimina el elemento con el id indicado
   fun tamaño(): Int     //Devuelve el número de elementos
   fun actualiza(id: Int, lugar: Lugar)  //Reemplaza un elemento

   fun añadeEjemplos() {
      añade(Lugar("Escuela Politécnica Superior de Gandía",
         "C/ Paranimf, 1 46730 Gandia (SPAIN)", GeoPunto(-0.166093,
         38.995656), TipoLugar.EDUCACION, "", 962849300,
         "http://www.epsg.upv.es",
         "Uno de los mejores lugares para formarse.", valoracion = 3f))
      añade(Lugar("Al de siempre",
         "P.Industrial Junto Molí Nou - 46722, Benifla (Valencia)",
         GeoPunto(-0.190642, 38.925857), TipoLugar.BAR, "", 636472405, "",
         "No te pierdas el arroz en calabaza.", valoracion = 3f))
      añade(Lugar("androidcurso.com","ciberespacio", GeoPunto(0.0, 0.0),
         TipoLugar.EDUCACION, "", 962849300, "http://androidcurso.com",
         "Amplia tus conocimientos sobre Android.", valoracion = 5f))
      añade(Lugar("Barranco del Infierno",
         "Vía Verde del río Serpis. Villalonga (Valencia)",
         GeoPunto(-0.295058, 38.867180), TipoLugar.NATURALEZA, "", 0,
         "http://sosegaos.blogspot.com.es/2009/02/lorcha-villalonga-via-"
         +"verde-del-rio.html","Espectacular ruta para bici o andar",
         valoracion = 4f))
      añade(Lugar("La Vital",
         "Avda. de La Vital, 0 46701 Gandía (Valencia)", GeoPunto(
         -0.1720092, 38.9705949), TipoLugar.COMPRAS, "", 962881070,
         "http://www.lavital.es/", "El típico centro comercial",
         valoracion = 2f))
   }
}

Una clase que implemente esta interface va a almacenar una lista de objetos de tipo Lugar. Mediante los métodos indicados vamos a poder acceder y modificar esta lista. Una interfaz también puede tener funciones estáticas, como añadeEjemplos(). En Java solo está permitido con API mínima >24, por lo que lo añadiremos esta función en una clase no abstracta.

4. Esta interface será usada en uno de los siguientes apartados.