Interfaces en Java. Ejemplos de Interfaces.

Una interface es un archivo .java formado por un conjunto de métodos abstractos. Además puede contener un conjunto de constantes públicas.
Podemos considerar una interface como una clase abstracta pura: todos sus métodos son abstractos y si tiene atributos son todos constantes.
En java una interface o interfaz se crea de forma similar a como se crea una clase utilizando la palabra clave interface en lugar de class.
[public] interface NombreInterface{
            declaraciones de métodos abstractos;
            [atributos constantes;]
}
Lo que aparece entre corchetes es opcional.
La interface puede definirse public o sin modificador de acceso, y tiene el mismo significado que para las clases.
Si tiene el modificador public el archivo .java que la contiene debe tener el mismo nombre que la interfaz. Igual que las clases, al compilar el archivo .java de la interface se genera un archivo .class
Todos los métodos de una interface son públicos y abstractos aunque no se indique explícitamente. Por lo tanto, no es necesario escribir en cada método public abstract.
Una interface puede contener atributos constantes. Las constates declaradas son públicas y estáticas aunque no se indique explícitamente. Por lo tanto, se pueden omitir los modificadores public static final cuando se declara el atributo. Las constantes se deben inicializar en la misma instrucción de declaración.
Diferencias entre una clase abstracta y una interface:
- En la interface todo método es abstracto y público sin necesidad de declararlo. Una clase abstracta puede tener métodos abstractos y no abstractos.
- Los atributos declarados en una interface son public static final. Una clase abstracta puede contener también atributos de otro tipo (de instancia, no constantes, private, etc).
Las interfaces juegan un papel fundamental en la creación de aplicaciones Java ya que permiten interactuar a objetos no relacionados entre sí. Utilizando interfaces es posible que clases no relacionadas, situadas en distintas jerarquías de clases sin relaciones de herencia, tengan comportamientos comunes.
Las interfaces definen un protocolo de comportamiento y proporcionan un formato común para implementarlo en las clases.
Los nombres de las interfaces suelen acabar en able aunque no es necesario: configurable, arrancable, dibujable, etc.
Ejemplo de interface en Java: interface Relacionable
//Interface que define relaciones de orden entre objetos.
public interface Relacionable {
    boolean esMayorQue(Object a);
    boolean esMenorQue(Object a);
    boolean esIgualQue(Object a);
}
IMPLEMENTAR UNA INTERFACE
Para indicar que una clase implementa los métodos de una interface se utiliza la palabra clave implements.
Las clases que implementan una interfaz deben implementar todos los métodos abstractos contenidos en la interface. De lo contrario serán clases abstractas y deberán declararse como tal.
Ejemplo de clase Java que implementa una interface: Una clase Linea que implementa la interfaz Relacionable. En ese caso se dice que la clase Linea es Relacionable
public class Linea implements Relacionable {

    private double x1;
    private double y1;
    private double x2;   
    private double y2;

    public Linea(double x1, double y1, double x2, double y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;      
        this.y2 = y2;
    }

    public double longitud() {
        double l = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
        return l;
    }

   
    ///////////////////////////////////////////////////////////////////////////////
    //
    //        Implementación de los tres métodos de la interface              //
    //
    ///////////////////////////////////////////////////////////////////////////////
   
    @Override
    public boolean esMayorQue(Object a) {
        if (a == null) {
            return false;
        }
        if (!(a instanceof Linea)) {
            return false;
        }
        double lon = ((Linea) a).longitud();
        return longitud() > lon;
    }

    @Override
    public boolean esMenorQue(Object a) {
        if (a == null) {
            return false;
        }
        if (!(a instanceof Linea)) {
            return false;
        }
        double lon = ((Linea) a).longitud();
        return longitud() < lon;
    }

    @Override
    public boolean esIgualQue(Object a) {
        if (a == null) {
            return false;
        }
        if (!(a instanceof Linea)) {
            return false;
        }
        double lon = ((Linea) a).longitud();
        return longitud() == lon;
       
    }

    // Sobreescritura del método toString heredeado de Object

    @Override
    public String toString() {
        return "\nCoordenadas inicio linea: " + x1 + " , " + y1
                  + "\nCoordenadas final linea: "+  x2 + " , " + y2
                  + "\nLongitud: " + longitud();
    }  
}

