Tutorial de Java

Interfaces

Anterior | Siguiente
Los métodos abstractos son útiles cuando se quiere que cada implementación de la clase parezca y funcione igual, pero necesita que se cree una nueva clase para utilizar los métodos abstractos. Los interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior, lo que permite simular la herencia múltiple de otros lenguajes.

Un interfaz sublima el concepto de clase abstracta hasta su grado más alto. Un interfaz podrá verse simplemente como una forma, es como un molde, solamente permite declarar nombres de métodos, listas de argumentos, tipos de retorno y adicionalmente miembros datos (los cuales podrán ser únicamente tipos básicos y serán tomados como constantes en tiempo de compilación, es decir, static y final).

Un interfaz contiene una colección de métodos que se implementan en otro lugar. Los métodos de una clase son public, static y final.

La principal diferencia entre interface y abstract es que un interfaz proporciona un mecanismo de encapsulación de los protocolos de los métodos sin forzar al usuario a utilizar la herencia. Por ejemplo:

    public interface VideoClip {
        // comienza la reproduccion del video
        void play();
        // reproduce el clip en un bucle
        void bucle();
        // detiene la reproduccion
        void stop();
        }

Las clases que quieran utilizar el interfaz VideoClip utilizarán la palabra implements y proporcionarán el código necesario para implementar los métodos que se han definido para el interfaz:

    class MiClase implements VideoClip {
        void play() {
            <código>
            }
        void bucle() {
            <código>
            }
        void stop() {
            <código>
            }

Al utilizar implements para el interface es como si se hiciese una acción de copiar-y-pegar del código del interface, con lo cual no se hereda nada, solamente se pueden usar los métodos.

La ventaja principal del uso de interfaces es que una clase interface puede ser implementada por cualquier número de clases, permitiendo a cada clase compartir el interfaz de programación sin tener que ser consciente de la implementación que hagan las otras clases que implementen el interface.

    class MiOtraClase implements VideoClip {
        void play() {
            <código nuevo>
            }
        void bucle() {
            <código nuevo>
            }
        void stop() {
            <código nuevo>
            }

Es decir, el aspecto más importante del uso de interfaces es que múltiples objetos de clases diferentes pueden ser tratados como si fuesen de un mismo tipo común, donde este tipo viene indicado por el nombre del interfaz.

Aunque se puede considerar el nombre del interfaz como un tipo de prototipo de referencia a objetos, no se pueden instanciar objetos en sí del tipo interfaz. La definición de un interfaz no tiene constructor, por lo que no es posible invocar el operador new sobre un tipo interfaz.

Un interfaz puede heredar de varios interfaces sin ningún problema. Sin embargo, una clase solamente puede heredar de una clase base, pero puede implementar varios interfaces. También, el JDK ofrece la posibilidad de definir un interfaz vacío, como es el caso de Serialize, que permite serializar un objeto. Un interfaz vacío se puede utilizar como un flag, un marcador para marcar a una clase con una propiedad determinada.

La aplicación java514.java, ilustra algunos de los conceptos referentes a los interfaces. Se definen dos interfaces, en uno de ellos se definen dos constantes y en el otro se declara un método put() y un método get(). Las constantes y los métodos se podrían haber colocado en la misma definición del interfaz, pero se han separado para mostrar que una clase simple puede implementar dos o más interfaces utilizando el separador coma (,) en la lista de interfaces.

También se definen dos clases, implementando cada una de ellas los dos interfaces. Esto significa que cada clase define el método put() y el método get(), declarados en un interfaz y hace uso de las constantes definidas en el otro interfaz. Estas clase se encuentran en ficheros separados por exigencias del compilador, los ficheros son Constantes.java y MiInterfaz.java, y el contenido de ambos ficheros es el que se muestra a continuación:

public interface Constantes {
    public final double pi = 6.14;
    public final int constanteInt = 125;
    }
      
public interface MiInterfaz {
    void put( int dato );
    int get();
    }

Es importante observar que en la definición de los dos métodos del interfaz, cada clase los define de la forma más adecuada para esa clase, sin tener en cuenta cómo estará definidos en las otras clases.

Una de las clases también define el método show(), que no está declarado en el interfaz. Este método se utiliza para demostrar que un método que no está declarado en el interfaz no puede ser accedido utilizando una variable referencia de tipo interfaz.

El método main() en la clase principal ejecuta una serie de instanciaciones, invocaciones de métodos y asignaciones destinadas a mostrar las características de los interfaces descritos anteriormente. Si se ejecuta la aplicación, las sentencias que se van imprimiendo en pantalla son autoexplicactivas de lo que está sucediendo en el corazón de la aplicación.

Los interfaces son útiles para recoger las similitudes entre clase no relacionadas, forzando una relación entre ellas. También para declarar métodos que forzosamente una o más clases han de implementar. Y también, para tener acceso a un objeto, para permitir el uso de un objeto sin revelar su clase, son los llamados objetos anónimos, que son muy útiles cuando se vende un paquete de clases a otros desarrolladores.

C++ permite al programador crear nuevas clases como una subclase de dos o más superclases diferentes, es la herencia múltiple. Java no permite herencia múltiple. Hay autores que dicen que el interfaz de Java es un sustituto de la herencia múltiple y hay otros que están en desacuerdo con eso. Lo cierto es que sí parece una alternativa y los interfaces resuelven algunos de los problemas de la herencia múltiple, por ejemplo:

  • No se pueden heredar variables desde un interfaz
  • No se pueden heredar implementaciones de métodos desde un interfaz
  • La jerarquía de un interfaz es independiente de la jerarquía de clases que implementen el mismo interfaz

y esto no es cierto en la herencia múltiple, tal como se ve desde C++.

Definición

La definición de un interfaz es semejante a la de una clase. La definición de interfaz tiene dos componentes, declaración y cuerpo. En forma esquemática sería:

    DeclaracionInterfaz {
        // cuerpoInterfaz
        }

Declaración

La mínima declaración consiste en la palabra clave interface y el nombre del interfaz. Por convenio, los nombres de interfaces comienzan con una letra mayúscula, como los nombres de las clases, pero no es obligatorio.

La declaración de interfaz puede tener dos componentes adicionales, el especificador de acceso public y la lista de superinterfaces.

Un interfaz puede extender otros interfaces. Sin embargo, mientras que una clase solamente puede extender otra clase, un interfaz puede extender cualquier número de interfaces. En el ejemplo se muestra la definición completa de un interfaz, declaración y cuerpo.

public interface MiInterfaz extends InterfazA,InterfazB {
    public final double PI = 3.14159;
    public final int entero = 125;
    void put( int dato );
    int get();
    }

El especificador de acceso public indica que el interfaz puede ser utilizado por cualquier clase de cualquier paquete. Si se omite, el interfaz solamente será accesible a aquellas clases que estén definidas en el mismo paquete.

La cláusula extends es similar a la de la declaración de clase, aunque un interfaz puede extender múltiples interfaces. Un interfaz no puede extender clases.

La lista de superinterfaces es un lista separada por comas de todas las interfaces que la nueva interfaz va a extender. Un interfaz hereda todas las constantes y métodos de sus superinterfaces, excepto si el interfaz oculta una constante con otra del mismo nombre, o si redeclara un método con una nueva declaración de ese método.

El cuerpo del interfaz contiene las declaraciones de los métodos, que terminan en un punto y coma y no contienen código alguno en su cuerpo. Esto es semejante al prototipo de funciones en C++. Todos los métodos declarados en un interfaz son implícitamente, public y abstract, y no se permite el uso de transient, volatile, private, protected o syncronized en la declaración de miembros en un interfaz. En el cuerpo del interfaz se pueden definir constantes, que serán implícitamente public, static y final.

Implementación

Un interfaz se utiliza definiendo una clase que implemente el interfaz a través de su nombre. Cuando una clase implementa un interfaz, debe proporcionar la definición completa de todos los métodos declarados en el interfaz y, también, la de todos los métodos declarados en todos los superinterfaces de ese interfaz.

Una clase puede implementar más de un interfaz, incluyendo varios nombre de interfaces separados por comas. En este caso, la clase debe proporcionar la definición completa de todos los métodos declarados en todos los interfaces de la lista y de todos los superinterfaces de esos interfaces.

En el anterior ejemplo, java514.java, se puede observar una clase que implementa dos interfaces, Constantes y MiInterfaz.

    class ClaseA implements Constantes,MiInterfaz {
        double dDato;
        // Define las versiones de put() y get() que utiliza la ClaseA
        public void put( int dato ) {
            // Se usa "pi" del interfaz Constantes
            dDato = (double)dato * pi;
            System.out.println(
                "En put() de ClaseA, usando pi del interfaz " +
                "Constantes: " + dDato );
            }
        public int get() {
            return( (int)dDato );
            }
        // Se define un metodo show() para la ClaseA que no esta
        // declarado en el interfaz MiInterfaz
        void show() {
            System.out.println(
                "En show() de ClaseA, dDato = " + dDato );
            }
        }

Como se puede observar, esta clase proporciona la definición completa de los métodos put() y get() del interfaz MiInterfaz, a la vez que utiliza las constantes definidas en el interfaz Constantes. Además, la clase proporciona la definición del método show() que no está declarado en ninguno de los interfaces, sino que es propio de la clase.

La definición de un interfaz es una definición de un nuevo tipo de datos. Se puede utilizar el nombre del interfaz en cualquier lugar en que se pueda utilizar un nombre de tipo de dato. Sin embargo, no se pueden instanciar objetos del tipo interfaz, porque un interfaz no tiene constructor. En esta sección, hay numerosos ejemplos de uso del nombre de un interfaz como tipo.

Herencia "Multiple"

Un interfaz no es solamente una forma más pura de denominar a una clase abstracta, su propósito es mucho más amplio que eso. Como un interfaz no tiene ninguna implementación, es decir, no hay ningun tipo de almacenamiento asociado al interfaz, no hay nada que impida la combinación de varios interfaces. Esto tiene mucho valor porque hay veces en que es necesario que un objeto X sea a y b y c. En C++ es donde esto se acuñó como herencia multiple y no es sencillo de hacer porque cada clase puede tener su propia implementación. En Java, se puede hacer lo mismo, pero solamente una de las clases puede tener implementeación, con lo cual los problemas que se presentan en C++ no suceden con Java cuando se combinan multiples interfaces.

En una clase derivada, el programador no está forzado a tener una clase base sea esta abstracta o concreta, es decir, una sin métodos abstractos. Si se hereda desde una clase no interfaz, solamente se puede heredar de una. El resto de los elementos base deben ser interfaces. Todos los nombres de interfaces se colocan después de la palabra clave implements y separados por comas. Se pueden tener tantos interfaces como se quiera y cada uno de ellos será un tipo independiente. El ejemplo siguiente, java516.java, muestra una clase concreta combinada con varios interfaces para producir una nueva clase

import java.util.*;

interface Luchar {
    void luchar();
    }  
interface Nadar {
    void nadar();
    }
interface Volar {
    void volar();
    }
class Accion {
    public void luchar() {}
    }
   
class Heroe extends Accion implements Luchar,Nadar,Volar {
    public void nadar() {}
    public void volar() {}
    }
    
public class java516 {
    static void a( Luchar x ) { 
        x.luchar(); 
        }
    static void b( Nadar x ) { 
        x.nadar(); 
        }
    static void c( Volar x ) { 
        x.volar(); 
        }
    static void d( Accion x ) { 
        x.luchar(); 
        }
  
    public static void main( String args[] ) {
        Heroe i = new Heroe();
        a( i ); // Trata al Heroe como Luchar
        b( i ); // Trata al Heroe como Nadar
        c( i ); // Trata al Heroe como Volar
        d( i ); // Trata al Heroe como Accion
        }
    }

Se puede observar que Heroe combina la clase concreta Accion con los interfaces Luchar, Nadar y Volar. Cuando se combina la clase concreta con interfaces de este modo, la clase debe ir la primera y luego los interfaces; en caso contrario, el compilador dará un error.

La autentificación, signature, para luchar() es la misma en el interfaz Luchar y en la clase Accion, y luchar() no está proporcionado con la definicion de Heroe. Si se quiere crear un objeto del nuevo tipo, la clase debe tener todas las definiciones que necesita, y aunque Heroe no proporciona una definicion explicita para luchar(), la definición es proporcionada automáticamente por Accion y así es posible crear objetos de tipo Heroe.

En la clase java516, hay cuatro métodos que toman como argumentos los distintos interfaces y la clase concreta. Cuando un objeto Heroe es creado, puede ser pasado a cualquierea de estos métodos, lo que significa que se está realizando un upcasting al interfaz de que se trate. Debido a la forma en que han sido diseñados los interfaces en Java, esto funciona perfectamente sin ninguna dificultad ni esfuerzo extra por parte del programador.

La razón principal de la existencia de los interfaces en el ejemplo anterior es el proder realizar un upcasting a más de un tipo base. Sin embargo, hay una segunda razón que es la misma por la que se usan clases abstractas: evitar que el programador cliente tenga que crear un objeto de esta clase y establecer que solamente es un interfaz. Y esto plantea la cuestión de qué se debe utilizar pues, un interfaz o una clase abastacta. Un interfaz proporciona los beneficios de una clase abstracta y, además, los beneficios propios del interfaz, así que es posible crear una clase base sin la definición de ningún método o variable miembro, por lo que se deberían utilizar mejor los interfaces que las clases abstractas. De hecho, si se desea tener una clase base, la primera elección debería ser un interfaz, y utilizar clases abstractas solamente si es necesario tener alguna variable miembro o algun método implementado.

Navegador

Home | Anterior | Siguiente | Indice | Correo