Cambios en la interfaz de Java 8: método estático, método predeterminado

Java 8 introdujo cambios en las interfaces, incluyendo métodos estáticos y métodos por defecto en las interfaces. Antes de Java 8, solo podíamos tener declaraciones de métodos en las interfaces. Pero desde Java 8, podemos tener métodos por defecto y métodos estáticos en las interfaces.

Interfaz Java 8

Diseñar interfaces siempre ha sido una tarea difícil porque si queremos agregar métodos adicionales en las interfaces, requerirá cambios en todas las clases que las implementan. A medida que la interfaz envejece, la cantidad de clases que la implementan puede crecer hasta un punto en que no sea posible extender las interfaces. Es por eso que al diseñar una aplicación, la mayoría de los marcos proporcionan una clase de implementación base y luego la extendemos y anulamos los métodos que son aplicables a nuestra aplicación. Vamos a analizar los métodos de interfaz por defecto y los métodos de interfaz estática y la razón de su introducción en los cambios de la interfaz Java 8.

Método por Defecto en la Interfaz Java

Para crear un método predeterminado en una interfaz de Java, necesitamos usar la palabra clave “default” con la firma del método. Por ejemplo,

package com.journaldev.java8.defaultmethod;

public interface Interface1 {

	void method1(String str);
	
	default void log(String str){
		System.out.println("I1 logging::"+str);
	}
}

Ten en cuenta que log(String str) es el método predeterminado en la interfaz Interface1. Ahora, cuando una clase implemente Interface1, no es obligatorio proporcionar una implementación para los métodos predeterminados de la interfaz. Esta característica nos ayudará a extender interfaces con métodos adicionales, todo lo que necesitamos es proporcionar una implementación predeterminada. Supongamos que tenemos otra interfaz con los siguientes métodos:

package com.journaldev.java8.defaultmethod;

public interface Interface2 {

	void method2();
	
	default void log(String str){
		System.out.println("I2 logging::"+str);
	}

}

Sabemos que Java no nos permite extender múltiples clases porque resultaría en el “Problema del Diamante”, donde el compilador no puede decidir qué método de superclase usar. Con los métodos predeterminados, el problema del diamante también surgiría para las interfaces. Porque si una clase está implementando tanto Interface1 como Interface2 y no implementa el método predeterminado común, el compilador no puede decidir cuál elegir. La extensión de múltiples interfaces es una parte integral de Java, la encontrarás en las clases principales de Java, así como en la mayoría de las aplicaciones empresariales y frameworks. Entonces, para asegurarnos de que este problema no ocurra en las interfaces, es obligatorio proporcionar una implementación para los métodos predeterminados comunes de las interfaces. Por lo tanto, si una clase está implementando ambas interfaces anteriores, tendrá que proporcionar una implementación para el método log() de lo contrario, el compilador generará un error en tiempo de compilación. Una clase simple que implementa tanto Interface1 como Interface2 será:

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

	@Override
	public void method2() {
	}

	@Override
	public void method1(String str) {
	}

	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
}

Puntos importantes sobre los métodos predeterminados de la interfaz de Java:

  1. Los métodos predeterminados de interfaz de Java nos ayudarán a extender interfaces sin temor a romper las clases de implementación.
  2. Los métodos predeterminados de interfaz en Java han eliminado las diferencias entre interfaces y clases abstractas.
  3. Los métodos predeterminados de interfaz en Java 8 nos ayudarán a evitar clases de utilidad, ya que todos los métodos de la clase Collections pueden proporcionarse en las propias interfaces.
  4. Los métodos predeterminados de interfaz en Java nos ayudarán a eliminar clases de implementación base; podemos proporcionar una implementación predeterminada y las clases de implementación pueden elegir cuál sobrescribir.
  5. Una de las principales razones para introducir métodos predeterminados en interfaces es mejorar la API de Colecciones en Java 8 para admitir expresiones lambda.
  6. Si alguna clase en la jerarquía tiene un método con la misma firma, entonces los métodos predeterminados se vuelven irrelevantes. Un método predeterminado no puede anular un método de java.lang.Object. La razón es muy simple, porque Object es la clase base para todas las clases de Java. Entonces, incluso si tenemos métodos de clase Object definidos como métodos predeterminados en interfaces, será inútil porque siempre se usará el método de clase Object. Por eso, para evitar confusiones, no podemos tener métodos predeterminados que anulen los métodos de la clase Object.
  7. Los métodos predeterminados de interfaz en Java también se conocen como Métodos Defensores o Métodos de extensión virtual.