La interfaz la puede implementar cualquier clase por ejemplo una clase Fraccion:
public class Fraccion implements Relacionable{
    private int num;
    private int den;
    public Fraccion() {
        this.num = 0;
        this.den = 1;
    }
    public Fraccion(int num, int den) {
        this.num = num;
        this.den = den;
        simplificar();
    }
    public Fraccion(int num) {
        this.num = num;
        this.den = 1;
    }
    public void setDen(int den) {
        this.den = den;
        this.simplificar();
    }
    public void setNum(int num) {
        this.num = num;
        this.simplificar();
    }  
    public int getDen() {
        return den;
    }
    public int getNum() {
        return num;
    }
    //sumar fracciones
    public Fraccion sumar(Fraccion f) {
        Fraccion aux = new Fraccion();
        aux.num = num * f.den + den * f.num;
        aux.den = den * f.den;
        aux.simplificar();  //se simplifica antes de devolverla
        return aux;
    }
    //restar fracciones
    public Fraccion restar(Fraccion f) {
        Fraccion aux = new Fraccion();
        aux.num = num * f.den - den * f.num;
        aux.den = den * f.den;
        aux.simplificar();  //se simplifica antes de devolverla
        return aux;
    }
    //multiplicar fracciones
    public Fraccion multiplicar(Fraccion f) {
        Fraccion aux = new Fraccion();
        aux.num = num * f.num;
        aux.den = den * f.den;
        aux.simplificar();  //se simplifica antes de devolverla
        return aux;
    }
    //dividir fracciones
    public Fraccion dividir(Fraccion f) {
        Fraccion aux = new Fraccion();
        aux.num = num * f.den;
        aux.den = den * f.num;
        aux.simplificar();  //se simplifica antes de devolverla
        return aux;
    }  
    //Cálculo del máximo común divisor por el algoritmo de Euclides
    private int mcd() {
        int u = Math.abs(num); //valor absoluto del numerador
        int v = Math.abs(den); //valor absoluto del denominador
        if (v == 0) {
            return u;
        }
        int r;
        while (v != 0) {
            r = u % v;
            u = v;
            v = r;
        }
        return u;
    }
    private void simplificar() {
        int n = mcd(); //se calcula el mcd de la fracción
        num = num / n;
        den = den / n;
    }

    // Sobreescritura del método toString heredeado de Object
    @Override
    public String toString() {
        simplificar();
        return num + "/" + den;
    }

    ///////////////////////////////////////////////////////////////////////////////
    //
    //        Implementación de los tres métodos de la interface              //
    //
    ///////////////////////////////////////////////////////////////////////////////

    @Override
    public boolean esMayorQue(Object a) {
        if (a == null) {
            return false;
        }
        if (!(a instanceof Fraccion)) {
            return false;
        }
        Fraccion f = (Fraccion) a;
        this.simplificar();
        f.simplificar();
        if ((num/(double)den) <= (f.num/(double)f.den)) {
            return false;
        }
        return true;
    }

    @Override
    public boolean esMenorQue(Object a) {
        if (a == null) {
            return false;
        }
        if (!(a instanceof Fraccion)) {
            return false;
        }
        Fraccion f = (Fraccion) a;
        this.simplificar();
        f.simplificar();
        if ((num/(double)den) >= (f.num/(double)f.den)) {
            return false;
        }
        return true;
    }

    @Override
    public boolean esIgualQue(Object a) {
        if (a == null) {
            return false;
        }
        if (!(a instanceof Fraccion)) {
            return false;
        }
        Fraccion f = (Fraccion) a;
        this.simplificar();
        f.simplificar();
        if (num != f.num) {
            return false;
        }
        if (den != f.den) {
            return false;
        }
        return true;
    }
}
Ejemplo de interface Java que solo contiene constantes:
//Interfaz que contiene días relevantes en una aplicación de banca.
public interface IDiasOperaciones {
    int DIA_PAGO_INTERESES = 5;
    int DIA_COBRO_HIPOTECA = 30;
    int DIA_COBRO_TARJETA = 28;
}
Una clase que implemente esta interface puede usar las constantes como si fuesen propias. Por ejemplo:
//Uso de las constantes dentro de una clase que implementa la interface IDIasOperaciones
public class Banco implements IDiasOperaciones{
    …………..
    public void mostrarInformacionIntereses(){
        System.out.println("El día " + DIA_PAGO_INTERESES
                                     + " de cada mes se realiza el pago de intereses");
    }
    …………..
}
Una clase que no implemente la interfaz puede usar las constantes escribiendo antes el nombre de la interfaz. Por ejemplo:
//Uso de las constantes dentro de una clase que NO implementa la interface IDIasOperaciones
public static void main(String[] args) {   
        …………………  
        System.out.println("Los intereses se pagan el día "
                                     + IDiasOperaciones.DIA_PAGO_INTERESES);
        System.out.println("La hipoteca se paga el día "
                                    + IDiasOperaciones.DIA_COBRO_HIPOTECA);
        …………………
    }
