¿Qué es una Interfaz en Java?
Java, siendo un lenguaje de programación orientada a objetos (POO), introduce el concepto de interfaz para definir tipos abstractos. Las interfaces sirven para escribir código que sea mantenible, reutilizable, y flexible. Podemos lograr lo anterior gracias a que las interfaces permiten el uso del polimorfismo.
¿Cómo Crear una Interfaz en Java?
Puedes crear una interfaz de una forma muy similar a como creas una
clase en Java,
con la diferencia de que en lugar de utilizar la palabra reservada class
,
debes usar la palabra reservada interface
(que significa “interfaz” en inglés).
5 Pasos Para Crear una Interfaz en Java
- Asigna el nivel de acceso de la interfaz, solo puede ser público o de paquete
- Utiliza la palabra reservada
interface
- Dale un nombre a tu interfaz, digamos
MiInterfaz
- Define los métodos que deberán implementar las clases
- Incluye métodos estáticos y/o métodos por defecto (Opcional)
A continuación, un ejemplo de cómo crear una interfaz simple en Java:
public interface MiInterfaz {
public void metodoUno();
public int metodoDos(int parametroUno);
}
Cuando vayas a utilizar interfaces en tus programas de Java, es importante que sepas cuál versión de Java estás utilizando, ya que las características de las que dispones varían dependiendo de la versión de Java. Por ejemplo, las interfaces “selladas” (en inglés: sealed interfaces) están disponibles únicamente a partir de la versión 17.
¿Cómo se Implementa una Interfaz en Java?
Para implementar una interfaz en Java, debes utilizar la palabra reservada implements
.
Digamos que has definido la siguiente interfaz:
public interface Calculadora {
int sumarEnteros(int a, int b);
}
Luego, puedes implementar la interfaz de la siguiente forma:
1. class MiCalculadora implements Calculadora {
2.
3. @Override
4. public int sumarEnteros(int a, int b) {
5. return a + b;
6. }
7. }
8.
9.
10. public class EjemploInterfaz {
11. public static void main(String[] args) {
12. var miCalculadora = new MiCalculadora();
13.
14. System.out.println( "2 + 3 es: " + miCalculadora.sumarEnteros(2, 3) );
15. }
16. }
Podrás notar que en la línea 3 estamos utilizando la anotación @Override
(significa “sobrescribir”),
esto no es obligatorio, pero se recomienda para distinguir entre los métodos que estás implementando
debido a que te lo pide la interfaz, y los métodos que son originales a la clase.
Además de implementar los métodos exigidos por la interfaz, también puedes agregar métodos adicionales a tu clase,
a continuación agrego el método sumarFlotantes()
:
1. class MiCalculadora implements Calculadora {
2.
3. @Override
4. public int sumarEnteros(int a, int b) {
5. return a + b;
6. }
7.
8. public float sumarFlotantes(float a, float b) {
9. return a + b;
10. }
11. }
El método agregado no lleva la anotación @Override
, de esta forma sabemos que es un método introducido por la clase
y no “exigido” por la interfaz que estamos implementando.
Implementando Múltiples Interfaces
Una clase puede implementar varias interfaces, a diferencia de la herencia, donde se puede extender únicamente una clase padre.
A continuación, un ejemplo de cómo implementar más de una interfaz en una misma clase:
interface HomoSapiens {
void hablar();
}
interface Alfabeta {
void leer();
void escribir();
}
class Humano implements HomoSapiens, Alfabeta {
@Override
public void hablar() {
System.out.println("Estoy hablando.");
}
@Override
public void leer() {
System.out.println("Estoy leyendo.");
}
@Override
public void escribir() {
System.out.println("Estoy escribiendo.");
}
}
public class Main {
public static void main(String[] args) {
var pancho = new Humano();
pancho.hablar(); // Imprime: "Estoy hablando!", en la consola.
pancho.leer(); // Imprime: "Estoy leyendo!", en la consola.
pancho.escribir(); // Imprime: "Estoy escribiendo!", en la consola.
}
}
Tipos de Interfaces en Java
Podemos clasificar las interfaces en Java en dos tipos principales: interfaces predefinidas e interfaces de programador. Cabe mencionar que esta clasificación no es oficial, pero personalmente pienso que es una buena forma de clasificar las interfaces.
En las siguientes subsecciones voy a describir brevemente cada uno de estos tipos, y más adelante daré información específica sobre estos tipos de interfaces, junto con algunos ejemplos prácticos.
Interfaces Predefinidas
Las interfaces predefinidas son aquellas que vienen incluidas en la librería estándar de Java, las cuales pueden a su vez ser clasificadas con los subtipos de interfaces de uso general e interfaces marcadoras (Conocidas en inglés como: “marker interfaces”).
Interfaces de uso general
Las interfaces predefinidas de uso general son aquellas que utilizamos comúnmente en nuestras aplicaciones.
Algunos ejemplos de estas interfaces de Java son:
List
, Set
, Map
, Iterable
, Comparable
, Runnable
, por mencionar algunas.
Interfaces marcadoras (en inglés: “marker interfaces”)
Este tipo de interfaces en Java no definen ningún método ni propiedad, solo sirven para “marcar” (De ahí su nombre) o etiquetar un comportamiento específico para las clases.
Algunos ejemplos de estas interfaces son:
Serializable
: Esta interfaz se utiliza para marcar una clase como serializable, lo que significa que sus instancias se pueden convertir en un flujo de bytes para su almacenamiento o transmisión.Cloneable
: Esta interfaz se utiliza para marcar una clase como clonable, lo que significa que sus instancias se pueden copiar con el métodoclone()
.RandomAccess
: Esta interfaz se utiliza para marcar una lista (List) como una lista de acceso aleatorio, lo que significa que sus elementos se pueden acceder de manera eficiente mediante su índice.SingleThreadModel
: Esta interfaz se utiliza para marcar una clase como segura para el uso en un entorno de subprocesos de un solo hilo.Annotation
: Esta interfaz se utiliza para marcar una clase como una anotación, lo que significa que se puede utilizar para agregar metadatos a otras clases y métodos.Remote
: Esta interfaz se utiliza para marcar una interfaz como remota, lo que significa que sus métodos se pueden invocar a través de la red.
Estos son solo algunos ejemplos de interfaces marcadoras. También es importante que tengas en cuenta que las interfaces marcadoras no tienen ningún efecto en el comportamiento o la funcionalidad de las clases que las implementan.
Utilizamos las interfaces marcadoras únicamente para etiquetar una clase con una cierta propiedad o comportamiento, lo que nos permite verificar si una clase tiene esa propiedad o comportamiento desde otras partes del programa.
Interfaces de Programador
Las interfaces de programador son aquellas que nosotros mismos creamos de acuerdo a los requerimientos y/o arquitectura de nuestros programas. Yo clasifico este tipo de interfaces en los subtipos de interfaces estándares e interfaces selladas (en inglés: “sealed interfaces”),
Más adelante explico este tipo de interfaces.
Interfaces Estándares
Las interfaces estándares son probablemente las más comunes en Java, existen desde la creación del lenguaje mismo, y son aquellas interfaces que nos permiten crear un tipo abstracto en nuestros programas de Java.
Tradicionalmente, en las interfaces de Java incluimos métodos abstractos (firmas de métodos) y luego, las clases que implementen estas interfaces, deben proveer el código que deberá ser ejecutado por esos métodos.
Sin embargo, las características de las interfaces han ido evolucionando con nuevas versiones de Java, de tal forma que hoy en día también podemos incluir métodos por defecto (en inglés: “default methods”) en las interfaces, estos métodos ya contienen la implementación del código, tal cual lo haría una clase normal.
También, en versiones recientes de Java podemos incluir métodos estáticos (en inglés: “static methods”) en las interfaces, que al igual que los métodos estáticos de las clases, no es necesario crear una instancia para poder invocarlos.
Características de una Interfaz en Java
Las interfaces son de naturaleza abstracta
Originalmente, las interfaces en Java fueron creadas con el único propósito de definir un contrato para las clases, por eso, dentro de las interfaces solamente se podían definir métodos abstractos, sin embargo, con el paso del tiempo las interfaces han incrementado su funcionalidad, de tal forma que ahora podemos incluir métodos por defecto y métodos estáticos, mismos que describiré más adelante en este artículo.
No obstante, aún podemos decir que las interfaces conservan su naturaleza abstracta, debido a que todavía funcionan como un contrato para las clases.
Las interfaces no pueden ser instanciadas directamente
No podemos crear objetos directamente utilizando las interfaces, si queremos crear una instancia que sea del tipo de una interfaz, primero debemos crear una clase que implemente la interfaz y posteriormente crear un objeto de dicha clase.
interface MiInterfaz {
void decirAlgo();
}
public static void main(String[] args) {
// Esto arroja un error de compilación
var miObjeto = new MiInterfaz();
}
Niveles de Acceso en las Interfaces de Java
Las interfaces en Java solo pueden tener acceso público o de paquete,
por ejemplo, decimos que la siguiente interfaz tiene acceso de paquete porque no utilizamos ningún modificador de acceso (como: public
, private
, o protected
):
package paquete1;
// Interfaz con acceso de paquete (acceso por defecto)
interface UnaInterfaz {
public int metodoUno();
public float metodoDos();
}
La interfaz anterior solo podrá ser implementada por clases que pertenezcan al mismo paquete de la interfaz,
en este caso paquete1
, las clases que estén definidas en otros paquetes no podrán acceder a esta interfaz,
cabe mencionar que este es el comportamiento normal del acceso de paquete,
no solo de las interfaces, sino también de las clases.
A continuación, la misma interfaz, pero esta vez con nivel de acceso público:
package paquete1;
// Interfaz con acceso público
public interface UnaInterfaz {
public int metodoUno();
public float metodoDos();
}
En este caso, podrás acceder a la interfaz desde cualquier parte de tu programa,
digamos desde una clase que pertenezca a un paquete diferente, como paquete2
.
Si intentas asignar un nivel de acceso privado (private
) o protegido (protected
) a tu interfaz,
obtendrás un error de compilación.
Niveles de Acceso de los Métodos de Interfaz
Los métodos que definas en una interfaz siempre tendrán acceso público de forma implícita,
es decir, puedes omitir la palabra reservada public
, como en el siguiente ejemplo:
public interface Calculadora {
float sumar(float n1, float n2);
float restar(float n1, float n2);
}
A diferencia de los métodos que implementamos en las clases, los métodos en una interfaz que no llevan modificador de acceso, Java los considera como públicos, y no de paquete.
De hecho, después de compilar el código anterior, Java le agrega la palabra reservada public
a los métodos de forma automática.
A continuación, la forma en que Java vería el código después de compilar:
public interface Calculadora {
public float sumar(float n1, float n2);
public float restar(float n1, float n2);
}
Si quisiéramos, podríamos incluir el modificador de acceso public
explícitamente en la definición de los métodos en las interfaces,
pero sería redundante. En mi opinión, en estos casos es mejor omitir el modificador de acceso,
porque logramos que nuestro código sea vea más conciso.
Definiendo Constantes en las Interfaces de Java
En Java, podemos definir constantes en las interfaces de la siguiente manera:
public interface Circulo {
// Esta es una constante
float PI = 3.14159f;
float calcularArea(float radio);
}
Al igual que las firmas de los métodos que incluimos en las interfaces,
Java agrega el modificador de acceso public
automáticamente, después de compilar.
Java también agrega las palabras reservadas static
y final
automáticamente a las constantes que definimos en las interfaces.
A continuación, la forma en que Java ve el código después de compilar:
public interface Circulo {
// Esta es una constante
public static final float PI = 3.14159f;
public float calcularArea(float radio);
}
Métodos Estáticos de Interfaz
A partir de Java 8, podemos implementar métodos estáticos en las interfaces, mismos que podemos invocar directamente con el nombre de la interfaz.
A continuación muestro una interfaz con la implementación de un método estático:
public interface MiInterfaz {
static void decirAlgo() {
System.out.println("Estoy diciendo algo.");
}
}
Luego, para poder invocar el método decirAlgo
,
solo nos referimos al nombre de la interfaz seguido del nombre del método,
utilizando el operador referencial.
MiInterfaz.decirAlgo(); // Esto imprime: "Estoy diciendo algo.", en la consola.
Así de simple, puedes observar que no tuvimos que implementar la interfaz, ni crear un objeto para poder invocar el método.
Una diferencia importante entre los métodos estáticos de interfaz y los métodos estáticos de clase, es que los métodos estáticos de interfaz no se heredan a las clases hijas de las clases que implementen la interfaz, tampoco se heredan a las interfaces hijas de la interfaz que implementa el método estático.
Métodos Por Defecto de Interfaz
Java 8 introdujo los métodos por defecto, con lo cual podemos implementar métodos en una interfaz. Las clases que implementen dichas interfaces no tienen que implementar los métodos por defecto que hayamos definido en la interfaz.
Para implementar un método por defecto en una interfaz,
hacemos uso de la palabra reservada default
en la definición del método:
public interface MiInterfaz {
default void decirAlgo() {
System.out.println("Soy un método por defecto.");
}
}
Después, cuando implementamos la interfaz en alguna clase,
no estamos forzados a ofrecer una implementación del método decirAlgo()
en la clase:
class MiClase implements MiInterfaz {}
public class ProbandoMetodosPorDefecto {
pubic static void main(String[] args) {
var miObjeto = new MiClase();
// Esto imprime: "Soy un método por defecto.", en consola.
miObjeto.decirAlgo();
}
}
Interfaces Selladas (“Sealed Interfaces”)
Las interfaces selladas se introdujeron con la versión 17 de Java, las cuales nos permiten crear interfaces que solo pueden ser implementadas por aquellas clases que nosotros permitamos explícitamente, o que solo pueden ser extendidas por las interfaces que nosotros indiquemos.
Para crear una interfaz sellada, utilizamos las palabras reservadas sealed
y permits
, como en el siguiente ejemplo:
1. public sealed interface Mascota permits Perro, Gato {
3.
4. void decirHola();
5.
6. }
En la línea 1 puedes observar que utilizamos la palabra reservada sealed
,
justo antes de la palabra reservada interface
,
luego empleamos la palabra reservada permits
,
después del nombre de la interfaz,
seguida de una lista de nombres de clases separadas por coma (Perro y Gato).
Esto significa que las clases Perro y Gato son las únicas que podrán implementar la interfaz Mascota, como se muestra a continuación:
1. class Perro implements Mascota {
2. @Override
3. public void decirHola() {
4. System.out.println("¡Guau!");
5. }
6. }
7.
8. class Gato implements Mascota {
9. @Override
10. public void decirHola() {
11. System.out.println("¡Miau!");
12. }
13. }
14.
15. public class EjemploInterfazSellada {
16. public static void main(String[] args) {
17. Mascota perro = new Perro();
18. perro.decirHola();
19.
20. Mascota gato = new Gato();
21. gato.decirHola();
22. }
23. }
Si intentamos implementar la interfaz sellada con una clase que no está permitida,
es decir, que no incluimos en la lista separada por comas después de la palabra reservada permits
,
obtendremos un error de compilación:
// Error de compilación.
class Perico implements Mascota {
@Override
public void decirHola() {
System.out.println("Hola!");
}
}
Referencias
Pankaj (2022)
“Java 8 Interface Changes - static method, default method” (Inglés)
Digital Ocean
Gupta, M (2021)
“Lucha contra la ambigüedad y mejora tu código con las clases selladas de Java 17” (Inglés)
Revista Java de Oracle.