Método Estático de Interfaz Java

El método estático de la interfaz de Java es similar al método por defecto excepto que no podemos anularlos en las clases de implementación. Esta característica nos ayuda a evitar resultados no deseados en caso de una implementación deficiente en las clases de implementación. Veamos esto con un ejemplo simple.

package com.journaldev.java8.staticmethod;

public interface MyData {

	default void print(String str) {
		if (!isNull(str))
			System.out.println("MyData Print::" + str);
	}

	static boolean isNull(String str) {
		System.out.println("Interface Null Check");

		return str == null ? true : "".equals(str) ? true : false;
	}
}

Ahora veamos una clase de implementación que tiene el método isNull() con una implementación deficiente.

package com.journaldev.java8.staticmethod;

public class MyDataImpl implements MyData {

	public boolean isNull(String str) {
		System.out.println("Impl Null Check");

		return str == null ? true : false;
	}
	
	public static void main(String args[]){
		MyDataImpl obj = new MyDataImpl();
		obj.print("");
		obj.isNull("abc");
	}
}

Observa que isNull(String str) es un método de clase simple, no está anulando el método de la interfaz. Por ejemplo, si agregamos la anotación @Override al método isNull(), dará como resultado un error de compilación. Ahora, cuando ejecutemos la aplicación, obtendremos la siguiente salida.

Interface Null Check
Impl Null Check

Si cambiamos el método de la interfaz de estático a por defecto, obtendremos la siguiente salida.

Impl Null Check
MyData Print::
Impl Null Check

El método estático de la interfaz de Java es visible solo para los métodos de la interfaz, si eliminamos el método isNull() de la clase MyDataImpl, no podremos usarlo para el objeto MyDataImpl. Sin embargo, al igual que otros métodos estáticos, podemos usar métodos estáticos de la interfaz utilizando el nombre de la clase. Por ejemplo, una declaración válida sería:

boolean result = MyData.isNull("abc");

Puntos importantes sobre el método estático de la interfaz de Java:

  1. El método estático de la interfaz de Java es parte de la interfaz, no podemos usarlo para objetos de clase de implementación.
  2. Los métodos estáticos de la interfaz de Java son útiles para proporcionar métodos de utilidad, por ejemplo, verificación de nulos, ordenación de colecciones, etc.
  3. El método estático de la interfaz Java nos ayuda a proporcionar seguridad al no permitir que las clases de implementación las anulen.
  4. No podemos definir métodos estáticos de interfaz para los métodos de la clase Object, obtendremos un error del compilador que dice “Este método estático no puede ocultar el método de instancia de Object”. Esto se debe a que no está permitido en Java, ya que Object es la clase base para todas las clases y no podemos tener un método estático a nivel de clase y otro método de instancia con la misma firma.
  5. Podemos usar métodos estáticos de interfaz de Java para eliminar las clases de utilidad como Collections y mover todos sus métodos estáticos a la interfaz correspondiente, lo que sería más fácil de encontrar y usar.

Interfaces Funcionales de Java

Antes de concluir la publicación, me gustaría proporcionar una breve introducción a las interfaces funcionales. Una interfaz con exactamente un método abstracto se conoce como interfaz funcional. Se ha introducido una nueva anotación @FunctionalInterface para marcar una interfaz como interfaz funcional. La anotación @FunctionalInterface es una facilidad para evitar la adición accidental de métodos abstractos en las interfaces funcionales. Es opcional, pero es una buena práctica usarla. Las interfaces funcionales son una característica esperada y muy buscada de Java 8 porque nos permite usar expresiones lambda para instanciarlas. Se ha añadido un nuevo paquete java.util.function con un conjunto de interfaces funcionales para proporcionar tipos objetivo para expresiones lambda y referencias a métodos. Investigaremos las interfaces funcionales y las expresiones lambda en futuras publicaciones.

Source:
https://www.digitalocean.com/community/tutorials/java-8-interface-changes-static-method-default-method