
La interfaz en Java ha evolucionado mucho a lo largo de los años. Veamos qué cambios han ocurrido en el proceso de su desarrollo.
Interfaces originales
Las interfaces en Java 1.0 eran bastante simples en comparación con lo que son ahora. Podrían contener solo dos tipos de elementos: constantes y métodos abstractos públicos.
Campos constantes
Las interfaces pueden contener campos, al igual que las clases regulares, pero con algunas diferencias:
- Los campos deben ser inicializados.
- Los campos se consideran públicos estáticos finales
- Los modificadores public, static y final no necesitan especificarse explícitamente (están "desactivados" por defecto)
public interface MyInterface { int MY_CONSTANT = 9; }
Aunque esto no se especifica explícitamente, el campo MY_CONSTANT se considera una constante final estática pública. Puede agregar estos modificadores, pero esto no es necesario.
Métodos abstractos
Los elementos más importantes de una interfaz son sus métodos. Los métodos de interfaz también difieren de los métodos de clase regular:
- Los métodos no tienen cuerpo
- La implementación del método es proporcionada por clases que implementan esta interfaz.
- Los métodos se consideran públicos y abstractos incluso si no se especifican explícitamente.
- Los métodos no pueden ser finales, ya que la combinación de modificadores abstractos y finales no está permitida en Java
public interface MyInterface { int doSomething(); String doSomethingCompletelyDifferent(); }
Anidamiento
Java 1.1 introdujo el concepto de clases que se pueden colocar dentro de otras clases. Dichas clases son de dos tipos: estáticas y no estáticas. Las interfaces también pueden contener otras interfaces y clases.
Incluso si esto no se especifica explícitamente, tales interfaces y clases se consideran públicas y estáticas.
public interface MyInterface { class MyClass {
Enumeraciones y anotaciones
Se introdujeron dos tipos más en Java 5: Enumeraciones y Anotaciones. También se pueden colocar dentro de las interfaces.
public interface MyInterface { enum MyEnum { FOO, BAR; } @interface MyAnnotation {
Tipos genéricos
Java 5 introdujo el concepto de genéricos, tipos genéricos. En resumen: los genéricos le permiten usar un tipo genérico en lugar de especificar un tipo específico. Por lo tanto, puede escribir código que funcione con un número diferente de tipos sin sacrificar la seguridad y sin proporcionar una implementación separada para cada tipo.
En las interfaces que comienzan en Java 5, puede definir un tipo genérico y luego usarlo como el tipo del valor de retorno de un método o como el tipo de argumento para un método.
La interfaz de Box funciona independientemente de si la usa para almacenar objetos como String, Integer, List, Shoe o cualquier otro.
interface Box<T> { void insert(T item); } class ShoeBox implements Box<Shoe> { public void insert(Shoe item) {
Métodos estáticos
A partir de Java 8, puede incluir métodos estáticos en las interfaces. Este enfoque ha cambiado la forma en que la interfaz funciona para nosotros. Ahora funcionan de manera bastante diferente a como funcionaban antes de Java 8. Inicialmente, todos los métodos en las interfaces eran abstractos. Esto significaba que la interfaz solo proporcionaba una firma, pero no una implementación. La implementación se dejó a las clases que implementan su interfaz.
Al utilizar métodos estáticos en interfaces, también debe proporcionar una implementación del cuerpo del método. Para usar este método en una interfaz, simplemente use la palabra clave estática. Los métodos estáticos se consideran públicos por defecto.
public interface MyInterface {
Herencia del método estático
A diferencia de los métodos estáticos normales, los métodos estáticos en las interfaces no se heredan. Esto significa que si desea llamar a un método de este tipo, debe llamarlo directamente desde la interfaz y no desde la clase que lo implementa.
MyInterface.staticMethod();
Este comportamiento es muy útil para evitar múltiples problemas de herencia. Imagine que tiene una clase que implementa dos interfaces. Cada una de las interfaces tiene un método estático con el mismo nombre y firma. ¿Cuál debe usarse primero?
¿Por qué es útil?
Imagine que tiene una interfaz y un conjunto completo de métodos auxiliares que funcionan con clases que implementan esta interfaz.
Tradicionalmente, ha habido un enfoque para usar una clase complementaria. Además de la interfaz, se creó una clase de utilidad con un nombre muy similar que contiene métodos estáticos que pertenecen a la interfaz.
Puede encontrar ejemplos del uso de este enfoque directamente en el JDK: la interfaz java.util.Collection y la clase de utilidad java.util.Collections que lo acompaña.
Con métodos estáticos en las interfaces, este enfoque ya no es relevante, no es necesario y no se recomienda. Ahora puedes tener todo en un solo lugar.
Métodos predeterminados
Los métodos predeterminados son similares a los métodos estáticos en que también debe proporcionar un cuerpo para ellos. Para declarar un método predeterminado, simplemente use la palabra clave predeterminada.
public interface MyInterface { default int doSomething() { return 0; } }
A diferencia de los métodos estáticos, los métodos son heredados por defecto por las clases que implementan la interfaz. Lo que es importante, tales clases pueden redefinir su comportamiento si es necesario.
Aunque hay una excepción. La interfaz no puede tener métodos predeterminados con la misma firma que los métodos toString, equals y hashCode de la clase Object. Eche un vistazo a la respuesta de Brian Goetz para comprender la validez de dicha solución:
permitir que los métodos predeterminados anulen los métodos de Object.¿Por qué es útil?
La idea de implementar métodos directamente en la interfaz no parece del todo correcta. Entonces, ¿por qué se introdujo esta funcionalidad por primera vez?
Las interfaces tienen un problema. Tan pronto como le dé su API a otras personas, siempre se "petrificará" (no se puede cambiar sin dolor).
Por tradición, Java toma muy en serio la compatibilidad con versiones anteriores. Los métodos predeterminados proporcionan una forma de ampliar las interfaces existentes con nuevos métodos. Lo más importante, los métodos predeterminados ya proporcionan una implementación específica. Esto significa que las clases que implementan su interfaz no necesitan implementar ningún método nuevo. Pero, si es necesario, los métodos predeterminados pueden anularse en cualquier momento si su implementación deja de ser adecuada. En resumen, puede proporcionar una nueva funcionalidad a las clases existentes que implementan su interfaz, manteniendo la compatibilidad.
Conflictos
Imaginemos que tenemos una clase que implementa dos interfaces. Estas interfaces tienen un método predeterminado con el mismo nombre y firma.
interface A { default int doSomething() { return 0; } } interface B { default int doSomething() { return 42; } } class MyClass implements A, B { }
Ahora el mismo método predeterminado con la misma firma se hereda de dos interfaces diferentes. Cada interfaz tiene su propia implementación de este método.
Entonces, ¿cómo sabe nuestra clase cuál de las dos implementaciones diferentes usar?
El no lo sabrá. El código anterior dará como resultado un error de compilación. Si necesita que funcione, debe anular el método conflictivo en su clase.
interface A { default int doSomething() { return 0; } } interface B { default int doSomething() { return 42; } } class MyClass implements A, B {
Métodos privados
Con la llegada de Java 8 y la introducción de métodos predeterminados y métodos estáticos, las interfaces ahora tienen la capacidad de contener no solo las firmas de métodos, sino también su implementación. Al escribir tales implementaciones, se recomienda que los métodos complejos se dividan en otros más simples. Dicho código es más fácil de reutilizar, mantener y comprender.
Para este propósito, usaría métodos privados, ya que pueden contener todos los detalles de implementación que no deberían ser visibles y utilizados desde el exterior.
Desafortunadamente en Java 8, una interfaz no puede contener métodos privados. Esto significa que puede usar:
- Técnicas corporales largas, complejas y difíciles de entender.
- Métodos auxiliares que forman parte de la interfaz. Esto viola el principio de encapsulación y contamina la API pública de la interfaz y las clases de implementación.
Afortunadamente, a partir de
Java 9, puede usar métodos privados en las interfaces . Tienen las siguientes características:
- los métodos privados tienen un cuerpo, no son abstractos
- pueden ser estáticos o no estáticos
- no son heredados por las clases que implementan la interfaz y las interfaces
- pueden llamar a otros métodos de interfaz
- los métodos privados pueden llamar a otros métodos privados, abstractos, estáticos o predeterminados
- los métodos estáticos privados solo pueden llamar a otros métodos estáticos y estáticos privados
public interface MyInterface { private static int staticMethod() { return 42; } private int nonStaticMethod() { return 0; } }
Orden cronológico
La siguiente es una lista cronológica de cambios para las versiones de Java:
Java 1.1
Clases anidadas
Interfaces anidadas
Java 5
Tipos genéricos
Transferencias Cerradas
Anotaciones anidadas
Java 8
Métodos predeterminados
Métodos estáticos
Java 9
Métodos privados