Si una clase implementa una interfaz todas sus clases derivadas heredan los métodos implementados en la clase base y las constantes definidas en la interfaz.
En UML una clase que implementa una interface se representa mediante una flecha con línea discontinua apuntando a la interface:

O también se puede representar de forma abreviada:

Una clase puede implementar más de una interface. En este caso los nombres de las interfaces se escriben a continuación de implements y separadas por comas:

public class UnaClase implements NombreInterface1, NombreInterface2, .....

El lenguaje Java no permite herencia múltiple, pero las interfaces proporcionan una alternativa para implementar algo parecido a la herencia múltiple de otros lenguajes. En java una clase solo puede tener una clase base pero puede implementar múltiples interfaces.

public class ClaseDerivada extends ClaseBase implements Interface1, Interface2, Interface3, .....



Cuando hay una implementación múltiple, es posible que dos interfaces tengan atributos o métodos con el mismo nombre. La clase que implementa las interfaces recibirá métodos o atributos con el mismo nombre. A esto se le llama colisión.
Java establece una serie de reglas para solucionar las colisiones:
-      Para las colisiones de nombres de atributos se obliga a especificar el nombre de la interfaz base pertenecen al utilizarlos. (NombreInterfaz.atributo)
-           Para las colisiones de los nombres de métodos:
-         Si tiene el mismo nombre y diferentes parámetros se produce sobrecarga de métodos, permitiéndose que existan varias maneras de llamar al método. 
-         Si sólo cambia el valor devuelto da un error de compilación indicando que no se pueden implementar los dos.  
-         Si coinciden en sus declaraciones, se debe eliminar uno de los dos.

HERENCIA ENTRE INTERFACES
Se puede establecer una jerarquía de herencia entre interfaces, igual que con las clases.


public interface Interface2 extends Interface1{
…….
}

public interface Interface3 extends Interface1{
…….
}
En este ejemplo Interface2 e Interface3 heredan los métodos y constantes de Interface1 y además pueden añadir los suyos.
INTERFACES Y POLIMORFISMO
La definición de una interface implica una definición de un nuevo tipo de referencia y por ello se puede usar el nombre de la interface como nombre de tipo.
El nombre de una interfaz se puede utilizar en cualquier lugar donde pueda aparecer el nombre de un tipo de datos.
Si se define una variable cuyo tipo es una interface, se le puede asignar un objeto que sea una instancia de una clase que implementa la interface.
Volviendo al ejemplo del principio, las clases Linea y Fraccion implementan la interfaz Relacionable. Entonces podemos escribir las instrucciones:
Relacionable r1 = new Linea(2,2,4,1); 
Relacionable r2 = new Fraccion(4,7);

Utilizando interfaces como tipo se puede aplicar el polimorfismo para clases que no están relacionadas por herencia.
Por ejemplo, podemos escribir:
System.out.println(r1);  //ejecuta toString de Linea
System.out.println(r2);  //ejecuta toString de Fraccion
Podemos crear un array de referencias de tipo Relacionable y asignarles referencias a objetos de clases que la implementan.
Relacionable [] array = new Relacionable[3];
array[0] = new Linea(2,2,4,1);
array[1] = new Fraccion(4,7);
array[2] = new Linea(14,3,22,1);
for(Relacionable r: array)
            System.out.println(r); 

En este caso dos clases no relacionadas, Linea y Fraccion, por implementar la misma interfaz Relacionable podemos manejarlas a través de referencias a la interfaz y aplicar polimorfismo.



Las interfaces proporcionan más polimorfismo que el que se puede obtener de una simple jerarquía de clases

3 comentarios:

  1. Conceptos muy claros. Fantastico!

    ResponderEliminar
    Respuestas
    1. Gracias, la idea es esa, intentar explicarlo de la forma más clara y sencilla posible aunque no siempre se consigue xd

      Eliminar
  2. muy bien explicado, ¿como puedo hacer un metodo publico y estatico que devuelva un objeto de la interfaz?

    ResponderEliminar